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