Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * xloginsert.c
4 : : * Functions for constructing WAL records
5 : : *
6 : : * Constructing a WAL record begins with a call to XLogBeginInsert,
7 : : * followed by a number of XLogRegister* calls. The registered data is
8 : : * collected in private working memory, and finally assembled into a chain
9 : : * of XLogRecData structs by a call to XLogRecordAssemble(). See
10 : : * access/transam/README for details.
11 : : *
12 : : * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
13 : : * Portions Copyright (c) 1994, Regents of the University of California
14 : : *
15 : : * src/backend/access/transam/xloginsert.c
16 : : *
17 : : *-------------------------------------------------------------------------
18 : : */
19 : :
20 : : #include "postgres.h"
21 : :
22 : : #ifdef USE_LZ4
23 : : #include <lz4.h>
24 : : #endif
25 : :
26 : : #ifdef USE_ZSTD
27 : : #include <zstd.h>
28 : : #endif
29 : :
30 : : #include "access/xact.h"
31 : : #include "access/xlog.h"
32 : : #include "access/xlog_internal.h"
33 : : #include "access/xloginsert.h"
34 : : #include "catalog/pg_control.h"
35 : : #include "common/pg_lzcompress.h"
36 : : #include "miscadmin.h"
37 : : #include "pg_trace.h"
38 : : #include "replication/origin.h"
39 : : #include "storage/bufmgr.h"
40 : : #include "storage/proc.h"
41 : : #include "utils/memutils.h"
42 : :
43 : : /*
44 : : * Guess the maximum buffer size required to store a compressed version of
45 : : * backup block image.
46 : : */
47 : : #ifdef USE_LZ4
48 : : #define LZ4_MAX_BLCKSZ LZ4_COMPRESSBOUND(BLCKSZ)
49 : : #else
50 : : #define LZ4_MAX_BLCKSZ 0
51 : : #endif
52 : :
53 : : #ifdef USE_ZSTD
54 : : #define ZSTD_MAX_BLCKSZ ZSTD_COMPRESSBOUND(BLCKSZ)
55 : : #else
56 : : #define ZSTD_MAX_BLCKSZ 0
57 : : #endif
58 : :
59 : : #define PGLZ_MAX_BLCKSZ PGLZ_MAX_OUTPUT(BLCKSZ)
60 : :
61 : : /* Buffer size required to store a compressed version of backup block image */
62 : : #define COMPRESS_BUFSIZE Max(Max(PGLZ_MAX_BLCKSZ, LZ4_MAX_BLCKSZ), ZSTD_MAX_BLCKSZ)
63 : :
64 : : /*
65 : : * For each block reference registered with XLogRegisterBuffer, we fill in
66 : : * a registered_buffer struct.
67 : : */
68 : : typedef struct
69 : : {
70 : : bool in_use; /* is this slot in use? */
71 : : uint8 flags; /* REGBUF_* flags */
72 : : RelFileLocator rlocator; /* identifies the relation and block */
73 : : ForkNumber forkno;
74 : : BlockNumber block;
75 : : const PageData *page; /* page content */
76 : : uint32 rdata_len; /* total length of data in rdata chain */
77 : : XLogRecData *rdata_head; /* head of the chain of data registered with
78 : : * this block */
79 : : XLogRecData *rdata_tail; /* last entry in the chain, or &rdata_head if
80 : : * empty */
81 : :
82 : : XLogRecData bkp_rdatas[2]; /* temporary rdatas used to hold references to
83 : : * backup block data in XLogRecordAssemble() */
84 : :
85 : : /* buffer to store a compressed version of backup block image */
86 : : char compressed_page[COMPRESS_BUFSIZE];
87 : : } registered_buffer;
88 : :
89 : : static registered_buffer *registered_buffers;
90 : : static int max_registered_buffers; /* allocated size */
91 : : static int max_registered_block_id = 0; /* highest block_id + 1 currently
92 : : * registered */
93 : :
94 : : /*
95 : : * A chain of XLogRecDatas to hold the "main data" of a WAL record, registered
96 : : * with XLogRegisterData(...).
97 : : */
98 : : static XLogRecData *mainrdata_head;
99 : : static XLogRecData *mainrdata_last = (XLogRecData *) &mainrdata_head;
100 : : static uint64 mainrdata_len; /* total # of bytes in chain */
101 : :
102 : : /* flags for the in-progress insertion */
103 : : static uint8 curinsert_flags = 0;
104 : :
105 : : /*
106 : : * These are used to hold the record header while constructing a record.
107 : : * 'hdr_scratch' is not a plain variable, but is palloc'd at initialization,
108 : : * because we want it to be MAXALIGNed and padding bytes zeroed.
109 : : *
110 : : * For simplicity, it's allocated large enough to hold the headers for any
111 : : * WAL record.
112 : : */
113 : : static XLogRecData hdr_rdt;
114 : : static char *hdr_scratch = NULL;
115 : :
116 : : #define SizeOfXlogOrigin (sizeof(RepOriginId) + sizeof(char))
117 : : #define SizeOfXLogTransactionId (sizeof(TransactionId) + sizeof(char))
118 : :
119 : : #define HEADER_SCRATCH_SIZE \
120 : : (SizeOfXLogRecord + \
121 : : MaxSizeOfXLogRecordBlockHeader * (XLR_MAX_BLOCK_ID + 1) + \
122 : : SizeOfXLogRecordDataHeaderLong + SizeOfXlogOrigin + \
123 : : SizeOfXLogTransactionId)
124 : :
125 : : /*
126 : : * An array of XLogRecData structs, to hold registered data.
127 : : */
128 : : static XLogRecData *rdatas;
129 : : static int num_rdatas; /* entries currently used */
130 : : static int max_rdatas; /* allocated size */
131 : :
132 : : static bool begininsert_called = false;
133 : :
134 : : /* Memory context to hold the registered buffer and data references. */
135 : : static MemoryContext xloginsert_cxt;
136 : :
137 : : static XLogRecData *XLogRecordAssemble(RmgrId rmid, uint8 info,
138 : : XLogRecPtr RedoRecPtr, bool doPageWrites,
139 : : XLogRecPtr *fpw_lsn, int *num_fpi,
140 : : bool *topxid_included);
141 : : static bool XLogCompressBackupBlock(const PageData *page, uint16 hole_offset,
142 : : uint16 hole_length, void *dest, uint16 *dlen);
143 : :
144 : : /*
145 : : * Begin constructing a WAL record. This must be called before the
146 : : * XLogRegister* functions and XLogInsert().
147 : : */
148 : : void
3994 heikki.linnakangas@i 149 :CBC 14518582 : XLogBeginInsert(void)
150 : : {
151 [ - + ]: 14518582 : Assert(max_registered_block_id == 0);
152 [ - + ]: 14518582 : Assert(mainrdata_last == (XLogRecData *) &mainrdata_head);
153 [ - + ]: 14518582 : Assert(mainrdata_len == 0);
154 : :
155 : : /* cross-check on whether we should be here or not */
156 [ - + ]: 14518582 : if (!XLogInsertAllowed())
3994 heikki.linnakangas@i 157 [ # # ]:UBC 0 : elog(ERROR, "cannot make new WAL entries during recovery");
158 : :
3774 heikki.linnakangas@i 159 [ - + ]:CBC 14518582 : if (begininsert_called)
3774 heikki.linnakangas@i 160 [ # # ]:UBC 0 : elog(ERROR, "XLogBeginInsert was already called");
161 : :
3994 heikki.linnakangas@i 162 :CBC 14518582 : begininsert_called = true;
163 : 14518582 : }
164 : :
165 : : /*
166 : : * Ensure that there are enough buffer and data slots in the working area,
167 : : * for subsequent XLogRegisterBuffer, XLogRegisterData and XLogRegisterBufData
168 : : * calls.
169 : : *
170 : : * There is always space for a small number of buffers and data chunks, enough
171 : : * for most record types. This function is for the exceptional cases that need
172 : : * more.
173 : : */
174 : : void
175 : 68345 : XLogEnsureRecordSpace(int max_block_id, int ndatas)
176 : : {
177 : : int nbuffers;
178 : :
179 : : /*
180 : : * This must be called before entering a critical section, because
181 : : * allocating memory inside a critical section can fail. repalloc() will
182 : : * check the same, but better to check it here too so that we fail
183 : : * consistently even if the arrays happen to be large enough already.
184 : : */
185 [ - + ]: 68345 : Assert(CritSectionCount == 0);
186 : :
187 : : /* the minimum values can't be decreased */
188 [ + + ]: 68345 : if (max_block_id < XLR_NORMAL_MAX_BLOCK_ID)
189 : 2028 : max_block_id = XLR_NORMAL_MAX_BLOCK_ID;
190 [ + + ]: 68345 : if (ndatas < XLR_NORMAL_RDATAS)
191 : 68320 : ndatas = XLR_NORMAL_RDATAS;
192 : :
193 [ - + ]: 68345 : if (max_block_id > XLR_MAX_BLOCK_ID)
3994 heikki.linnakangas@i 194 [ # # ]:UBC 0 : elog(ERROR, "maximum number of WAL record block references exceeded");
3994 heikki.linnakangas@i 195 :CBC 68345 : nbuffers = max_block_id + 1;
196 : :
197 [ + + ]: 68345 : if (nbuffers > max_registered_buffers)
198 : : {
199 : 1682 : registered_buffers = (registered_buffer *)
200 : 1682 : repalloc(registered_buffers, sizeof(registered_buffer) * nbuffers);
201 : :
202 : : /*
203 : : * At least the padding bytes in the structs must be zeroed, because
204 : : * they are included in WAL data, but initialize it all for tidiness.
205 : : */
206 [ + - + - : 1682 : MemSet(®istered_buffers[max_registered_buffers], 0,
+ - - + -
- ]
207 : : (nbuffers - max_registered_buffers) * sizeof(registered_buffer));
208 : 1682 : max_registered_buffers = nbuffers;
209 : : }
210 : :
211 [ + + ]: 68345 : if (ndatas > max_rdatas)
212 : : {
213 : 15 : rdatas = (XLogRecData *) repalloc(rdatas, sizeof(XLogRecData) * ndatas);
214 : 15 : max_rdatas = ndatas;
215 : : }
216 : 68345 : }
217 : :
218 : : /*
219 : : * Reset WAL record construction buffers.
220 : : */
221 : : void
222 : 14548460 : XLogResetInsertion(void)
223 : : {
224 : : int i;
225 : :
226 [ + + ]: 29388990 : for (i = 0; i < max_registered_block_id; i++)
227 : 14840530 : registered_buffers[i].in_use = false;
228 : :
229 : 14548460 : num_rdatas = 0;
230 : 14548460 : max_registered_block_id = 0;
231 : 14548460 : mainrdata_len = 0;
232 : 14548460 : mainrdata_last = (XLogRecData *) &mainrdata_head;
3231 andres@anarazel.de 233 : 14548460 : curinsert_flags = 0;
3994 heikki.linnakangas@i 234 : 14548460 : begininsert_called = false;
235 : 14548460 : }
236 : :
237 : : /*
238 : : * Register a reference to a buffer with the WAL record being constructed.
239 : : * This must be called for every page that the WAL-logged operation modifies.
240 : : */
241 : : void
242 : 14571905 : XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
243 : : {
244 : : registered_buffer *regbuf;
245 : :
246 : : /* NO_IMAGE doesn't make sense with FORCE_IMAGE */
247 [ + + - + ]: 14571905 : Assert(!((flags & REGBUF_FORCE_IMAGE) && (flags & (REGBUF_NO_IMAGE))));
248 [ - + ]: 14571905 : Assert(begininsert_called);
249 : :
250 : : /*
251 : : * Ordinarily, buffer should be exclusive-locked and marked dirty before
252 : : * we get here, otherwise we could end up violating one of the rules in
253 : : * access/transam/README.
254 : : *
255 : : * Some callers intentionally register a clean page and never update that
256 : : * page's LSN; in that case they can pass the flag REGBUF_NO_CHANGE to
257 : : * bypass these checks.
258 : : */
259 : : #ifdef USE_ASSERT_CHECKING
735 jdavis@postgresql.or 260 [ + + ]: 14571905 : if (!(flags & REGBUF_NO_CHANGE))
19 andres@anarazel.de 261 [ + - - + ]:GNC 14571745 : Assert(BufferIsLockedByMeInMode(buffer, BUFFER_LOCK_EXCLUSIVE) &&
262 : : BufferIsDirty(buffer));
263 : : #endif
264 : :
3994 heikki.linnakangas@i 265 [ + + ]:CBC 14571905 : if (block_id >= max_registered_block_id)
266 : : {
267 [ - + ]: 14209236 : if (block_id >= max_registered_buffers)
3994 heikki.linnakangas@i 268 [ # # ]:UBC 0 : elog(ERROR, "too many registered buffers");
3994 heikki.linnakangas@i 269 :CBC 14209236 : max_registered_block_id = block_id + 1;
270 : : }
271 : :
272 : 14571905 : regbuf = ®istered_buffers[block_id];
273 : :
1209 rhaas@postgresql.org 274 : 14571905 : BufferGetTag(buffer, ®buf->rlocator, ®buf->forkno, ®buf->block);
3477 kgrittn@postgresql.o 275 : 14571905 : regbuf->page = BufferGetPage(buffer);
3994 heikki.linnakangas@i 276 : 14571905 : regbuf->flags = flags;
277 : 14571905 : regbuf->rdata_tail = (XLogRecData *) ®buf->rdata_head;
278 : 14571905 : regbuf->rdata_len = 0;
279 : :
280 : : /*
281 : : * Check that this page hasn't already been registered with some other
282 : : * block_id.
283 : : */
284 : : #ifdef USE_ASSERT_CHECKING
285 : : {
286 : : int i;
287 : :
288 [ + + ]: 31108281 : for (i = 0; i < max_registered_block_id; i++)
289 : : {
290 : 16536376 : registered_buffer *regbuf_old = ®istered_buffers[i];
291 : :
292 [ + + + + ]: 16536376 : if (i == block_id || !regbuf_old->in_use)
293 : 14948068 : continue;
294 : :
1209 rhaas@postgresql.org 295 [ + - + - : 1588308 : Assert(!RelFileLocatorEquals(regbuf_old->rlocator, regbuf->rlocator) ||
+ - + + -
+ ]
296 : : regbuf_old->forkno != regbuf->forkno ||
297 : : regbuf_old->block != regbuf->block);
298 : : }
299 : : }
300 : : #endif
301 : :
3994 heikki.linnakangas@i 302 : 14571905 : regbuf->in_use = true;
303 : 14571905 : }
304 : :
305 : : /*
306 : : * Like XLogRegisterBuffer, but for registering a block that's not in the
307 : : * shared buffer pool (i.e. when you don't have a Buffer for it).
308 : : */
309 : : void
1209 rhaas@postgresql.org 310 : 257442 : XLogRegisterBlock(uint8 block_id, RelFileLocator *rlocator, ForkNumber forknum,
311 : : BlockNumber blknum, const PageData *page, uint8 flags)
312 : : {
313 : : registered_buffer *regbuf;
314 : :
3994 heikki.linnakangas@i 315 [ - + ]: 257442 : Assert(begininsert_called);
316 : :
317 [ + - ]: 257442 : if (block_id >= max_registered_block_id)
318 : 257442 : max_registered_block_id = block_id + 1;
319 : :
320 [ - + ]: 257442 : if (block_id >= max_registered_buffers)
3994 heikki.linnakangas@i 321 [ # # ]:UBC 0 : elog(ERROR, "too many registered buffers");
322 : :
3994 heikki.linnakangas@i 323 :CBC 257442 : regbuf = ®istered_buffers[block_id];
324 : :
1209 rhaas@postgresql.org 325 : 257442 : regbuf->rlocator = *rlocator;
3994 heikki.linnakangas@i 326 : 257442 : regbuf->forkno = forknum;
327 : 257442 : regbuf->block = blknum;
328 : 257442 : regbuf->page = page;
329 : 257442 : regbuf->flags = flags;
330 : 257442 : regbuf->rdata_tail = (XLogRecData *) ®buf->rdata_head;
331 : 257442 : regbuf->rdata_len = 0;
332 : :
333 : : /*
334 : : * Check that this page hasn't already been registered with some other
335 : : * block_id.
336 : : */
337 : : #ifdef USE_ASSERT_CHECKING
338 : : {
339 : : int i;
340 : :
341 [ + + ]: 707111 : for (i = 0; i < max_registered_block_id; i++)
342 : : {
343 : 449669 : registered_buffer *regbuf_old = ®istered_buffers[i];
344 : :
345 [ + + - + ]: 449669 : if (i == block_id || !regbuf_old->in_use)
346 : 257442 : continue;
347 : :
1209 rhaas@postgresql.org 348 [ + - + - : 192227 : Assert(!RelFileLocatorEquals(regbuf_old->rlocator, regbuf->rlocator) ||
+ - + - -
+ ]
349 : : regbuf_old->forkno != regbuf->forkno ||
350 : : regbuf_old->block != regbuf->block);
351 : : }
352 : : }
353 : : #endif
354 : :
3994 heikki.linnakangas@i 355 : 257442 : regbuf->in_use = true;
356 : 257442 : }
357 : :
358 : : /*
359 : : * Add data to the WAL record that's being constructed.
360 : : *
361 : : * The data is appended to the "main chunk", available at replay with
362 : : * XLogRecGetData().
363 : : */
364 : : void
256 peter@eisentraut.org 365 : 15004192 : XLogRegisterData(const void *data, uint32 len)
366 : : {
367 : : XLogRecData *rdata;
368 : :
3994 heikki.linnakangas@i 369 [ - + ]: 15004192 : Assert(begininsert_called);
370 : :
371 [ - + ]: 15004192 : if (num_rdatas >= max_rdatas)
934 michael@paquier.xyz 372 [ # # ]:UBC 0 : ereport(ERROR,
373 : : (errmsg_internal("too much WAL data"),
374 : : errdetail_internal("%d out of %d data segments are already in use.",
375 : : num_rdatas, max_rdatas)));
3994 heikki.linnakangas@i 376 :CBC 15004192 : rdata = &rdatas[num_rdatas++];
377 : :
378 : 15004192 : rdata->data = data;
379 : 15004192 : rdata->len = len;
380 : :
381 : : /*
382 : : * we use the mainrdata_last pointer to track the end of the chain, so no
383 : : * need to clear 'next' here.
384 : : */
385 : :
386 : 15004192 : mainrdata_last->next = rdata;
387 : 15004192 : mainrdata_last = rdata;
388 : :
389 : 15004192 : mainrdata_len += len;
390 : 15004192 : }
391 : :
392 : : /*
393 : : * Add buffer-specific data to the WAL record that's being constructed.
394 : : *
395 : : * Block_id must reference a block previously registered with
396 : : * XLogRegisterBuffer(). If this is called more than once for the same
397 : : * block_id, the data is appended.
398 : : *
399 : : * The maximum amount of data that can be registered per block is 65535
400 : : * bytes. That should be plenty; if you need more than BLCKSZ bytes to
401 : : * reconstruct the changes to the page, you might as well just log a full
402 : : * copy of it. (the "main data" that's not associated with a block is not
403 : : * limited)
404 : : */
405 : : void
256 peter@eisentraut.org 406 : 20365094 : XLogRegisterBufData(uint8 block_id, const void *data, uint32 len)
407 : : {
408 : : registered_buffer *regbuf;
409 : : XLogRecData *rdata;
410 : :
3994 heikki.linnakangas@i 411 [ - + ]: 20365094 : Assert(begininsert_called);
412 : :
413 : : /* find the registered buffer struct */
414 : 20365094 : regbuf = ®istered_buffers[block_id];
415 [ - + ]: 20365094 : if (!regbuf->in_use)
3994 heikki.linnakangas@i 416 [ # # ]:UBC 0 : elog(ERROR, "no block with id %d registered with WAL insertion",
417 : : block_id);
418 : :
419 : : /*
420 : : * Check against max_rdatas and ensure we do not register more data per
421 : : * buffer than can be handled by the physical data format; i.e. that
422 : : * regbuf->rdata_len does not grow beyond what
423 : : * XLogRecordBlockHeader->data_length can hold.
424 : : */
934 michael@paquier.xyz 425 [ - + ]:CBC 20365094 : if (num_rdatas >= max_rdatas)
934 michael@paquier.xyz 426 [ # # ]:UBC 0 : ereport(ERROR,
427 : : (errmsg_internal("too much WAL data"),
428 : : errdetail_internal("%d out of %d data segments are already in use.",
429 : : num_rdatas, max_rdatas)));
934 michael@paquier.xyz 430 [ + - - + ]:CBC 20365094 : if (regbuf->rdata_len + len > UINT16_MAX || len > UINT16_MAX)
934 michael@paquier.xyz 431 [ # # ]:UBC 0 : ereport(ERROR,
432 : : (errmsg_internal("too much WAL data"),
433 : : errdetail_internal("Registering more than maximum %u bytes allowed to block %u: current %u bytes, adding %u bytes.",
434 : : UINT16_MAX, block_id, regbuf->rdata_len, len)));
435 : :
3994 heikki.linnakangas@i 436 :CBC 20365094 : rdata = &rdatas[num_rdatas++];
437 : :
438 : 20365094 : rdata->data = data;
439 : 20365094 : rdata->len = len;
440 : :
441 : 20365094 : regbuf->rdata_tail->next = rdata;
442 : 20365094 : regbuf->rdata_tail = rdata;
443 : 20365094 : regbuf->rdata_len += len;
444 : 20365094 : }
445 : :
446 : : /*
447 : : * Set insert status flags for the upcoming WAL record.
448 : : *
449 : : * The flags that can be used here are:
450 : : * - XLOG_INCLUDE_ORIGIN, to determine if the replication origin should be
451 : : * included in the record.
452 : : * - XLOG_MARK_UNIMPORTANT, to signal that the record is not important for
453 : : * durability, which allows to avoid triggering WAL archiving and other
454 : : * background activity.
455 : : */
456 : : void
3231 andres@anarazel.de 457 : 9313934 : XLogSetRecordFlags(uint8 flags)
458 : : {
3834 459 [ - + ]: 9313934 : Assert(begininsert_called);
1925 akapila@postgresql.o 460 : 9313934 : curinsert_flags |= flags;
3834 andres@anarazel.de 461 : 9313934 : }
462 : :
463 : : /*
464 : : * Insert an XLOG record having the specified RMID and info bytes, with the
465 : : * body of the record being the data and buffer references registered earlier
466 : : * with XLogRegister* calls.
467 : : *
468 : : * Returns XLOG pointer to end of record (beginning of next record).
469 : : * This can be used as LSN for data pages affected by the logged action.
470 : : * (LSN is the XLOG point up to which the XLOG must be flushed to disk
471 : : * before the data page can be written out. This implements the basic
472 : : * WAL rule "write the log before the data".)
473 : : */
474 : : XLogRecPtr
3994 heikki.linnakangas@i 475 : 14518582 : XLogInsert(RmgrId rmid, uint8 info)
476 : : {
477 : : XLogRecPtr EndPos;
478 : :
479 : : /* XLogBeginInsert() must have been called. */
480 [ - + ]: 14518582 : if (!begininsert_called)
3994 heikki.linnakangas@i 481 [ # # ]:UBC 0 : elog(ERROR, "XLogBeginInsert was not called");
482 : :
483 : : /*
484 : : * The caller can set rmgr bits, XLR_SPECIAL_REL_UPDATE and
485 : : * XLR_CHECK_CONSISTENCY; the rest are reserved for use by me.
486 : : */
3183 rhaas@postgresql.org 487 [ - + ]:CBC 14518582 : if ((info & ~(XLR_RMGR_INFO_MASK |
488 : : XLR_SPECIAL_REL_UPDATE |
489 : : XLR_CHECK_CONSISTENCY)) != 0)
4008 heikki.linnakangas@i 490 [ # # ]:UBC 0 : elog(PANIC, "invalid xlog info mask %02X", info);
491 : :
492 : : TRACE_POSTGRESQL_WAL_INSERT(rmid, info);
493 : :
494 : : /*
495 : : * In bootstrap mode, we don't actually log anything but XLOG resources;
496 : : * return a phony record pointer.
497 : : */
4008 heikki.linnakangas@i 498 [ + + + + ]:CBC 14518582 : if (IsBootstrapProcessingMode() && rmid != RM_XLOG_ID)
499 : : {
3994 500 : 626800 : XLogResetInsertion();
3050 tgl@sss.pgh.pa.us 501 : 626800 : EndPos = SizeOfXLogLongPHD; /* start of 1st chkpt record */
4008 heikki.linnakangas@i 502 : 626800 : return EndPos;
503 : : }
504 : :
505 : : do
506 : : {
507 : : XLogRecPtr RedoRecPtr;
508 : : bool doPageWrites;
1455 akapila@postgresql.o 509 : 13898696 : bool topxid_included = false;
510 : : XLogRecPtr fpw_lsn;
511 : : XLogRecData *rdt;
2001 512 : 13898696 : int num_fpi = 0;
513 : :
514 : : /*
515 : : * Get values needed to decide whether to do full-page writes. Since
516 : : * we don't yet have an insertion lock, these could change under us,
517 : : * but XLogInsertRecord will recheck them once it has a lock.
518 : : */
3994 heikki.linnakangas@i 519 : 13898696 : GetFullPageWriteInfo(&RedoRecPtr, &doPageWrites);
520 : :
521 : 13898696 : rdt = XLogRecordAssemble(rmid, info, RedoRecPtr, doPageWrites,
522 : : &fpw_lsn, &num_fpi, &topxid_included);
523 : :
1455 akapila@postgresql.o 524 : 13898696 : EndPos = XLogInsertRecord(rdt, fpw_lsn, curinsert_flags, num_fpi,
525 : : topxid_included);
3994 heikki.linnakangas@i 526 [ + + ]: 13898696 : } while (EndPos == InvalidXLogRecPtr);
527 : :
528 : 13891782 : XLogResetInsertion();
529 : :
4008 530 : 13891782 : return EndPos;
531 : : }
532 : :
533 : : /*
534 : : * Simple wrapper to XLogInsert to insert a WAL record with elementary
535 : : * contents (only an int64 is supported as value currently).
536 : : */
537 : : XLogRecPtr
112 alvherre@kurilemu.de 538 :GNC 69 : XLogSimpleInsertInt64(RmgrId rmid, uint8 info, int64 value)
539 : : {
540 : 69 : XLogBeginInsert();
541 : 69 : XLogRegisterData(&value, sizeof(value));
542 : 69 : return XLogInsert(rmid, info);
543 : : }
544 : :
545 : : /*
546 : : * Assemble a WAL record from the registered data and buffers into an
547 : : * XLogRecData chain, ready for insertion with XLogInsertRecord().
548 : : *
549 : : * The record header fields are filled in, except for the xl_prev field. The
550 : : * calculated CRC does not include the record header yet.
551 : : *
552 : : * If there are any registered buffers, and a full-page image was not taken
553 : : * of all of them, *fpw_lsn is set to the lowest LSN among such pages. This
554 : : * signals that the assembled record is only good for insertion on the
555 : : * assumption that the RedoRecPtr and doPageWrites values were up-to-date.
556 : : *
557 : : * *topxid_included is set if the topmost transaction ID is logged with the
558 : : * current subtransaction.
559 : : */
560 : : static XLogRecData *
3994 heikki.linnakangas@i 561 :CBC 13898696 : XLogRecordAssemble(RmgrId rmid, uint8 info,
562 : : XLogRecPtr RedoRecPtr, bool doPageWrites,
563 : : XLogRecPtr *fpw_lsn, int *num_fpi, bool *topxid_included)
564 : : {
565 : : XLogRecData *rdt;
934 michael@paquier.xyz 566 : 13898696 : uint64 total_len = 0;
567 : : int block_id;
568 : : pg_crc32c rdata_crc;
3994 heikki.linnakangas@i 569 : 13898696 : registered_buffer *prev_regbuf = NULL;
570 : : XLogRecData *rdt_datas_last;
571 : : XLogRecord *rechdr;
572 : 13898696 : char *scratch = hdr_scratch;
573 : :
574 : : /*
575 : : * Note: this function can be called multiple times for the same record.
576 : : * All the modifications we do to the rdata chains below must handle that.
577 : : */
578 : :
579 : : /* The record begins with the fixed-size header */
580 : 13898696 : rechdr = (XLogRecord *) scratch;
581 : 13898696 : scratch += SizeOfXLogRecord;
582 : :
583 : 13898696 : hdr_rdt.next = NULL;
584 : 13898696 : rdt_datas_last = &hdr_rdt;
585 : 13898696 : hdr_rdt.data = hdr_scratch;
586 : :
587 : : /*
588 : : * Enforce consistency checks for this record if user is looking for it.
589 : : * Do this before at the beginning of this routine to give the possibility
590 : : * for callers of XLogInsert() to pass XLR_CHECK_CONSISTENCY directly for
591 : : * a record.
592 : : */
3183 rhaas@postgresql.org 593 [ + + ]: 13898696 : if (wal_consistency_checking[rmid])
594 : 2170746 : info |= XLR_CHECK_CONSISTENCY;
595 : :
596 : : /*
597 : : * Make an rdata chain containing all the data portions of all block
598 : : * references. This includes the data for full-page images. Also append
599 : : * the headers for the block references in the scratch buffer.
600 : : */
4008 heikki.linnakangas@i 601 : 13898696 : *fpw_lsn = InvalidXLogRecPtr;
3994 602 [ + + ]: 28142138 : for (block_id = 0; block_id < max_registered_block_id; block_id++)
603 : : {
604 : 14243442 : registered_buffer *regbuf = ®istered_buffers[block_id];
605 : : bool needs_backup;
606 : : bool needs_data;
607 : : XLogRecordBlockHeader bkpb;
608 : : XLogRecordBlockImageHeader bimg;
3879 fujii@postgresql.org 609 : 14243442 : XLogRecordBlockCompressHeader cbimg = {0};
610 : : bool samerel;
3883 611 : 14243442 : bool is_compressed = false;
612 : : bool include_image;
613 : :
3994 heikki.linnakangas@i 614 [ + + ]: 14243442 : if (!regbuf->in_use)
615 : 11183 : continue;
616 : :
617 : : /* Determine if this block needs to be backed up */
618 [ + + ]: 14232259 : if (regbuf->flags & REGBUF_FORCE_IMAGE)
619 : 299726 : needs_backup = true;
620 [ + + ]: 13932533 : else if (regbuf->flags & REGBUF_NO_IMAGE)
621 : 208362 : needs_backup = false;
622 [ + + ]: 13724171 : else if (!doPageWrites)
623 : 270682 : needs_backup = false;
624 : : else
625 : : {
626 : : /*
627 : : * We assume page LSN is first data on *every* page that can be
628 : : * passed to XLogInsert, whether it has the standard page layout
629 : : * or not.
630 : : */
631 : 13453489 : XLogRecPtr page_lsn = PageGetLSN(regbuf->page);
632 : :
633 : 13453489 : needs_backup = (page_lsn <= RedoRecPtr);
634 [ + + ]: 13453489 : if (!needs_backup)
635 : : {
636 [ + + + + ]: 13358831 : if (*fpw_lsn == InvalidXLogRecPtr || page_lsn < *fpw_lsn)
637 : 12939128 : *fpw_lsn = page_lsn;
638 : : }
639 : : }
640 : :
641 : : /* Determine if the buffer data needs to included */
642 [ + + ]: 14232259 : if (regbuf->rdata_len == 0)
643 : 2671060 : needs_data = false;
644 [ + + ]: 11561199 : else if ((regbuf->flags & REGBUF_KEEP_DATA) != 0)
645 : 297404 : needs_data = true;
646 : : else
647 : 11263795 : needs_data = !needs_backup;
648 : :
649 : 14232259 : bkpb.id = block_id;
650 : 14232259 : bkpb.fork_flags = regbuf->forkno;
651 : 14232259 : bkpb.data_length = 0;
652 : :
653 [ + + ]: 14232259 : if ((regbuf->flags & REGBUF_WILL_INIT) == REGBUF_WILL_INIT)
654 : 205091 : bkpb.fork_flags |= BKPBLOCK_WILL_INIT;
655 : :
656 : : /*
657 : : * If needs_backup is true or WAL checking is enabled for current
658 : : * resource manager, log a full-page write for the current block.
659 : : */
3183 rhaas@postgresql.org 660 [ + + + + ]: 14232259 : include_image = needs_backup || (info & XLR_CHECK_CONSISTENCY) != 0;
661 : :
662 [ + + ]: 14232259 : if (include_image)
663 : : {
280 peter@eisentraut.org 664 : 2707625 : const PageData *page = regbuf->page;
2812 tgl@sss.pgh.pa.us 665 : 2707625 : uint16 compressed_len = 0;
666 : :
667 : : /*
668 : : * The page needs to be backed up, so calculate its hole length
669 : : * and offset.
670 : : */
3994 heikki.linnakangas@i 671 [ + + ]: 2707625 : if (regbuf->flags & REGBUF_STANDARD)
672 : : {
673 : : /* Assume we can omit data between pd_lower and pd_upper */
674 : 2556221 : uint16 lower = ((PageHeader) page)->pd_lower;
675 : 2556221 : uint16 upper = ((PageHeader) page)->pd_upper;
676 : :
677 [ + + + + ]: 2556221 : if (lower >= SizeOfPageHeaderData &&
678 [ + - ]: 2554363 : upper > lower &&
679 : : upper <= BLCKSZ)
680 : : {
3879 fujii@postgresql.org 681 : 2554363 : bimg.hole_offset = lower;
682 : 2554363 : cbimg.hole_length = upper - lower;
683 : : }
684 : : else
685 : : {
686 : : /* No "hole" to remove */
687 : 1858 : bimg.hole_offset = 0;
688 : 1858 : cbimg.hole_length = 0;
689 : : }
690 : : }
691 : : else
692 : : {
693 : : /* Not a standard page header, don't try to eliminate "hole" */
694 : 151404 : bimg.hole_offset = 0;
695 : 151404 : cbimg.hole_length = 0;
696 : : }
697 : :
698 : : /*
699 : : * Try to compress a block image if wal_compression is enabled
700 : : */
1581 michael@paquier.xyz 701 [ - + ]: 2707625 : if (wal_compression != WAL_COMPRESSION_NONE)
702 : : {
703 : : is_compressed =
3879 fujii@postgresql.org 704 :UBC 0 : XLogCompressBackupBlock(page, bimg.hole_offset,
705 : 0 : cbimg.hole_length,
3883 706 : 0 : regbuf->compressed_page,
707 : : &compressed_len);
708 : : }
709 : :
710 : : /*
711 : : * Fill in the remaining fields in the XLogRecordBlockHeader
712 : : * struct
713 : : */
3994 heikki.linnakangas@i 714 :CBC 2707625 : bkpb.fork_flags |= BKPBLOCK_HAS_IMAGE;
715 : :
716 : : /* Report a full page image constructed for the WAL record */
2001 akapila@postgresql.o 717 : 2707625 : *num_fpi += 1;
718 : :
719 : : /*
720 : : * Construct XLogRecData entries for the page content.
721 : : */
3994 heikki.linnakangas@i 722 : 2707625 : rdt_datas_last->next = ®buf->bkp_rdatas[0];
723 : 2707625 : rdt_datas_last = rdt_datas_last->next;
724 : :
3879 fujii@postgresql.org 725 : 2707625 : bimg.bimg_info = (cbimg.hole_length == 0) ? 0 : BKPIMAGE_HAS_HOLE;
726 : :
727 : : /*
728 : : * If WAL consistency checking is enabled for the resource manager
729 : : * of this WAL record, a full-page image is included in the record
730 : : * for the block modified. During redo, the full-page is replayed
731 : : * only if BKPIMAGE_APPLY is set.
732 : : */
3183 rhaas@postgresql.org 733 [ + + ]: 2707625 : if (needs_backup)
734 : 394384 : bimg.bimg_info |= BKPIMAGE_APPLY;
735 : :
3883 fujii@postgresql.org 736 [ - + ]: 2707625 : if (is_compressed)
737 : : {
738 : : /* The current compression is stored in the WAL record */
3883 fujii@postgresql.org 739 :UBC 0 : bimg.length = compressed_len;
740 : :
741 : : /* Set the compression method used for this block */
1581 michael@paquier.xyz 742 [ # # # # : 0 : switch ((WalCompression) wal_compression)
# ]
743 : : {
744 : 0 : case WAL_COMPRESSION_PGLZ:
745 : 0 : bimg.bimg_info |= BKPIMAGE_COMPRESS_PGLZ;
746 : 0 : break;
747 : :
748 : 0 : case WAL_COMPRESSION_LZ4:
749 : : #ifdef USE_LZ4
750 : 0 : bimg.bimg_info |= BKPIMAGE_COMPRESS_LZ4;
751 : : #else
752 : : elog(ERROR, "LZ4 is not supported by this build");
753 : : #endif
754 : 0 : break;
755 : :
1326 756 : 0 : case WAL_COMPRESSION_ZSTD:
757 : : #ifdef USE_ZSTD
758 : 0 : bimg.bimg_info |= BKPIMAGE_COMPRESS_ZSTD;
759 : : #else
760 : : elog(ERROR, "zstd is not supported by this build");
761 : : #endif
762 : 0 : break;
763 : :
1581 764 : 0 : case WAL_COMPRESSION_NONE:
765 : 0 : Assert(false); /* cannot happen */
766 : : break;
767 : : /* no default case, so that compiler will warn */
768 : : }
769 : :
3883 fujii@postgresql.org 770 : 0 : rdt_datas_last->data = regbuf->compressed_page;
771 : 0 : rdt_datas_last->len = compressed_len;
772 : : }
773 : : else
774 : : {
3879 fujii@postgresql.org 775 :CBC 2707625 : bimg.length = BLCKSZ - cbimg.hole_length;
776 : :
777 [ + + ]: 2707625 : if (cbimg.hole_length == 0)
778 : : {
3883 779 : 153262 : rdt_datas_last->data = page;
780 : 153262 : rdt_datas_last->len = BLCKSZ;
781 : : }
782 : : else
783 : : {
784 : : /* must skip the hole */
785 : 2554363 : rdt_datas_last->data = page;
3879 786 : 2554363 : rdt_datas_last->len = bimg.hole_offset;
787 : :
3883 788 : 2554363 : rdt_datas_last->next = ®buf->bkp_rdatas[1];
789 : 2554363 : rdt_datas_last = rdt_datas_last->next;
790 : :
3879 791 : 2554363 : rdt_datas_last->data =
792 : 2554363 : page + (bimg.hole_offset + cbimg.hole_length);
793 : 2554363 : rdt_datas_last->len =
794 : 2554363 : BLCKSZ - (bimg.hole_offset + cbimg.hole_length);
795 : : }
796 : : }
797 : :
3883 798 : 2707625 : total_len += bimg.length;
799 : : }
800 : :
3994 heikki.linnakangas@i 801 [ + + ]: 14232259 : if (needs_data)
802 : : {
803 : : /*
804 : : * When copying to XLogRecordBlockHeader, the length is narrowed
805 : : * to an uint16. Double-check that it is still correct.
806 : : */
1188 michael@paquier.xyz 807 [ - + ]: 11519478 : Assert(regbuf->rdata_len <= UINT16_MAX);
808 : :
809 : : /*
810 : : * Link the caller-supplied rdata chain for this buffer to the
811 : : * overall list.
812 : : */
3994 heikki.linnakangas@i 813 : 11519478 : bkpb.fork_flags |= BKPBLOCK_HAS_DATA;
1188 michael@paquier.xyz 814 : 11519478 : bkpb.data_length = (uint16) regbuf->rdata_len;
3994 heikki.linnakangas@i 815 : 11519478 : total_len += regbuf->rdata_len;
816 : :
817 : 11519478 : rdt_datas_last->next = regbuf->rdata_head;
818 : 11519478 : rdt_datas_last = regbuf->rdata_tail;
819 : : }
820 : :
1209 rhaas@postgresql.org 821 [ + + + - : 14232259 : if (prev_regbuf && RelFileLocatorEquals(regbuf->rlocator, prev_regbuf->rlocator))
+ - + - ]
822 : : {
3994 heikki.linnakangas@i 823 : 713417 : samerel = true;
824 : 713417 : bkpb.fork_flags |= BKPBLOCK_SAME_REL;
825 : : }
826 : : else
827 : 13518842 : samerel = false;
3829 828 : 14232259 : prev_regbuf = regbuf;
829 : :
830 : : /* Ok, copy the header to the scratch buffer */
3994 831 : 14232259 : memcpy(scratch, &bkpb, SizeOfXLogRecordBlockHeader);
832 : 14232259 : scratch += SizeOfXLogRecordBlockHeader;
3183 rhaas@postgresql.org 833 [ + + ]: 14232259 : if (include_image)
834 : : {
3994 heikki.linnakangas@i 835 : 2707625 : memcpy(scratch, &bimg, SizeOfXLogRecordBlockImageHeader);
836 : 2707625 : scratch += SizeOfXLogRecordBlockImageHeader;
3879 fujii@postgresql.org 837 [ + + - + ]: 2707625 : if (cbimg.hole_length != 0 && is_compressed)
838 : : {
3883 fujii@postgresql.org 839 :UBC 0 : memcpy(scratch, &cbimg,
840 : : SizeOfXLogRecordBlockCompressHeader);
841 : 0 : scratch += SizeOfXLogRecordBlockCompressHeader;
842 : : }
843 : : }
3994 heikki.linnakangas@i 844 [ + + ]:CBC 14232259 : if (!samerel)
845 : : {
1209 rhaas@postgresql.org 846 : 13518842 : memcpy(scratch, ®buf->rlocator, sizeof(RelFileLocator));
847 : 13518842 : scratch += sizeof(RelFileLocator);
848 : : }
3994 heikki.linnakangas@i 849 : 14232259 : memcpy(scratch, ®buf->block, sizeof(BlockNumber));
850 : 14232259 : scratch += sizeof(BlockNumber);
851 : : }
852 : :
853 : : /* followed by the record's origin, if any */
3231 andres@anarazel.de 854 [ + + ]: 13898696 : if ((curinsert_flags & XLOG_INCLUDE_ORIGIN) &&
855 [ + + ]: 8630673 : replorigin_session_origin != InvalidRepOriginId)
856 : : {
3135 tgl@sss.pgh.pa.us 857 : 150143 : *(scratch++) = (char) XLR_BLOCK_ID_ORIGIN;
3682 alvherre@alvh.no-ip. 858 : 150143 : memcpy(scratch, &replorigin_session_origin, sizeof(replorigin_session_origin));
859 : 150143 : scratch += sizeof(replorigin_session_origin);
860 : : }
861 : :
862 : : /* followed by toplevel XID, if not already included in previous record */
1455 akapila@postgresql.o 863 [ + + ]: 13898696 : if (IsSubxactTopXidLogPending())
864 : : {
1925 865 : 221 : TransactionId xid = GetTopTransactionIdIfAny();
866 : :
867 : : /* Set the flag that the top xid is included in the WAL */
1455 868 : 221 : *topxid_included = true;
869 : :
1925 870 : 221 : *(scratch++) = (char) XLR_BLOCK_ID_TOPLEVEL_XID;
871 : 221 : memcpy(scratch, &xid, sizeof(TransactionId));
872 : 221 : scratch += sizeof(TransactionId);
873 : : }
874 : :
875 : : /* followed by main data, if any */
3994 heikki.linnakangas@i 876 [ + + ]: 13898696 : if (mainrdata_len > 0)
877 : : {
878 [ + + ]: 13580194 : if (mainrdata_len > 255)
879 : : {
880 : : uint32 mainrdata_len_4b;
881 : :
934 michael@paquier.xyz 882 [ - + ]: 31112 : if (mainrdata_len > PG_UINT32_MAX)
934 michael@paquier.xyz 883 [ # # ]:UBC 0 : ereport(ERROR,
884 : : (errmsg_internal("too much WAL data"),
885 : : errdetail_internal("Main data length is %" PRIu64 " bytes for a maximum of %u bytes.",
886 : : mainrdata_len,
887 : : PG_UINT32_MAX)));
888 : :
934 michael@paquier.xyz 889 :CBC 31112 : mainrdata_len_4b = (uint32) mainrdata_len;
3135 tgl@sss.pgh.pa.us 890 : 31112 : *(scratch++) = (char) XLR_BLOCK_ID_DATA_LONG;
934 michael@paquier.xyz 891 : 31112 : memcpy(scratch, &mainrdata_len_4b, sizeof(uint32));
3994 heikki.linnakangas@i 892 : 31112 : scratch += sizeof(uint32);
893 : : }
894 : : else
895 : : {
3135 tgl@sss.pgh.pa.us 896 : 13549082 : *(scratch++) = (char) XLR_BLOCK_ID_DATA_SHORT;
3994 heikki.linnakangas@i 897 : 13549082 : *(scratch++) = (uint8) mainrdata_len;
898 : : }
899 : 13580194 : rdt_datas_last->next = mainrdata_head;
900 : 13580194 : rdt_datas_last = mainrdata_last;
901 : 13580194 : total_len += mainrdata_len;
902 : : }
903 : 13898696 : rdt_datas_last->next = NULL;
904 : :
905 : 13898696 : hdr_rdt.len = (scratch - hdr_scratch);
906 : 13898696 : total_len += hdr_rdt.len;
907 : :
908 : : /*
909 : : * Calculate CRC of the data
910 : : *
911 : : * Note that the record header isn't added into the CRC initially since we
912 : : * don't know the prev-link yet. Thus, the CRC will represent the CRC of
913 : : * the whole record in the order: rdata, then backup blocks, then record
914 : : * header.
915 : : */
916 : 13898696 : INIT_CRC32C(rdata_crc);
917 : 13898696 : COMP_CRC32C(rdata_crc, hdr_scratch + SizeOfXLogRecord, hdr_rdt.len - SizeOfXLogRecord);
918 [ + + ]: 52673412 : for (rdt = hdr_rdt.next; rdt != NULL; rdt = rdt->next)
919 : 38774716 : COMP_CRC32C(rdata_crc, rdt->data, rdt->len);
920 : :
921 : : /*
922 : : * Ensure that the XLogRecord is not too large.
923 : : *
924 : : * XLogReader machinery is only able to handle records up to a certain
925 : : * size (ignoring machine resource limitations), so make sure that we will
926 : : * not emit records larger than the sizes advertised to be supported.
927 : : */
807 noah@leadboat.com 928 [ - + ]: 13898696 : if (total_len > XLogRecordMaxSize)
934 michael@paquier.xyz 929 [ # # ]:UBC 0 : ereport(ERROR,
930 : : (errmsg_internal("oversized WAL record"),
931 : : errdetail_internal("WAL record would be %" PRIu64 " bytes (of maximum %u bytes); rmid %u flags %u.",
932 : : total_len, XLogRecordMaxSize, rmid, info)));
933 : :
934 : : /*
935 : : * Fill in the fields in the record header. Prev-link is filled in later,
936 : : * once we know where in the WAL the record will be inserted. The CRC does
937 : : * not include the record header yet.
938 : : */
4008 heikki.linnakangas@i 939 :CBC 13898696 : rechdr->xl_xid = GetCurrentTransactionIdIfAny();
934 michael@paquier.xyz 940 : 13898696 : rechdr->xl_tot_len = (uint32) total_len;
4008 heikki.linnakangas@i 941 : 13898696 : rechdr->xl_info = info;
942 : 13898696 : rechdr->xl_rmid = rmid;
943 : 13898696 : rechdr->xl_prev = InvalidXLogRecPtr;
3994 944 : 13898696 : rechdr->xl_crc = rdata_crc;
945 : :
4008 946 : 13898696 : return &hdr_rdt;
947 : : }
948 : :
949 : : /*
950 : : * Create a compressed version of a backup block image.
951 : : *
952 : : * Returns false if compression fails (i.e., compressed result is actually
953 : : * bigger than original). Otherwise, returns true and sets 'dlen' to
954 : : * the length of compressed block image.
955 : : */
956 : : static bool
280 peter@eisentraut.org 957 :UBC 0 : XLogCompressBackupBlock(const PageData *page, uint16 hole_offset, uint16 hole_length,
958 : : void *dest, uint16 *dlen)
959 : : {
3883 fujii@postgresql.org 960 : 0 : int32 orig_len = BLCKSZ - hole_length;
1581 michael@paquier.xyz 961 : 0 : int32 len = -1;
3883 fujii@postgresql.org 962 : 0 : int32 extra_bytes = 0;
963 : : const void *source;
964 : : PGAlignedBlock tmp;
965 : :
966 [ # # ]: 0 : if (hole_length != 0)
967 : : {
968 : : /* must skip the hole */
419 peter@eisentraut.org 969 : 0 : memcpy(tmp.data, page, hole_offset);
970 : 0 : memcpy(tmp.data + hole_offset,
3883 fujii@postgresql.org 971 : 0 : page + (hole_offset + hole_length),
972 : 0 : BLCKSZ - (hole_length + hole_offset));
419 peter@eisentraut.org 973 : 0 : source = tmp.data;
974 : :
975 : : /*
976 : : * Extra data needs to be stored in WAL record for the compressed
977 : : * version of block image if the hole exists.
978 : : */
3883 fujii@postgresql.org 979 : 0 : extra_bytes = SizeOfXLogRecordBlockCompressHeader;
980 : : }
981 : : else
982 : 0 : source = page;
983 : :
1581 michael@paquier.xyz 984 [ # # # # : 0 : switch ((WalCompression) wal_compression)
# ]
985 : : {
986 : 0 : case WAL_COMPRESSION_PGLZ:
987 : 0 : len = pglz_compress(source, orig_len, dest, PGLZ_strategy_default);
988 : 0 : break;
989 : :
990 : 0 : case WAL_COMPRESSION_LZ4:
991 : : #ifdef USE_LZ4
992 : 0 : len = LZ4_compress_default(source, dest, orig_len,
993 : : COMPRESS_BUFSIZE);
994 [ # # ]: 0 : if (len <= 0)
995 : 0 : len = -1; /* failure */
996 : : #else
997 : : elog(ERROR, "LZ4 is not supported by this build");
998 : : #endif
999 : 0 : break;
1000 : :
1326 1001 : 0 : case WAL_COMPRESSION_ZSTD:
1002 : : #ifdef USE_ZSTD
1003 : 0 : len = ZSTD_compress(dest, COMPRESS_BUFSIZE, source, orig_len,
1004 : : ZSTD_CLEVEL_DEFAULT);
1005 [ # # ]: 0 : if (ZSTD_isError(len))
1006 : 0 : len = -1; /* failure */
1007 : : #else
1008 : : elog(ERROR, "zstd is not supported by this build");
1009 : : #endif
1010 : 0 : break;
1011 : :
1581 1012 : 0 : case WAL_COMPRESSION_NONE:
1013 : 0 : Assert(false); /* cannot happen */
1014 : : break;
1015 : : /* no default case, so that compiler will warn */
1016 : : }
1017 : :
1018 : : /*
1019 : : * We recheck the actual size even if compression reports success and see
1020 : : * if the number of bytes saved by compression is larger than the length
1021 : : * of extra data needed for the compressed version of block image.
1022 : : */
3883 fujii@postgresql.org 1023 [ # # ]: 0 : if (len >= 0 &&
1024 [ # # ]: 0 : len + extra_bytes < orig_len)
1025 : : {
3810 bruce@momjian.us 1026 : 0 : *dlen = (uint16) len; /* successful compression */
3883 fujii@postgresql.org 1027 : 0 : return true;
1028 : : }
1029 : 0 : return false;
1030 : : }
1031 : :
1032 : : /*
1033 : : * Determine whether the buffer referenced has to be backed up.
1034 : : *
1035 : : * Since we don't yet have the insert lock, fullPageWrites and runningBackups
1036 : : * (which forces full-page writes) could change later, so the result should
1037 : : * be used for optimization purposes only.
1038 : : */
1039 : : bool
4008 heikki.linnakangas@i 1040 :CBC 143703 : XLogCheckBufferNeedsBackup(Buffer buffer)
1041 : : {
1042 : : XLogRecPtr RedoRecPtr;
1043 : : bool doPageWrites;
1044 : : Page page;
1045 : :
1046 : 143703 : GetFullPageWriteInfo(&RedoRecPtr, &doPageWrites);
1047 : :
3477 kgrittn@postgresql.o 1048 : 143703 : page = BufferGetPage(buffer);
1049 : :
4008 heikki.linnakangas@i 1050 [ + + + + ]: 143703 : if (doPageWrites && PageGetLSN(page) <= RedoRecPtr)
1051 : 1200 : return true; /* buffer requires backup */
1052 : :
1053 : 142503 : return false; /* buffer does not need to be backed up */
1054 : : }
1055 : :
1056 : : /*
1057 : : * Write a backup block if needed when we are setting a hint. Note that
1058 : : * this may be called for a variety of page types, not just heaps.
1059 : : *
1060 : : * Callable while holding just share lock on the buffer content.
1061 : : *
1062 : : * We can't use the plain backup block mechanism since that relies on the
1063 : : * Buffer being exclusively locked. Since some modifications (setting LSN, hint
1064 : : * bits) are allowed in a sharelocked buffer that can lead to wal checksum
1065 : : * failures. So instead we copy the page and insert the copied data as normal
1066 : : * record data.
1067 : : *
1068 : : * We only need to do something if page has not yet been full page written in
1069 : : * this checkpoint round. The LSN of the inserted wal record is returned if we
1070 : : * had to write, InvalidXLogRecPtr otherwise.
1071 : : *
1072 : : * It is possible that multiple concurrent backends could attempt to write WAL
1073 : : * records. In that case, multiple copies of the same block would be recorded
1074 : : * in separate WAL records by different backends, though that is still OK from
1075 : : * a correctness perspective.
1076 : : */
1077 : : XLogRecPtr
1078 : 59719 : XLogSaveBufferForHint(Buffer buffer, bool buffer_std)
1079 : : {
1080 : 59719 : XLogRecPtr recptr = InvalidXLogRecPtr;
1081 : : XLogRecPtr lsn;
1082 : : XLogRecPtr RedoRecPtr;
1083 : :
1084 : : /*
1085 : : * Ensure no checkpoint can change our view of RedoRecPtr.
1086 : : */
1298 rhaas@postgresql.org 1087 [ - + ]: 59719 : Assert((MyProc->delayChkptFlags & DELAY_CHKPT_START) != 0);
1088 : :
1089 : : /*
1090 : : * Update RedoRecPtr so that we can make the right decision
1091 : : */
4008 heikki.linnakangas@i 1092 : 59719 : RedoRecPtr = GetRedoRecPtr();
1093 : :
1094 : : /*
1095 : : * We assume page LSN is first data on *every* page that can be passed to
1096 : : * XLogInsert, whether it has the standard page layout or not. Since we're
1097 : : * only holding a share-lock on the page, we must take the buffer header
1098 : : * lock when we look at the LSN.
1099 : : */
1100 : 59719 : lsn = BufferGetLSNAtomic(buffer);
1101 : :
1102 [ + + ]: 59719 : if (lsn <= RedoRecPtr)
1103 : : {
1559 fujii@postgresql.org 1104 : 30409 : int flags = 0;
1105 : : PGAlignedBlock copied_buffer;
4008 heikki.linnakangas@i 1106 : 30409 : char *origdata = (char *) BufferGetBlock(buffer);
1107 : : RelFileLocator rlocator;
1108 : : ForkNumber forkno;
1109 : : BlockNumber blkno;
1110 : :
1111 : : /*
1112 : : * Copy buffer so we don't have to worry about concurrent hint bit or
1113 : : * lsn updates. We assume pd_lower/upper cannot be changed without an
1114 : : * exclusive lock, so the contents bkp are not racy.
1115 : : */
3994 1116 [ + + ]: 30409 : if (buffer_std)
1117 : : {
1118 : : /* Assume we can omit data between pd_lower and pd_upper */
3477 kgrittn@postgresql.o 1119 : 18966 : Page page = BufferGetPage(buffer);
3994 heikki.linnakangas@i 1120 : 18966 : uint16 lower = ((PageHeader) page)->pd_lower;
1121 : 18966 : uint16 upper = ((PageHeader) page)->pd_upper;
1122 : :
2613 tgl@sss.pgh.pa.us 1123 : 18966 : memcpy(copied_buffer.data, origdata, lower);
1124 : 18966 : memcpy(copied_buffer.data + upper, origdata + upper, BLCKSZ - upper);
1125 : : }
1126 : : else
1127 : 11443 : memcpy(copied_buffer.data, origdata, BLCKSZ);
1128 : :
3994 heikki.linnakangas@i 1129 : 30409 : XLogBeginInsert();
1130 : :
1131 [ + + ]: 30409 : if (buffer_std)
1132 : 18966 : flags |= REGBUF_STANDARD;
1133 : :
1209 rhaas@postgresql.org 1134 : 30409 : BufferGetTag(buffer, &rlocator, &forkno, &blkno);
1135 : 30409 : XLogRegisterBlock(0, &rlocator, forkno, blkno, copied_buffer.data, flags);
1136 : :
3990 heikki.linnakangas@i 1137 : 30409 : recptr = XLogInsert(RM_XLOG_ID, XLOG_FPI_FOR_HINT);
1138 : : }
1139 : :
4008 1140 : 59719 : return recptr;
1141 : : }
1142 : :
1143 : : /*
1144 : : * Write a WAL record containing a full image of a page. Caller is responsible
1145 : : * for writing the page to disk after calling this routine.
1146 : : *
1147 : : * Note: If you're using this function, you should be building pages in private
1148 : : * memory and writing them directly to smgr. If you're using buffers, call
1149 : : * log_newpage_buffer instead.
1150 : : *
1151 : : * If the page follows the standard page layout, with a PageHeader and unused
1152 : : * space between pd_lower and pd_upper, set 'page_std' to true. That allows
1153 : : * the unused space to be left out from the WAL record, making it smaller.
1154 : : */
1155 : : XLogRecPtr
1134 pg@bowt.ie 1156 : 126026 : log_newpage(RelFileLocator *rlocator, ForkNumber forknum, BlockNumber blkno,
1157 : : Page page, bool page_std)
1158 : : {
1159 : : int flags;
1160 : : XLogRecPtr recptr;
1161 : :
3994 heikki.linnakangas@i 1162 : 126026 : flags = REGBUF_FORCE_IMAGE;
4008 1163 [ + + ]: 126026 : if (page_std)
3994 1164 : 125803 : flags |= REGBUF_STANDARD;
1165 : :
1166 : 126026 : XLogBeginInsert();
1134 pg@bowt.ie 1167 : 126026 : XLogRegisterBlock(0, rlocator, forknum, blkno, page, flags);
3994 heikki.linnakangas@i 1168 : 126026 : recptr = XLogInsert(RM_XLOG_ID, XLOG_FPI);
1169 : :
1170 : : /*
1171 : : * The page may be uninitialized. If so, we can't set the LSN because that
1172 : : * would corrupt the page.
1173 : : */
4008 1174 [ + + ]: 126026 : if (!PageIsNew(page))
1175 : : {
1176 : 126022 : PageSetLSN(page, recptr);
1177 : : }
1178 : :
1179 : 126026 : return recptr;
1180 : : }
1181 : :
1182 : : /*
1183 : : * Like log_newpage(), but allows logging multiple pages in one operation.
1184 : : * It is more efficient than calling log_newpage() for each page separately,
1185 : : * because we can write multiple pages in a single WAL record.
1186 : : */
1187 : : void
1134 pg@bowt.ie 1188 : 19196 : log_newpages(RelFileLocator *rlocator, ForkNumber forknum, int num_pages,
1189 : : BlockNumber *blknos, Page *pages, bool page_std)
1190 : : {
1191 : : int flags;
1192 : : XLogRecPtr recptr;
1193 : : int i;
1194 : : int j;
1195 : :
1866 heikki.linnakangas@i 1196 : 19196 : flags = REGBUF_FORCE_IMAGE;
1197 [ + + ]: 19196 : if (page_std)
1198 : 19152 : flags |= REGBUF_STANDARD;
1199 : :
1200 : : /*
1201 : : * Iterate over all the pages. They are collected into batches of
1202 : : * XLR_MAX_BLOCK_ID pages, and a single WAL-record is written for each
1203 : : * batch.
1204 : : */
1205 : 19196 : XLogEnsureRecordSpace(XLR_MAX_BLOCK_ID - 1, 0);
1206 : :
1207 : 19196 : i = 0;
1208 [ + + ]: 38392 : while (i < num_pages)
1209 : : {
1210 : 19196 : int batch_start = i;
1211 : : int nbatch;
1212 : :
1213 : 19196 : XLogBeginInsert();
1214 : :
1215 : 19196 : nbatch = 0;
1216 [ + + + + ]: 56892 : while (nbatch < XLR_MAX_BLOCK_ID && i < num_pages)
1217 : : {
1134 pg@bowt.ie 1218 : 37696 : XLogRegisterBlock(nbatch, rlocator, forknum, blknos[i], pages[i], flags);
1866 heikki.linnakangas@i 1219 : 37696 : i++;
1220 : 37696 : nbatch++;
1221 : : }
1222 : :
1223 : 19196 : recptr = XLogInsert(RM_XLOG_ID, XLOG_FPI);
1224 : :
1225 [ + + ]: 56892 : for (j = batch_start; j < i; j++)
1226 : : {
1227 : : /*
1228 : : * The page may be uninitialized. If so, we can't set the LSN
1229 : : * because that would corrupt the page.
1230 : : */
1231 [ + + ]: 37696 : if (!PageIsNew(pages[j]))
1232 : : {
1233 : 37692 : PageSetLSN(pages[j], recptr);
1234 : : }
1235 : : }
1236 : : }
1237 : 19196 : }
1238 : :
1239 : : /*
1240 : : * Write a WAL record containing a full image of a page.
1241 : : *
1242 : : * Caller should initialize the buffer and mark it dirty before calling this
1243 : : * function. This function will set the page LSN.
1244 : : *
1245 : : * If the page follows the standard page layout, with a PageHeader and unused
1246 : : * space between pd_lower and pd_upper, set 'page_std' to true. That allows
1247 : : * the unused space to be left out from the WAL record, making it smaller.
1248 : : */
1249 : : XLogRecPtr
4008 1250 : 123334 : log_newpage_buffer(Buffer buffer, bool page_std)
1251 : : {
3477 kgrittn@postgresql.o 1252 : 123334 : Page page = BufferGetPage(buffer);
1253 : : RelFileLocator rlocator;
1254 : : ForkNumber forknum;
1255 : : BlockNumber blkno;
1256 : :
1257 : : /* Shared buffers should be modified in a critical section. */
4008 heikki.linnakangas@i 1258 [ - + ]: 123334 : Assert(CritSectionCount > 0);
1259 : :
1134 pg@bowt.ie 1260 : 123334 : BufferGetTag(buffer, &rlocator, &forknum, &blkno);
1261 : :
1262 : 123334 : return log_newpage(&rlocator, forknum, blkno, page, page_std);
1263 : : }
1264 : :
1265 : : /*
1266 : : * WAL-log a range of blocks in a relation.
1267 : : *
1268 : : * An image of all pages with block numbers 'startblk' <= X < 'endblk' is
1269 : : * written to the WAL. If the range is large, this is done in multiple WAL
1270 : : * records.
1271 : : *
1272 : : * If all page follows the standard page layout, with a PageHeader and unused
1273 : : * space between pd_lower and pd_upper, set 'page_std' to true. That allows
1274 : : * the unused space to be left out from the WAL records, making them smaller.
1275 : : *
1276 : : * NOTE: This function acquires exclusive-locks on the pages. Typically, this
1277 : : * is used on a newly-built relation, and the caller is holding a
1278 : : * AccessExclusiveLock on it, so no other backend can be accessing it at the
1279 : : * same time. If that's not the case, you must ensure that this does not
1280 : : * cause a deadlock through some other means.
1281 : : */
1282 : : void
1283 : 46987 : log_newpage_range(Relation rel, ForkNumber forknum,
1284 : : BlockNumber startblk, BlockNumber endblk,
1285 : : bool page_std)
1286 : : {
1287 : : int flags;
1288 : : BlockNumber blkno;
1289 : :
2046 noah@leadboat.com 1290 : 46987 : flags = REGBUF_FORCE_IMAGE;
1291 [ + + ]: 46987 : if (page_std)
1292 : 369 : flags |= REGBUF_STANDARD;
1293 : :
1294 : : /*
1295 : : * Iterate over all the pages in the range. They are collected into
1296 : : * batches of XLR_MAX_BLOCK_ID pages, and a single WAL-record is written
1297 : : * for each batch.
1298 : : */
2399 heikki.linnakangas@i 1299 : 46987 : XLogEnsureRecordSpace(XLR_MAX_BLOCK_ID - 1, 0);
1300 : :
1301 : 46987 : blkno = startblk;
1302 [ + + ]: 82756 : while (blkno < endblk)
1303 : : {
1304 : : Buffer bufpack[XLR_MAX_BLOCK_ID];
1305 : : XLogRecPtr recptr;
1306 : : int nbufs;
1307 : : int i;
1308 : :
1309 [ - + ]: 35769 : CHECK_FOR_INTERRUPTS();
1310 : :
1311 : : /* Collect a batch of blocks. */
1312 : 35769 : nbufs = 0;
1313 [ + + + + ]: 170599 : while (nbufs < XLR_MAX_BLOCK_ID && blkno < endblk)
1314 : : {
1134 pg@bowt.ie 1315 : 134830 : Buffer buf = ReadBufferExtended(rel, forknum, blkno,
1316 : : RBM_NORMAL, NULL);
1317 : :
2399 heikki.linnakangas@i 1318 : 134830 : LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
1319 : :
1320 : : /*
1321 : : * Completely empty pages are not WAL-logged. Writing a WAL record
1322 : : * would change the LSN, and we don't want that. We want the page
1323 : : * to stay empty.
1324 : : */
1325 [ + + ]: 134830 : if (!PageIsNew(BufferGetPage(buf)))
1326 : 134348 : bufpack[nbufs++] = buf;
1327 : : else
1328 : 482 : UnlockReleaseBuffer(buf);
1329 : 134830 : blkno++;
1330 : : }
1331 : :
1332 : : /* Nothing more to do if all remaining blocks were empty. */
924 tgl@sss.pgh.pa.us 1333 [ - + ]: 35769 : if (nbufs == 0)
924 tgl@sss.pgh.pa.us 1334 :UBC 0 : break;
1335 : :
1336 : : /* Write WAL record for this batch. */
2399 heikki.linnakangas@i 1337 :CBC 35769 : XLogBeginInsert();
1338 : :
1339 : 35769 : START_CRIT_SECTION();
1340 [ + + ]: 170117 : for (i = 0; i < nbufs; i++)
1341 : : {
1342 : 134348 : MarkBufferDirty(bufpack[i]);
735 jdavis@postgresql.or 1343 : 134348 : XLogRegisterBuffer(i, bufpack[i], flags);
1344 : : }
1345 : :
2399 heikki.linnakangas@i 1346 : 35769 : recptr = XLogInsert(RM_XLOG_ID, XLOG_FPI);
1347 : :
1348 [ + + ]: 170117 : for (i = 0; i < nbufs; i++)
1349 : : {
1350 : 134348 : PageSetLSN(BufferGetPage(bufpack[i]), recptr);
1351 : 134348 : UnlockReleaseBuffer(bufpack[i]);
1352 : : }
1353 [ - + ]: 35769 : END_CRIT_SECTION();
1354 : : }
1355 : 46987 : }
1356 : :
1357 : : /*
1358 : : * Allocate working buffers needed for WAL record construction.
1359 : : */
1360 : : void
3994 1361 : 18973 : InitXLogInsert(void)
1362 : : {
1363 : : #ifdef USE_ASSERT_CHECKING
1364 : :
1365 : : /*
1366 : : * Check that any records assembled can be decoded. This is capped based
1367 : : * on what XLogReader would require at its maximum bound. The XLOG_BLCKSZ
1368 : : * addend covers the larger allocate_recordbuf() demand. This code path
1369 : : * is called once per backend, more than enough for this check.
1370 : : */
1371 : : size_t max_required =
757 noah@leadboat.com 1372 : 18973 : DecodeXLogRecordRequiredSpace(XLogRecordMaxSize + XLOG_BLCKSZ);
1373 : :
934 michael@paquier.xyz 1374 [ - + ]: 18973 : Assert(AllocSizeIsValid(max_required));
1375 : : #endif
1376 : :
1377 : : /* Initialize the working areas */
3994 heikki.linnakangas@i 1378 [ + - ]: 18973 : if (xloginsert_cxt == NULL)
1379 : : {
1380 : 18973 : xloginsert_cxt = AllocSetContextCreate(TopMemoryContext,
1381 : : "WAL record construction",
1382 : : ALLOCSET_DEFAULT_SIZES);
1383 : : }
1384 : :
1385 [ + - ]: 18973 : if (registered_buffers == NULL)
1386 : : {
1387 : 18973 : registered_buffers = (registered_buffer *)
1388 : 18973 : MemoryContextAllocZero(xloginsert_cxt,
1389 : : sizeof(registered_buffer) * (XLR_NORMAL_MAX_BLOCK_ID + 1));
1390 : 18973 : max_registered_buffers = XLR_NORMAL_MAX_BLOCK_ID + 1;
1391 : : }
1392 [ + - ]: 18973 : if (rdatas == NULL)
1393 : : {
1394 : 18973 : rdatas = MemoryContextAlloc(xloginsert_cxt,
1395 : : sizeof(XLogRecData) * XLR_NORMAL_RDATAS);
1396 : 18973 : max_rdatas = XLR_NORMAL_RDATAS;
1397 : : }
1398 : :
1399 : : /*
1400 : : * Allocate a buffer to hold the header information for a WAL record.
1401 : : */
1402 [ + - ]: 18973 : if (hdr_scratch == NULL)
3986 1403 : 18973 : hdr_scratch = MemoryContextAllocZero(xloginsert_cxt,
1404 : : HEADER_SCRATCH_SIZE);
4008 1405 : 18973 : }
|