/src/postgres/src/backend/replication/logical/reorderbuffer.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * reorderbuffer.c |
4 | | * PostgreSQL logical replay/reorder buffer management |
5 | | * |
6 | | * |
7 | | * Copyright (c) 2012-2026, PostgreSQL Global Development Group |
8 | | * |
9 | | * |
10 | | * IDENTIFICATION |
11 | | * src/backend/replication/logical/reorderbuffer.c |
12 | | * |
13 | | * NOTES |
14 | | * This module gets handed individual pieces of transactions in the order |
15 | | * they are written to the WAL and is responsible to reassemble them into |
16 | | * toplevel transaction sized pieces. When a transaction is completely |
17 | | * reassembled - signaled by reading the transaction commit record - it |
18 | | * will then call the output plugin (cf. ReorderBufferCommit()) with the |
19 | | * individual changes. The output plugins rely on snapshots built by |
20 | | * snapbuild.c which hands them to us. |
21 | | * |
22 | | * Transactions and subtransactions/savepoints in postgres are not |
23 | | * immediately linked to each other from outside the performing |
24 | | * backend. Only at commit/abort (or special xact_assignment records) they |
25 | | * are linked together. Which means that we will have to splice together a |
26 | | * toplevel transaction from its subtransactions. To do that efficiently we |
27 | | * build a binary heap indexed by the smallest current lsn of the individual |
28 | | * subtransactions' changestreams. As the individual streams are inherently |
29 | | * ordered by LSN - since that is where we build them from - the transaction |
30 | | * can easily be reassembled by always using the subtransaction with the |
31 | | * smallest current LSN from the heap. |
32 | | * |
33 | | * In order to cope with large transactions - which can be several times as |
34 | | * big as the available memory - this module supports spooling the contents |
35 | | * of large transactions to disk. When the transaction is replayed the |
36 | | * contents of individual (sub-)transactions will be read from disk in |
37 | | * chunks. |
38 | | * |
39 | | * This module also has to deal with reassembling toast records from the |
40 | | * individual chunks stored in WAL. When a new (or initial) version of a |
41 | | * tuple is stored in WAL it will always be preceded by the toast chunks |
42 | | * emitted for the columns stored out of line. Within a single toplevel |
43 | | * transaction there will be no other data carrying records between a row's |
44 | | * toast chunks and the row data itself. See ReorderBufferToast* for |
45 | | * details. |
46 | | * |
47 | | * ReorderBuffer uses two special memory context types - SlabContext for |
48 | | * allocations of fixed-length structures (changes and transactions), and |
49 | | * GenerationContext for the variable-length transaction data (allocated |
50 | | * and freed in groups with similar lifespans). |
51 | | * |
52 | | * To limit the amount of memory used by decoded changes, we track memory |
53 | | * used at the reorder buffer level (i.e. total amount of memory), and for |
54 | | * each transaction. When the total amount of used memory exceeds the |
55 | | * limit, the transaction consuming the most memory is then serialized to |
56 | | * disk. |
57 | | * |
58 | | * Only decoded changes are evicted from memory (spilled to disk), not the |
59 | | * transaction records. The number of toplevel transactions is limited, |
60 | | * but a transaction with many subtransactions may still consume significant |
61 | | * amounts of memory. However, the transaction records are fairly small and |
62 | | * are not included in the memory limit. |
63 | | * |
64 | | * The current eviction algorithm is very simple - the transaction is |
65 | | * picked merely by size, while it might be useful to also consider age |
66 | | * (LSN) of the changes for example. With the new Generational memory |
67 | | * allocator, evicting the oldest changes would make it more likely the |
68 | | * memory gets actually freed. |
69 | | * |
70 | | * We use a max-heap with transaction size as the key to efficiently find |
71 | | * the largest transaction. We update the max-heap whenever the memory |
72 | | * counter is updated; however transactions with size 0 are not stored in |
73 | | * the heap, because they have no changes to evict. |
74 | | * |
75 | | * We still rely on max_changes_in_memory when loading serialized changes |
76 | | * back into memory. At that point we can't use the memory limit directly |
77 | | * as we load the subxacts independently. One option to deal with this |
78 | | * would be to count the subxacts, and allow each to allocate 1/N of the |
79 | | * memory limit. That however does not seem very appealing, because with |
80 | | * many subtransactions it may easily cause thrashing (short cycles of |
81 | | * deserializing and applying very few changes). We probably should give |
82 | | * a bit more memory to the oldest subtransactions, because it's likely |
83 | | * they are the source for the next sequence of changes. |
84 | | * |
85 | | * ------------------------------------------------------------------------- |
86 | | */ |
87 | | #include "postgres.h" |
88 | | |
89 | | #include <unistd.h> |
90 | | #include <sys/stat.h> |
91 | | |
92 | | #include "access/detoast.h" |
93 | | #include "access/heapam.h" |
94 | | #include "access/rewriteheap.h" |
95 | | #include "access/transam.h" |
96 | | #include "access/xact.h" |
97 | | #include "access/xlog_internal.h" |
98 | | #include "catalog/catalog.h" |
99 | | #include "common/int.h" |
100 | | #include "lib/binaryheap.h" |
101 | | #include "miscadmin.h" |
102 | | #include "pgstat.h" |
103 | | #include "replication/logical.h" |
104 | | #include "replication/reorderbuffer.h" |
105 | | #include "replication/slot.h" |
106 | | #include "replication/snapbuild.h" /* just for SnapBuildSnapDecRefcount */ |
107 | | #include "storage/bufmgr.h" |
108 | | #include "storage/fd.h" |
109 | | #include "storage/procarray.h" |
110 | | #include "storage/sinval.h" |
111 | | #include "utils/builtins.h" |
112 | | #include "utils/inval.h" |
113 | | #include "utils/memutils.h" |
114 | | #include "utils/rel.h" |
115 | | #include "utils/relfilenumbermap.h" |
116 | | #include "utils/wait_event.h" |
117 | | |
118 | | /* |
119 | | * Each transaction has an 8MB limit for invalidation messages distributed from |
120 | | * other transactions. This limit is set considering scenarios with many |
121 | | * concurrent logical decoding operations. When the distributed invalidation |
122 | | * messages reach this threshold, the transaction is marked as |
123 | | * RBTXN_DISTR_INVAL_OVERFLOWED to invalidate the complete cache as we have lost |
124 | | * some inval messages and hence don't know what needs to be invalidated. |
125 | | */ |
126 | | #define MAX_DISTR_INVAL_MSG_PER_TXN \ |
127 | 0 | ((8 * 1024 * 1024) / sizeof(SharedInvalidationMessage)) |
128 | | |
129 | | /* entry for a hash table we use to map from xid to our transaction state */ |
130 | | typedef struct ReorderBufferTXNByIdEnt |
131 | | { |
132 | | TransactionId xid; |
133 | | ReorderBufferTXN *txn; |
134 | | } ReorderBufferTXNByIdEnt; |
135 | | |
136 | | /* data structures for (relfilelocator, ctid) => (cmin, cmax) mapping */ |
137 | | typedef struct ReorderBufferTupleCidKey |
138 | | { |
139 | | RelFileLocator rlocator; |
140 | | ItemPointerData tid; |
141 | | } ReorderBufferTupleCidKey; |
142 | | |
143 | | typedef struct ReorderBufferTupleCidEnt |
144 | | { |
145 | | ReorderBufferTupleCidKey key; |
146 | | CommandId cmin; |
147 | | CommandId cmax; |
148 | | CommandId combocid; /* just for debugging */ |
149 | | } ReorderBufferTupleCidEnt; |
150 | | |
151 | | /* Virtual file descriptor with file offset tracking */ |
152 | | typedef struct TXNEntryFile |
153 | | { |
154 | | File vfd; /* -1 when the file is closed */ |
155 | | off_t curOffset; /* offset for next write or read. Reset to 0 |
156 | | * when vfd is opened. */ |
157 | | } TXNEntryFile; |
158 | | |
159 | | /* k-way in-order change iteration support structures */ |
160 | | typedef struct ReorderBufferIterTXNEntry |
161 | | { |
162 | | XLogRecPtr lsn; |
163 | | ReorderBufferChange *change; |
164 | | ReorderBufferTXN *txn; |
165 | | TXNEntryFile file; |
166 | | XLogSegNo segno; |
167 | | } ReorderBufferIterTXNEntry; |
168 | | |
169 | | typedef struct ReorderBufferIterTXNState |
170 | | { |
171 | | binaryheap *heap; |
172 | | Size nr_txns; |
173 | | dlist_head old_change; |
174 | | ReorderBufferIterTXNEntry entries[FLEXIBLE_ARRAY_MEMBER]; |
175 | | } ReorderBufferIterTXNState; |
176 | | |
177 | | /* toast datastructures */ |
178 | | typedef struct ReorderBufferToastEnt |
179 | | { |
180 | | Oid chunk_id; /* toast_table.chunk_id */ |
181 | | int32 last_chunk_seq; /* toast_table.chunk_seq of the last chunk we |
182 | | * have seen */ |
183 | | Size num_chunks; /* number of chunks we've already seen */ |
184 | | Size size; /* combined size of chunks seen */ |
185 | | dlist_head chunks; /* linked list of chunks */ |
186 | | varlena *reconstructed; /* reconstructed varlena now pointed to in |
187 | | * main tup */ |
188 | | } ReorderBufferToastEnt; |
189 | | |
190 | | /* Disk serialization support datastructures */ |
191 | | typedef struct ReorderBufferDiskChange |
192 | | { |
193 | | Size size; |
194 | | ReorderBufferChange change; |
195 | | /* data follows */ |
196 | | } ReorderBufferDiskChange; |
197 | | |
198 | 0 | #define IsSpecInsert(action) \ |
199 | 0 | ( \ |
200 | 0 | ((action) == REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT) \ |
201 | 0 | ) |
202 | 0 | #define IsSpecConfirmOrAbort(action) \ |
203 | 0 | ( \ |
204 | 0 | (((action) == REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM) || \ |
205 | 0 | ((action) == REORDER_BUFFER_CHANGE_INTERNAL_SPEC_ABORT)) \ |
206 | 0 | ) |
207 | 0 | #define IsInsertOrUpdate(action) \ |
208 | 0 | ( \ |
209 | 0 | (((action) == REORDER_BUFFER_CHANGE_INSERT) || \ |
210 | 0 | ((action) == REORDER_BUFFER_CHANGE_UPDATE) || \ |
211 | 0 | ((action) == REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT)) \ |
212 | 0 | ) |
213 | | |
214 | | /* |
215 | | * Maximum number of changes kept in memory, per transaction. After that, |
216 | | * changes are spooled to disk. |
217 | | * |
218 | | * The current value should be sufficient to decode the entire transaction |
219 | | * without hitting disk in OLTP workloads, while starting to spool to disk in |
220 | | * other workloads reasonably fast. |
221 | | * |
222 | | * At some point in the future it probably makes sense to have a more elaborate |
223 | | * resource management here, but it's not entirely clear what that would look |
224 | | * like. |
225 | | */ |
226 | | int logical_decoding_work_mem; |
227 | | static const Size max_changes_in_memory = 4096; /* XXX for restore only */ |
228 | | |
229 | | /* GUC variable */ |
230 | | int debug_logical_replication_streaming = DEBUG_LOGICAL_REP_STREAMING_BUFFERED; |
231 | | |
232 | | /* --------------------------------------- |
233 | | * primary reorderbuffer support routines |
234 | | * --------------------------------------- |
235 | | */ |
236 | | static ReorderBufferTXN *ReorderBufferAllocTXN(ReorderBuffer *rb); |
237 | | static void ReorderBufferFreeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn); |
238 | | static ReorderBufferTXN *ReorderBufferTXNByXid(ReorderBuffer *rb, |
239 | | TransactionId xid, bool create, bool *is_new, |
240 | | XLogRecPtr lsn, bool create_as_top); |
241 | | static void ReorderBufferTransferSnapToParent(ReorderBufferTXN *txn, |
242 | | ReorderBufferTXN *subtxn); |
243 | | |
244 | | static void AssertTXNLsnOrder(ReorderBuffer *rb); |
245 | | |
246 | | /* --------------------------------------- |
247 | | * support functions for lsn-order iterating over the ->changes of a |
248 | | * transaction and its subtransactions |
249 | | * |
250 | | * used for iteration over the k-way heap merge of a transaction and its |
251 | | * subtransactions |
252 | | * --------------------------------------- |
253 | | */ |
254 | | static void ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn, |
255 | | ReorderBufferIterTXNState *volatile *iter_state); |
256 | | static ReorderBufferChange *ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state); |
257 | | static void ReorderBufferIterTXNFinish(ReorderBuffer *rb, |
258 | | ReorderBufferIterTXNState *state); |
259 | | static void ReorderBufferExecuteInvalidations(uint32 nmsgs, SharedInvalidationMessage *msgs); |
260 | | |
261 | | /* |
262 | | * --------------------------------------- |
263 | | * Disk serialization support functions |
264 | | * --------------------------------------- |
265 | | */ |
266 | | static void ReorderBufferCheckMemoryLimit(ReorderBuffer *rb); |
267 | | static void ReorderBufferSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn); |
268 | | static void ReorderBufferSerializeChange(ReorderBuffer *rb, ReorderBufferTXN *txn, |
269 | | int fd, ReorderBufferChange *change); |
270 | | static Size ReorderBufferRestoreChanges(ReorderBuffer *rb, ReorderBufferTXN *txn, |
271 | | TXNEntryFile *file, XLogSegNo *segno); |
272 | | static void ReorderBufferRestoreChange(ReorderBuffer *rb, ReorderBufferTXN *txn, |
273 | | char *data); |
274 | | static void ReorderBufferRestoreCleanup(ReorderBuffer *rb, ReorderBufferTXN *txn); |
275 | | static void ReorderBufferTruncateTXN(ReorderBuffer *rb, ReorderBufferTXN *txn, |
276 | | bool txn_prepared); |
277 | | static void ReorderBufferMaybeMarkTXNStreamed(ReorderBuffer *rb, ReorderBufferTXN *txn); |
278 | | static bool ReorderBufferCheckAndTruncateAbortedTXN(ReorderBuffer *rb, ReorderBufferTXN *txn); |
279 | | static void ReorderBufferCleanupSerializedTXNs(const char *slotname); |
280 | | static void ReorderBufferSerializedPath(char *path, ReplicationSlot *slot, |
281 | | TransactionId xid, XLogSegNo segno); |
282 | | static int ReorderBufferTXNSizeCompare(const pairingheap_node *a, const pairingheap_node *b, void *arg); |
283 | | |
284 | | static void ReorderBufferFreeSnap(ReorderBuffer *rb, Snapshot snap); |
285 | | static Snapshot ReorderBufferCopySnap(ReorderBuffer *rb, Snapshot orig_snap, |
286 | | ReorderBufferTXN *txn, CommandId cid); |
287 | | |
288 | | /* |
289 | | * --------------------------------------- |
290 | | * Streaming support functions |
291 | | * --------------------------------------- |
292 | | */ |
293 | | static inline bool ReorderBufferCanStream(ReorderBuffer *rb); |
294 | | static inline bool ReorderBufferCanStartStreaming(ReorderBuffer *rb); |
295 | | static void ReorderBufferStreamTXN(ReorderBuffer *rb, ReorderBufferTXN *txn); |
296 | | static void ReorderBufferStreamCommit(ReorderBuffer *rb, ReorderBufferTXN *txn); |
297 | | |
298 | | /* --------------------------------------- |
299 | | * toast reassembly support |
300 | | * --------------------------------------- |
301 | | */ |
302 | | static void ReorderBufferToastInitHash(ReorderBuffer *rb, ReorderBufferTXN *txn); |
303 | | static void ReorderBufferToastReset(ReorderBuffer *rb, ReorderBufferTXN *txn); |
304 | | static void ReorderBufferToastReplace(ReorderBuffer *rb, ReorderBufferTXN *txn, |
305 | | Relation relation, ReorderBufferChange *change); |
306 | | static void ReorderBufferToastAppendChunk(ReorderBuffer *rb, ReorderBufferTXN *txn, |
307 | | Relation relation, ReorderBufferChange *change); |
308 | | |
309 | | /* |
310 | | * --------------------------------------- |
311 | | * memory accounting |
312 | | * --------------------------------------- |
313 | | */ |
314 | | static Size ReorderBufferChangeSize(ReorderBufferChange *change); |
315 | | static void ReorderBufferChangeMemoryUpdate(ReorderBuffer *rb, |
316 | | ReorderBufferChange *change, |
317 | | ReorderBufferTXN *txn, |
318 | | bool addition, Size sz); |
319 | | |
320 | | /* |
321 | | * Allocate a new ReorderBuffer and clean out any old serialized state from |
322 | | * prior ReorderBuffer instances for the same slot. |
323 | | */ |
324 | | ReorderBuffer * |
325 | | ReorderBufferAllocate(void) |
326 | 0 | { |
327 | 0 | ReorderBuffer *buffer; |
328 | 0 | HASHCTL hash_ctl; |
329 | 0 | MemoryContext new_ctx; |
330 | |
|
331 | 0 | Assert(MyReplicationSlot != NULL); |
332 | | |
333 | | /* allocate memory in own context, to have better accountability */ |
334 | 0 | new_ctx = AllocSetContextCreate(CurrentMemoryContext, |
335 | 0 | "ReorderBuffer", |
336 | 0 | ALLOCSET_DEFAULT_SIZES); |
337 | |
|
338 | 0 | buffer = |
339 | 0 | (ReorderBuffer *) MemoryContextAlloc(new_ctx, sizeof(ReorderBuffer)); |
340 | |
|
341 | 0 | memset(&hash_ctl, 0, sizeof(hash_ctl)); |
342 | |
|
343 | 0 | buffer->context = new_ctx; |
344 | |
|
345 | 0 | buffer->change_context = SlabContextCreate(new_ctx, |
346 | 0 | "Change", |
347 | 0 | SLAB_DEFAULT_BLOCK_SIZE, |
348 | 0 | sizeof(ReorderBufferChange)); |
349 | |
|
350 | 0 | buffer->txn_context = SlabContextCreate(new_ctx, |
351 | 0 | "TXN", |
352 | 0 | SLAB_DEFAULT_BLOCK_SIZE, |
353 | 0 | sizeof(ReorderBufferTXN)); |
354 | | |
355 | | /* |
356 | | * To minimize memory fragmentation caused by long-running transactions |
357 | | * with changes spanning multiple memory blocks, we use a single |
358 | | * fixed-size memory block for decoded tuple storage. The performance |
359 | | * testing showed that the default memory block size maintains logical |
360 | | * decoding performance without causing fragmentation due to concurrent |
361 | | * transactions. One might think that we can use the max size as |
362 | | * SLAB_LARGE_BLOCK_SIZE but the test also showed it doesn't help resolve |
363 | | * the memory fragmentation. |
364 | | */ |
365 | 0 | buffer->tup_context = GenerationContextCreate(new_ctx, |
366 | 0 | "Tuples", |
367 | 0 | SLAB_DEFAULT_BLOCK_SIZE, |
368 | 0 | SLAB_DEFAULT_BLOCK_SIZE, |
369 | 0 | SLAB_DEFAULT_BLOCK_SIZE); |
370 | |
|
371 | 0 | hash_ctl.keysize = sizeof(TransactionId); |
372 | 0 | hash_ctl.entrysize = sizeof(ReorderBufferTXNByIdEnt); |
373 | 0 | hash_ctl.hcxt = buffer->context; |
374 | |
|
375 | 0 | buffer->by_txn = hash_create("ReorderBufferByXid", 1000, &hash_ctl, |
376 | 0 | HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); |
377 | |
|
378 | 0 | buffer->by_txn_last_xid = InvalidTransactionId; |
379 | 0 | buffer->by_txn_last_txn = NULL; |
380 | |
|
381 | 0 | buffer->outbuf = NULL; |
382 | 0 | buffer->outbufsize = 0; |
383 | 0 | buffer->size = 0; |
384 | | |
385 | | /* txn_heap is ordered by transaction size */ |
386 | 0 | buffer->txn_heap = pairingheap_allocate(ReorderBufferTXNSizeCompare, NULL); |
387 | |
|
388 | 0 | buffer->spillTxns = 0; |
389 | 0 | buffer->spillCount = 0; |
390 | 0 | buffer->spillBytes = 0; |
391 | 0 | buffer->streamTxns = 0; |
392 | 0 | buffer->streamCount = 0; |
393 | 0 | buffer->streamBytes = 0; |
394 | 0 | buffer->memExceededCount = 0; |
395 | 0 | buffer->totalTxns = 0; |
396 | 0 | buffer->totalBytes = 0; |
397 | |
|
398 | 0 | buffer->current_restart_decoding_lsn = InvalidXLogRecPtr; |
399 | |
|
400 | 0 | dlist_init(&buffer->toplevel_by_lsn); |
401 | 0 | dlist_init(&buffer->txns_by_base_snapshot_lsn); |
402 | 0 | dclist_init(&buffer->catchange_txns); |
403 | | |
404 | | /* |
405 | | * Ensure there's no stale data from prior uses of this slot, in case some |
406 | | * prior exit avoided calling ReorderBufferFree. Failure to do this can |
407 | | * produce duplicated txns, and it's very cheap if there's nothing there. |
408 | | */ |
409 | 0 | ReorderBufferCleanupSerializedTXNs(NameStr(MyReplicationSlot->data.name)); |
410 | |
|
411 | 0 | return buffer; |
412 | 0 | } |
413 | | |
414 | | /* |
415 | | * Free a ReorderBuffer |
416 | | */ |
417 | | void |
418 | | ReorderBufferFree(ReorderBuffer *rb) |
419 | 0 | { |
420 | 0 | MemoryContext context = rb->context; |
421 | | |
422 | | /* |
423 | | * We free separately allocated data by entirely scrapping reorderbuffer's |
424 | | * memory context. |
425 | | */ |
426 | 0 | MemoryContextDelete(context); |
427 | | |
428 | | /* Free disk space used by unconsumed reorder buffers */ |
429 | 0 | ReorderBufferCleanupSerializedTXNs(NameStr(MyReplicationSlot->data.name)); |
430 | 0 | } |
431 | | |
432 | | /* |
433 | | * Allocate a new ReorderBufferTXN. |
434 | | */ |
435 | | static ReorderBufferTXN * |
436 | | ReorderBufferAllocTXN(ReorderBuffer *rb) |
437 | 0 | { |
438 | 0 | ReorderBufferTXN *txn; |
439 | |
|
440 | 0 | txn = (ReorderBufferTXN *) |
441 | 0 | MemoryContextAlloc(rb->txn_context, sizeof(ReorderBufferTXN)); |
442 | |
|
443 | 0 | memset(txn, 0, sizeof(ReorderBufferTXN)); |
444 | |
|
445 | 0 | dlist_init(&txn->changes); |
446 | 0 | dlist_init(&txn->tuplecids); |
447 | 0 | dlist_init(&txn->subtxns); |
448 | | |
449 | | /* InvalidCommandId is not zero, so set it explicitly */ |
450 | 0 | txn->command_id = InvalidCommandId; |
451 | 0 | txn->output_plugin_private = NULL; |
452 | |
|
453 | 0 | return txn; |
454 | 0 | } |
455 | | |
456 | | /* |
457 | | * Free a ReorderBufferTXN. |
458 | | */ |
459 | | static void |
460 | | ReorderBufferFreeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn) |
461 | 0 | { |
462 | | /* clean the lookup cache if we were cached (quite likely) */ |
463 | 0 | if (rb->by_txn_last_xid == txn->xid) |
464 | 0 | { |
465 | 0 | rb->by_txn_last_xid = InvalidTransactionId; |
466 | 0 | rb->by_txn_last_txn = NULL; |
467 | 0 | } |
468 | | |
469 | | /* free data that's contained */ |
470 | |
|
471 | 0 | if (txn->gid != NULL) |
472 | 0 | { |
473 | 0 | pfree(txn->gid); |
474 | 0 | txn->gid = NULL; |
475 | 0 | } |
476 | |
|
477 | 0 | if (txn->tuplecid_hash != NULL) |
478 | 0 | { |
479 | 0 | hash_destroy(txn->tuplecid_hash); |
480 | 0 | txn->tuplecid_hash = NULL; |
481 | 0 | } |
482 | |
|
483 | 0 | if (txn->invalidations) |
484 | 0 | { |
485 | 0 | pfree(txn->invalidations); |
486 | 0 | txn->invalidations = NULL; |
487 | 0 | } |
488 | |
|
489 | 0 | if (txn->invalidations_distributed) |
490 | 0 | { |
491 | 0 | pfree(txn->invalidations_distributed); |
492 | 0 | txn->invalidations_distributed = NULL; |
493 | 0 | } |
494 | | |
495 | | /* Reset the toast hash */ |
496 | 0 | ReorderBufferToastReset(rb, txn); |
497 | | |
498 | | /* All changes must be deallocated */ |
499 | 0 | Assert(txn->size == 0); |
500 | |
|
501 | 0 | pfree(txn); |
502 | 0 | } |
503 | | |
504 | | /* |
505 | | * Allocate a ReorderBufferChange. |
506 | | */ |
507 | | ReorderBufferChange * |
508 | | ReorderBufferAllocChange(ReorderBuffer *rb) |
509 | 0 | { |
510 | 0 | ReorderBufferChange *change; |
511 | |
|
512 | 0 | change = (ReorderBufferChange *) |
513 | 0 | MemoryContextAlloc(rb->change_context, sizeof(ReorderBufferChange)); |
514 | |
|
515 | 0 | memset(change, 0, sizeof(ReorderBufferChange)); |
516 | 0 | return change; |
517 | 0 | } |
518 | | |
519 | | /* |
520 | | * Free a ReorderBufferChange and update memory accounting, if requested. |
521 | | */ |
522 | | void |
523 | | ReorderBufferFreeChange(ReorderBuffer *rb, ReorderBufferChange *change, |
524 | | bool upd_mem) |
525 | 0 | { |
526 | | /* update memory accounting info */ |
527 | 0 | if (upd_mem) |
528 | 0 | ReorderBufferChangeMemoryUpdate(rb, change, NULL, false, |
529 | 0 | ReorderBufferChangeSize(change)); |
530 | | |
531 | | /* free contained data */ |
532 | 0 | switch (change->action) |
533 | 0 | { |
534 | 0 | case REORDER_BUFFER_CHANGE_INSERT: |
535 | 0 | case REORDER_BUFFER_CHANGE_UPDATE: |
536 | 0 | case REORDER_BUFFER_CHANGE_DELETE: |
537 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT: |
538 | 0 | if (change->data.tp.newtuple) |
539 | 0 | { |
540 | 0 | ReorderBufferFreeTupleBuf(change->data.tp.newtuple); |
541 | 0 | change->data.tp.newtuple = NULL; |
542 | 0 | } |
543 | |
|
544 | 0 | if (change->data.tp.oldtuple) |
545 | 0 | { |
546 | 0 | ReorderBufferFreeTupleBuf(change->data.tp.oldtuple); |
547 | 0 | change->data.tp.oldtuple = NULL; |
548 | 0 | } |
549 | 0 | break; |
550 | 0 | case REORDER_BUFFER_CHANGE_MESSAGE: |
551 | 0 | if (change->data.msg.prefix != NULL) |
552 | 0 | pfree(change->data.msg.prefix); |
553 | 0 | change->data.msg.prefix = NULL; |
554 | 0 | if (change->data.msg.message != NULL) |
555 | 0 | pfree(change->data.msg.message); |
556 | 0 | change->data.msg.message = NULL; |
557 | 0 | break; |
558 | 0 | case REORDER_BUFFER_CHANGE_INVALIDATION: |
559 | 0 | if (change->data.inval.invalidations) |
560 | 0 | pfree(change->data.inval.invalidations); |
561 | 0 | change->data.inval.invalidations = NULL; |
562 | 0 | break; |
563 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT: |
564 | 0 | if (change->data.snapshot) |
565 | 0 | { |
566 | 0 | ReorderBufferFreeSnap(rb, change->data.snapshot); |
567 | 0 | change->data.snapshot = NULL; |
568 | 0 | } |
569 | 0 | break; |
570 | | /* no data in addition to the struct itself */ |
571 | 0 | case REORDER_BUFFER_CHANGE_TRUNCATE: |
572 | 0 | if (change->data.truncate.relids != NULL) |
573 | 0 | { |
574 | 0 | ReorderBufferFreeRelids(rb, change->data.truncate.relids); |
575 | 0 | change->data.truncate.relids = NULL; |
576 | 0 | } |
577 | 0 | break; |
578 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM: |
579 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_ABORT: |
580 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID: |
581 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID: |
582 | 0 | break; |
583 | 0 | } |
584 | | |
585 | 0 | pfree(change); |
586 | 0 | } |
587 | | |
588 | | /* |
589 | | * Allocate a HeapTuple fitting a tuple of size tuple_len (excluding header |
590 | | * overhead). |
591 | | */ |
592 | | HeapTuple |
593 | | ReorderBufferAllocTupleBuf(ReorderBuffer *rb, Size tuple_len) |
594 | 0 | { |
595 | 0 | HeapTuple tuple; |
596 | 0 | Size alloc_len; |
597 | |
|
598 | 0 | alloc_len = tuple_len + SizeofHeapTupleHeader; |
599 | |
|
600 | 0 | tuple = (HeapTuple) MemoryContextAlloc(rb->tup_context, |
601 | 0 | HEAPTUPLESIZE + alloc_len); |
602 | 0 | tuple->t_data = (HeapTupleHeader) ((char *) tuple + HEAPTUPLESIZE); |
603 | |
|
604 | 0 | return tuple; |
605 | 0 | } |
606 | | |
607 | | /* |
608 | | * Free a HeapTuple returned by ReorderBufferAllocTupleBuf(). |
609 | | */ |
610 | | void |
611 | | ReorderBufferFreeTupleBuf(HeapTuple tuple) |
612 | 0 | { |
613 | 0 | pfree(tuple); |
614 | 0 | } |
615 | | |
616 | | /* |
617 | | * Allocate an array for relids of truncated relations. |
618 | | * |
619 | | * We use the global memory context (for the whole reorder buffer), because |
620 | | * none of the existing ones seems like a good match (some are SLAB, so we |
621 | | * can't use those, and tup_context is meant for tuple data, not relids). We |
622 | | * could add yet another context, but it seems like an overkill - TRUNCATE is |
623 | | * not particularly common operation, so it does not seem worth it. |
624 | | */ |
625 | | Oid * |
626 | | ReorderBufferAllocRelids(ReorderBuffer *rb, int nrelids) |
627 | 0 | { |
628 | 0 | Oid *relids; |
629 | 0 | Size alloc_len; |
630 | |
|
631 | 0 | alloc_len = sizeof(Oid) * nrelids; |
632 | |
|
633 | 0 | relids = (Oid *) MemoryContextAlloc(rb->context, alloc_len); |
634 | |
|
635 | 0 | return relids; |
636 | 0 | } |
637 | | |
638 | | /* |
639 | | * Free an array of relids. |
640 | | */ |
641 | | void |
642 | | ReorderBufferFreeRelids(ReorderBuffer *rb, Oid *relids) |
643 | 0 | { |
644 | 0 | pfree(relids); |
645 | 0 | } |
646 | | |
647 | | /* |
648 | | * Return the ReorderBufferTXN from the given buffer, specified by Xid. |
649 | | * If create is true, and a transaction doesn't already exist, create it |
650 | | * (with the given LSN, and as top transaction if that's specified); |
651 | | * when this happens, is_new is set to true. |
652 | | */ |
653 | | static ReorderBufferTXN * |
654 | | ReorderBufferTXNByXid(ReorderBuffer *rb, TransactionId xid, bool create, |
655 | | bool *is_new, XLogRecPtr lsn, bool create_as_top) |
656 | 0 | { |
657 | 0 | ReorderBufferTXN *txn; |
658 | 0 | ReorderBufferTXNByIdEnt *ent; |
659 | 0 | bool found; |
660 | |
|
661 | 0 | Assert(TransactionIdIsValid(xid)); |
662 | | |
663 | | /* |
664 | | * Check the one-entry lookup cache first |
665 | | */ |
666 | 0 | if (TransactionIdIsValid(rb->by_txn_last_xid) && |
667 | 0 | rb->by_txn_last_xid == xid) |
668 | 0 | { |
669 | 0 | txn = rb->by_txn_last_txn; |
670 | |
|
671 | 0 | if (txn != NULL) |
672 | 0 | { |
673 | | /* found it, and it's valid */ |
674 | 0 | if (is_new) |
675 | 0 | *is_new = false; |
676 | 0 | return txn; |
677 | 0 | } |
678 | | |
679 | | /* |
680 | | * cached as non-existent, and asked not to create? Then nothing else |
681 | | * to do. |
682 | | */ |
683 | 0 | if (!create) |
684 | 0 | return NULL; |
685 | | /* otherwise fall through to create it */ |
686 | 0 | } |
687 | | |
688 | | /* |
689 | | * If the cache wasn't hit or it yielded a "does-not-exist" and we want to |
690 | | * create an entry. |
691 | | */ |
692 | | |
693 | | /* search the lookup table */ |
694 | 0 | ent = (ReorderBufferTXNByIdEnt *) |
695 | 0 | hash_search(rb->by_txn, |
696 | 0 | &xid, |
697 | 0 | create ? HASH_ENTER : HASH_FIND, |
698 | 0 | &found); |
699 | 0 | if (found) |
700 | 0 | txn = ent->txn; |
701 | 0 | else if (create) |
702 | 0 | { |
703 | | /* initialize the new entry, if creation was requested */ |
704 | 0 | Assert(ent != NULL); |
705 | 0 | Assert(XLogRecPtrIsValid(lsn)); |
706 | |
|
707 | 0 | ent->txn = ReorderBufferAllocTXN(rb); |
708 | 0 | ent->txn->xid = xid; |
709 | 0 | txn = ent->txn; |
710 | 0 | txn->first_lsn = lsn; |
711 | 0 | txn->restart_decoding_lsn = rb->current_restart_decoding_lsn; |
712 | |
|
713 | 0 | if (create_as_top) |
714 | 0 | { |
715 | 0 | dlist_push_tail(&rb->toplevel_by_lsn, &txn->node); |
716 | 0 | AssertTXNLsnOrder(rb); |
717 | 0 | } |
718 | 0 | } |
719 | 0 | else |
720 | 0 | txn = NULL; /* not found and not asked to create */ |
721 | | |
722 | | /* update cache */ |
723 | 0 | rb->by_txn_last_xid = xid; |
724 | 0 | rb->by_txn_last_txn = txn; |
725 | |
|
726 | 0 | if (is_new) |
727 | 0 | *is_new = !found; |
728 | |
|
729 | 0 | Assert(!create || txn != NULL); |
730 | 0 | return txn; |
731 | 0 | } |
732 | | |
733 | | /* |
734 | | * Record the partial change for the streaming of in-progress transactions. We |
735 | | * can stream only complete changes so if we have a partial change like toast |
736 | | * table insert or speculative insert then we mark such a 'txn' so that it |
737 | | * can't be streamed. We also ensure that if the changes in such a 'txn' can |
738 | | * be streamed and are above logical_decoding_work_mem threshold then we stream |
739 | | * them as soon as we have a complete change. |
740 | | */ |
741 | | static void |
742 | | ReorderBufferProcessPartialChange(ReorderBuffer *rb, ReorderBufferTXN *txn, |
743 | | ReorderBufferChange *change, |
744 | | bool toast_insert) |
745 | 0 | { |
746 | 0 | ReorderBufferTXN *toptxn; |
747 | | |
748 | | /* |
749 | | * The partial changes need to be processed only while streaming |
750 | | * in-progress transactions. |
751 | | */ |
752 | 0 | if (!ReorderBufferCanStream(rb)) |
753 | 0 | return; |
754 | | |
755 | | /* Get the top transaction. */ |
756 | 0 | toptxn = rbtxn_get_toptxn(txn); |
757 | | |
758 | | /* |
759 | | * Indicate a partial change for toast inserts. The change will be |
760 | | * considered as complete once we get the insert or update on the main |
761 | | * table and we are sure that the pending toast chunks are not required |
762 | | * anymore. |
763 | | * |
764 | | * If we allow streaming when there are pending toast chunks then such |
765 | | * chunks won't be released till the insert (multi_insert) is complete and |
766 | | * we expect the txn to have streamed all changes after streaming. This |
767 | | * restriction is mainly to ensure the correctness of streamed |
768 | | * transactions and it doesn't seem worth uplifting such a restriction |
769 | | * just to allow this case because anyway we will stream the transaction |
770 | | * once such an insert is complete. |
771 | | */ |
772 | 0 | if (toast_insert) |
773 | 0 | toptxn->txn_flags |= RBTXN_HAS_PARTIAL_CHANGE; |
774 | 0 | else if (rbtxn_has_partial_change(toptxn) && |
775 | 0 | IsInsertOrUpdate(change->action) && |
776 | 0 | change->data.tp.clear_toast_afterwards) |
777 | 0 | toptxn->txn_flags &= ~RBTXN_HAS_PARTIAL_CHANGE; |
778 | | |
779 | | /* |
780 | | * Indicate a partial change for speculative inserts. The change will be |
781 | | * considered as complete once we get the speculative confirm or abort |
782 | | * token. |
783 | | */ |
784 | 0 | if (IsSpecInsert(change->action)) |
785 | 0 | toptxn->txn_flags |= RBTXN_HAS_PARTIAL_CHANGE; |
786 | 0 | else if (rbtxn_has_partial_change(toptxn) && |
787 | 0 | IsSpecConfirmOrAbort(change->action)) |
788 | 0 | toptxn->txn_flags &= ~RBTXN_HAS_PARTIAL_CHANGE; |
789 | | |
790 | | /* |
791 | | * Stream the transaction if it is serialized before and the changes are |
792 | | * now complete in the top-level transaction. |
793 | | * |
794 | | * The reason for doing the streaming of such a transaction as soon as we |
795 | | * get the complete change for it is that previously it would have reached |
796 | | * the memory threshold and wouldn't get streamed because of incomplete |
797 | | * changes. Delaying such transactions would increase apply lag for them. |
798 | | */ |
799 | 0 | if (ReorderBufferCanStartStreaming(rb) && |
800 | 0 | !(rbtxn_has_partial_change(toptxn)) && |
801 | 0 | rbtxn_is_serialized(txn) && |
802 | 0 | rbtxn_has_streamable_change(toptxn)) |
803 | 0 | ReorderBufferStreamTXN(rb, toptxn); |
804 | 0 | } |
805 | | |
806 | | /* |
807 | | * Queue a change into a transaction so it can be replayed upon commit or will be |
808 | | * streamed when we reach logical_decoding_work_mem threshold. |
809 | | */ |
810 | | void |
811 | | ReorderBufferQueueChange(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn, |
812 | | ReorderBufferChange *change, bool toast_insert) |
813 | 0 | { |
814 | 0 | ReorderBufferTXN *txn; |
815 | |
|
816 | 0 | txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true); |
817 | | |
818 | | /* |
819 | | * If we have detected that the transaction is aborted while streaming the |
820 | | * previous changes or by checking its CLOG, there is no point in |
821 | | * collecting further changes for it. |
822 | | */ |
823 | 0 | if (rbtxn_is_aborted(txn)) |
824 | 0 | { |
825 | | /* |
826 | | * We don't need to update memory accounting for this change as we |
827 | | * have not added it to the queue yet. |
828 | | */ |
829 | 0 | ReorderBufferFreeChange(rb, change, false); |
830 | 0 | return; |
831 | 0 | } |
832 | | |
833 | | /* |
834 | | * The changes that are sent downstream are considered streamable. We |
835 | | * remember such transactions so that only those will later be considered |
836 | | * for streaming. |
837 | | */ |
838 | 0 | if (change->action == REORDER_BUFFER_CHANGE_INSERT || |
839 | 0 | change->action == REORDER_BUFFER_CHANGE_UPDATE || |
840 | 0 | change->action == REORDER_BUFFER_CHANGE_DELETE || |
841 | 0 | change->action == REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT || |
842 | 0 | change->action == REORDER_BUFFER_CHANGE_TRUNCATE || |
843 | 0 | change->action == REORDER_BUFFER_CHANGE_MESSAGE) |
844 | 0 | { |
845 | 0 | ReorderBufferTXN *toptxn = rbtxn_get_toptxn(txn); |
846 | |
|
847 | 0 | toptxn->txn_flags |= RBTXN_HAS_STREAMABLE_CHANGE; |
848 | 0 | } |
849 | |
|
850 | 0 | change->lsn = lsn; |
851 | 0 | change->txn = txn; |
852 | |
|
853 | 0 | Assert(XLogRecPtrIsValid(lsn)); |
854 | 0 | dlist_push_tail(&txn->changes, &change->node); |
855 | 0 | txn->nentries++; |
856 | 0 | txn->nentries_mem++; |
857 | | |
858 | | /* update memory accounting information */ |
859 | 0 | ReorderBufferChangeMemoryUpdate(rb, change, NULL, true, |
860 | 0 | ReorderBufferChangeSize(change)); |
861 | | |
862 | | /* process partial change */ |
863 | 0 | ReorderBufferProcessPartialChange(rb, txn, change, toast_insert); |
864 | | |
865 | | /* check the memory limits and evict something if needed */ |
866 | 0 | ReorderBufferCheckMemoryLimit(rb); |
867 | 0 | } |
868 | | |
869 | | /* |
870 | | * A transactional message is queued to be processed upon commit and a |
871 | | * non-transactional message gets processed immediately. |
872 | | */ |
873 | | void |
874 | | ReorderBufferQueueMessage(ReorderBuffer *rb, TransactionId xid, |
875 | | Snapshot snap, XLogRecPtr lsn, |
876 | | bool transactional, const char *prefix, |
877 | | Size message_size, const char *message) |
878 | 0 | { |
879 | 0 | if (transactional) |
880 | 0 | { |
881 | 0 | MemoryContext oldcontext; |
882 | 0 | ReorderBufferChange *change; |
883 | |
|
884 | 0 | Assert(xid != InvalidTransactionId); |
885 | | |
886 | | /* |
887 | | * We don't expect snapshots for transactional changes - we'll use the |
888 | | * snapshot derived later during apply (unless the change gets |
889 | | * skipped). |
890 | | */ |
891 | 0 | Assert(!snap); |
892 | |
|
893 | 0 | oldcontext = MemoryContextSwitchTo(rb->context); |
894 | |
|
895 | 0 | change = ReorderBufferAllocChange(rb); |
896 | 0 | change->action = REORDER_BUFFER_CHANGE_MESSAGE; |
897 | 0 | change->data.msg.prefix = pstrdup(prefix); |
898 | 0 | change->data.msg.message_size = message_size; |
899 | 0 | change->data.msg.message = palloc(message_size); |
900 | 0 | memcpy(change->data.msg.message, message, message_size); |
901 | |
|
902 | 0 | ReorderBufferQueueChange(rb, xid, lsn, change, false); |
903 | |
|
904 | 0 | MemoryContextSwitchTo(oldcontext); |
905 | 0 | } |
906 | 0 | else |
907 | 0 | { |
908 | 0 | ReorderBufferTXN *txn = NULL; |
909 | 0 | volatile Snapshot snapshot_now = snap; |
910 | | |
911 | | /* Non-transactional changes require a valid snapshot. */ |
912 | 0 | Assert(snapshot_now); |
913 | |
|
914 | 0 | if (xid != InvalidTransactionId) |
915 | 0 | txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true); |
916 | | |
917 | | /* setup snapshot to allow catalog access */ |
918 | 0 | SetupHistoricSnapshot(snapshot_now, NULL); |
919 | 0 | PG_TRY(); |
920 | 0 | { |
921 | 0 | rb->message(rb, txn, lsn, false, prefix, message_size, message); |
922 | |
|
923 | 0 | TeardownHistoricSnapshot(false); |
924 | 0 | } |
925 | 0 | PG_CATCH(); |
926 | 0 | { |
927 | 0 | TeardownHistoricSnapshot(true); |
928 | 0 | PG_RE_THROW(); |
929 | 0 | } |
930 | 0 | PG_END_TRY(); |
931 | 0 | } |
932 | 0 | } |
933 | | |
934 | | /* |
935 | | * AssertTXNLsnOrder |
936 | | * Verify LSN ordering of transaction lists in the reorderbuffer |
937 | | * |
938 | | * Other LSN-related invariants are checked too. |
939 | | * |
940 | | * No-op if assertions are not in use. |
941 | | */ |
942 | | static void |
943 | | AssertTXNLsnOrder(ReorderBuffer *rb) |
944 | 0 | { |
945 | | #ifdef USE_ASSERT_CHECKING |
946 | | LogicalDecodingContext *ctx = rb->private_data; |
947 | | dlist_iter iter; |
948 | | XLogRecPtr prev_first_lsn = InvalidXLogRecPtr; |
949 | | XLogRecPtr prev_base_snap_lsn = InvalidXLogRecPtr; |
950 | | |
951 | | /* |
952 | | * Skip the verification if we don't reach the LSN at which we start |
953 | | * decoding the contents of transactions yet because until we reach the |
954 | | * LSN, we could have transactions that don't have the association between |
955 | | * the top-level transaction and subtransaction yet and consequently have |
956 | | * the same LSN. We don't guarantee this association until we try to |
957 | | * decode the actual contents of transaction. The ordering of the records |
958 | | * prior to the start_decoding_at LSN should have been checked before the |
959 | | * restart. |
960 | | */ |
961 | | if (SnapBuildXactNeedsSkip(ctx->snapshot_builder, ctx->reader->EndRecPtr)) |
962 | | return; |
963 | | |
964 | | dlist_foreach(iter, &rb->toplevel_by_lsn) |
965 | | { |
966 | | ReorderBufferTXN *cur_txn = dlist_container(ReorderBufferTXN, node, |
967 | | iter.cur); |
968 | | |
969 | | /* start LSN must be set */ |
970 | | Assert(XLogRecPtrIsValid(cur_txn->first_lsn)); |
971 | | |
972 | | /* If there is an end LSN, it must be higher than start LSN */ |
973 | | if (XLogRecPtrIsValid(cur_txn->end_lsn)) |
974 | | Assert(cur_txn->first_lsn <= cur_txn->end_lsn); |
975 | | |
976 | | /* Current initial LSN must be strictly higher than previous */ |
977 | | if (XLogRecPtrIsValid(prev_first_lsn)) |
978 | | Assert(prev_first_lsn < cur_txn->first_lsn); |
979 | | |
980 | | /* known-as-subtxn txns must not be listed */ |
981 | | Assert(!rbtxn_is_known_subxact(cur_txn)); |
982 | | |
983 | | prev_first_lsn = cur_txn->first_lsn; |
984 | | } |
985 | | |
986 | | dlist_foreach(iter, &rb->txns_by_base_snapshot_lsn) |
987 | | { |
988 | | ReorderBufferTXN *cur_txn = dlist_container(ReorderBufferTXN, |
989 | | base_snapshot_node, |
990 | | iter.cur); |
991 | | |
992 | | /* base snapshot (and its LSN) must be set */ |
993 | | Assert(cur_txn->base_snapshot != NULL); |
994 | | Assert(XLogRecPtrIsValid(cur_txn->base_snapshot_lsn)); |
995 | | |
996 | | /* current LSN must be strictly higher than previous */ |
997 | | if (XLogRecPtrIsValid(prev_base_snap_lsn)) |
998 | | Assert(prev_base_snap_lsn < cur_txn->base_snapshot_lsn); |
999 | | |
1000 | | /* known-as-subtxn txns must not be listed */ |
1001 | | Assert(!rbtxn_is_known_subxact(cur_txn)); |
1002 | | |
1003 | | prev_base_snap_lsn = cur_txn->base_snapshot_lsn; |
1004 | | } |
1005 | | #endif |
1006 | 0 | } |
1007 | | |
1008 | | /* |
1009 | | * AssertChangeLsnOrder |
1010 | | * |
1011 | | * Check ordering of changes in the (sub)transaction. |
1012 | | */ |
1013 | | static void |
1014 | | AssertChangeLsnOrder(ReorderBufferTXN *txn) |
1015 | 0 | { |
1016 | | #ifdef USE_ASSERT_CHECKING |
1017 | | dlist_iter iter; |
1018 | | XLogRecPtr prev_lsn = txn->first_lsn; |
1019 | | |
1020 | | dlist_foreach(iter, &txn->changes) |
1021 | | { |
1022 | | ReorderBufferChange *cur_change; |
1023 | | |
1024 | | cur_change = dlist_container(ReorderBufferChange, node, iter.cur); |
1025 | | |
1026 | | Assert(XLogRecPtrIsValid(txn->first_lsn)); |
1027 | | Assert(XLogRecPtrIsValid(cur_change->lsn)); |
1028 | | Assert(txn->first_lsn <= cur_change->lsn); |
1029 | | |
1030 | | if (XLogRecPtrIsValid(txn->end_lsn)) |
1031 | | Assert(cur_change->lsn <= txn->end_lsn); |
1032 | | |
1033 | | Assert(prev_lsn <= cur_change->lsn); |
1034 | | |
1035 | | prev_lsn = cur_change->lsn; |
1036 | | } |
1037 | | #endif |
1038 | 0 | } |
1039 | | |
1040 | | /* |
1041 | | * ReorderBufferGetOldestTXN |
1042 | | * Return oldest transaction in reorderbuffer |
1043 | | */ |
1044 | | ReorderBufferTXN * |
1045 | | ReorderBufferGetOldestTXN(ReorderBuffer *rb) |
1046 | 0 | { |
1047 | 0 | ReorderBufferTXN *txn; |
1048 | |
|
1049 | 0 | AssertTXNLsnOrder(rb); |
1050 | |
|
1051 | 0 | if (dlist_is_empty(&rb->toplevel_by_lsn)) |
1052 | 0 | return NULL; |
1053 | | |
1054 | 0 | txn = dlist_head_element(ReorderBufferTXN, node, &rb->toplevel_by_lsn); |
1055 | |
|
1056 | 0 | Assert(!rbtxn_is_known_subxact(txn)); |
1057 | 0 | Assert(XLogRecPtrIsValid(txn->first_lsn)); |
1058 | 0 | return txn; |
1059 | 0 | } |
1060 | | |
1061 | | /* |
1062 | | * ReorderBufferGetOldestXmin |
1063 | | * Return oldest Xmin in reorderbuffer |
1064 | | * |
1065 | | * Returns oldest possibly running Xid from the point of view of snapshots |
1066 | | * used in the transactions kept by reorderbuffer, or InvalidTransactionId if |
1067 | | * there are none. |
1068 | | * |
1069 | | * Since snapshots are assigned monotonically, this equals the Xmin of the |
1070 | | * base snapshot with minimal base_snapshot_lsn. |
1071 | | */ |
1072 | | TransactionId |
1073 | | ReorderBufferGetOldestXmin(ReorderBuffer *rb) |
1074 | 0 | { |
1075 | 0 | ReorderBufferTXN *txn; |
1076 | |
|
1077 | 0 | AssertTXNLsnOrder(rb); |
1078 | |
|
1079 | 0 | if (dlist_is_empty(&rb->txns_by_base_snapshot_lsn)) |
1080 | 0 | return InvalidTransactionId; |
1081 | | |
1082 | 0 | txn = dlist_head_element(ReorderBufferTXN, base_snapshot_node, |
1083 | 0 | &rb->txns_by_base_snapshot_lsn); |
1084 | 0 | return txn->base_snapshot->xmin; |
1085 | 0 | } |
1086 | | |
1087 | | void |
1088 | | ReorderBufferSetRestartPoint(ReorderBuffer *rb, XLogRecPtr ptr) |
1089 | 0 | { |
1090 | 0 | rb->current_restart_decoding_lsn = ptr; |
1091 | 0 | } |
1092 | | |
1093 | | /* |
1094 | | * ReorderBufferAssignChild |
1095 | | * |
1096 | | * Make note that we know that subxid is a subtransaction of xid, seen as of |
1097 | | * the given lsn. |
1098 | | */ |
1099 | | void |
1100 | | ReorderBufferAssignChild(ReorderBuffer *rb, TransactionId xid, |
1101 | | TransactionId subxid, XLogRecPtr lsn) |
1102 | 0 | { |
1103 | 0 | ReorderBufferTXN *txn; |
1104 | 0 | ReorderBufferTXN *subtxn; |
1105 | 0 | bool new_top; |
1106 | 0 | bool new_sub; |
1107 | |
|
1108 | 0 | txn = ReorderBufferTXNByXid(rb, xid, true, &new_top, lsn, true); |
1109 | 0 | subtxn = ReorderBufferTXNByXid(rb, subxid, true, &new_sub, lsn, false); |
1110 | |
|
1111 | 0 | if (!new_sub) |
1112 | 0 | { |
1113 | 0 | if (rbtxn_is_known_subxact(subtxn)) |
1114 | 0 | { |
1115 | | /* already associated, nothing to do */ |
1116 | 0 | return; |
1117 | 0 | } |
1118 | 0 | else |
1119 | 0 | { |
1120 | | /* |
1121 | | * We already saw this transaction, but initially added it to the |
1122 | | * list of top-level txns. Now that we know it's not top-level, |
1123 | | * remove it from there. |
1124 | | */ |
1125 | 0 | dlist_delete(&subtxn->node); |
1126 | 0 | } |
1127 | 0 | } |
1128 | | |
1129 | 0 | subtxn->txn_flags |= RBTXN_IS_SUBXACT; |
1130 | 0 | subtxn->toplevel_xid = xid; |
1131 | 0 | Assert(subtxn->nsubtxns == 0); |
1132 | | |
1133 | | /* set the reference to top-level transaction */ |
1134 | 0 | subtxn->toptxn = txn; |
1135 | | |
1136 | | /* add to subtransaction list */ |
1137 | 0 | dlist_push_tail(&txn->subtxns, &subtxn->node); |
1138 | 0 | txn->nsubtxns++; |
1139 | | |
1140 | | /* Possibly transfer the subtxn's snapshot to its top-level txn. */ |
1141 | 0 | ReorderBufferTransferSnapToParent(txn, subtxn); |
1142 | | |
1143 | | /* Verify LSN-ordering invariant */ |
1144 | 0 | AssertTXNLsnOrder(rb); |
1145 | 0 | } |
1146 | | |
1147 | | /* |
1148 | | * ReorderBufferTransferSnapToParent |
1149 | | * Transfer base snapshot from subtxn to top-level txn, if needed |
1150 | | * |
1151 | | * This is done if the top-level txn doesn't have a base snapshot, or if the |
1152 | | * subtxn's base snapshot has an earlier LSN than the top-level txn's base |
1153 | | * snapshot's LSN. This can happen if there are no changes in the toplevel |
1154 | | * txn but there are some in the subtxn, or the first change in subtxn has |
1155 | | * earlier LSN than first change in the top-level txn and we learned about |
1156 | | * their kinship only now. |
1157 | | * |
1158 | | * The subtransaction's snapshot is cleared regardless of the transfer |
1159 | | * happening, since it's not needed anymore in either case. |
1160 | | * |
1161 | | * We do this as soon as we become aware of their kinship, to avoid queueing |
1162 | | * extra snapshots to txns known-as-subtxns -- only top-level txns will |
1163 | | * receive further snapshots. |
1164 | | */ |
1165 | | static void |
1166 | | ReorderBufferTransferSnapToParent(ReorderBufferTXN *txn, |
1167 | | ReorderBufferTXN *subtxn) |
1168 | 0 | { |
1169 | 0 | Assert(subtxn->toplevel_xid == txn->xid); |
1170 | |
|
1171 | 0 | if (subtxn->base_snapshot != NULL) |
1172 | 0 | { |
1173 | 0 | if (txn->base_snapshot == NULL || |
1174 | 0 | subtxn->base_snapshot_lsn < txn->base_snapshot_lsn) |
1175 | 0 | { |
1176 | | /* |
1177 | | * If the toplevel transaction already has a base snapshot but |
1178 | | * it's newer than the subxact's, purge it. |
1179 | | */ |
1180 | 0 | if (txn->base_snapshot != NULL) |
1181 | 0 | { |
1182 | 0 | SnapBuildSnapDecRefcount(txn->base_snapshot); |
1183 | 0 | dlist_delete(&txn->base_snapshot_node); |
1184 | 0 | } |
1185 | | |
1186 | | /* |
1187 | | * The snapshot is now the top transaction's; transfer it, and |
1188 | | * adjust the list position of the top transaction in the list by |
1189 | | * moving it to where the subtransaction is. |
1190 | | */ |
1191 | 0 | txn->base_snapshot = subtxn->base_snapshot; |
1192 | 0 | txn->base_snapshot_lsn = subtxn->base_snapshot_lsn; |
1193 | 0 | dlist_insert_before(&subtxn->base_snapshot_node, |
1194 | 0 | &txn->base_snapshot_node); |
1195 | | |
1196 | | /* |
1197 | | * The subtransaction doesn't have a snapshot anymore (so it |
1198 | | * mustn't be in the list.) |
1199 | | */ |
1200 | 0 | subtxn->base_snapshot = NULL; |
1201 | 0 | subtxn->base_snapshot_lsn = InvalidXLogRecPtr; |
1202 | 0 | dlist_delete(&subtxn->base_snapshot_node); |
1203 | 0 | } |
1204 | 0 | else |
1205 | 0 | { |
1206 | | /* Base snap of toplevel is fine, so subxact's is not needed */ |
1207 | 0 | SnapBuildSnapDecRefcount(subtxn->base_snapshot); |
1208 | 0 | dlist_delete(&subtxn->base_snapshot_node); |
1209 | 0 | subtxn->base_snapshot = NULL; |
1210 | 0 | subtxn->base_snapshot_lsn = InvalidXLogRecPtr; |
1211 | 0 | } |
1212 | 0 | } |
1213 | 0 | } |
1214 | | |
1215 | | /* |
1216 | | * Associate a subtransaction with its toplevel transaction at commit |
1217 | | * time. There may be no further changes added after this. |
1218 | | */ |
1219 | | void |
1220 | | ReorderBufferCommitChild(ReorderBuffer *rb, TransactionId xid, |
1221 | | TransactionId subxid, XLogRecPtr commit_lsn, |
1222 | | XLogRecPtr end_lsn) |
1223 | 0 | { |
1224 | 0 | ReorderBufferTXN *subtxn; |
1225 | |
|
1226 | 0 | subtxn = ReorderBufferTXNByXid(rb, subxid, false, NULL, |
1227 | 0 | InvalidXLogRecPtr, false); |
1228 | | |
1229 | | /* |
1230 | | * No need to do anything if that subtxn didn't contain any changes |
1231 | | */ |
1232 | 0 | if (!subtxn) |
1233 | 0 | return; |
1234 | | |
1235 | 0 | subtxn->final_lsn = commit_lsn; |
1236 | 0 | subtxn->end_lsn = end_lsn; |
1237 | | |
1238 | | /* |
1239 | | * Assign this subxact as a child of the toplevel xact (no-op if already |
1240 | | * done.) |
1241 | | */ |
1242 | 0 | ReorderBufferAssignChild(rb, xid, subxid, InvalidXLogRecPtr); |
1243 | 0 | } |
1244 | | |
1245 | | |
1246 | | /* |
1247 | | * Support for efficiently iterating over a transaction's and its |
1248 | | * subtransactions' changes. |
1249 | | * |
1250 | | * We do by doing a k-way merge between transactions/subtransactions. For that |
1251 | | * we model the current heads of the different transactions as a binary heap |
1252 | | * so we easily know which (sub-)transaction has the change with the smallest |
1253 | | * lsn next. |
1254 | | * |
1255 | | * We assume the changes in individual transactions are already sorted by LSN. |
1256 | | */ |
1257 | | |
1258 | | /* |
1259 | | * Binary heap comparison function. |
1260 | | */ |
1261 | | static int |
1262 | | ReorderBufferIterCompare(Datum a, Datum b, void *arg) |
1263 | 0 | { |
1264 | 0 | ReorderBufferIterTXNState *state = (ReorderBufferIterTXNState *) arg; |
1265 | 0 | XLogRecPtr pos_a = state->entries[DatumGetInt32(a)].lsn; |
1266 | 0 | XLogRecPtr pos_b = state->entries[DatumGetInt32(b)].lsn; |
1267 | |
|
1268 | 0 | if (pos_a < pos_b) |
1269 | 0 | return 1; |
1270 | 0 | else if (pos_a == pos_b) |
1271 | 0 | return 0; |
1272 | 0 | return -1; |
1273 | 0 | } |
1274 | | |
1275 | | /* |
1276 | | * Allocate & initialize an iterator which iterates in lsn order over a |
1277 | | * transaction and all its subtransactions. |
1278 | | * |
1279 | | * Note: The iterator state is returned through iter_state parameter rather |
1280 | | * than the function's return value. This is because the state gets cleaned up |
1281 | | * in a PG_CATCH block in the caller, so we want to make sure the caller gets |
1282 | | * back the state even if this function throws an exception. |
1283 | | */ |
1284 | | static void |
1285 | | ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn, |
1286 | | ReorderBufferIterTXNState *volatile *iter_state) |
1287 | 0 | { |
1288 | 0 | Size nr_txns = 0; |
1289 | 0 | ReorderBufferIterTXNState *state; |
1290 | 0 | dlist_iter cur_txn_i; |
1291 | 0 | Size off; |
1292 | |
|
1293 | 0 | *iter_state = NULL; |
1294 | | |
1295 | | /* Check ordering of changes in the toplevel transaction. */ |
1296 | 0 | AssertChangeLsnOrder(txn); |
1297 | | |
1298 | | /* |
1299 | | * Calculate the size of our heap: one element for every transaction that |
1300 | | * contains changes. (Besides the transactions already in the reorder |
1301 | | * buffer, we count the one we were directly passed.) |
1302 | | */ |
1303 | 0 | if (txn->nentries > 0) |
1304 | 0 | nr_txns++; |
1305 | |
|
1306 | 0 | dlist_foreach(cur_txn_i, &txn->subtxns) |
1307 | 0 | { |
1308 | 0 | ReorderBufferTXN *cur_txn; |
1309 | |
|
1310 | 0 | cur_txn = dlist_container(ReorderBufferTXN, node, cur_txn_i.cur); |
1311 | | |
1312 | | /* Check ordering of changes in this subtransaction. */ |
1313 | 0 | AssertChangeLsnOrder(cur_txn); |
1314 | |
|
1315 | 0 | if (cur_txn->nentries > 0) |
1316 | 0 | nr_txns++; |
1317 | 0 | } |
1318 | | |
1319 | | /* allocate iteration state */ |
1320 | 0 | state = (ReorderBufferIterTXNState *) |
1321 | 0 | MemoryContextAllocZero(rb->context, |
1322 | 0 | sizeof(ReorderBufferIterTXNState) + |
1323 | 0 | sizeof(ReorderBufferIterTXNEntry) * nr_txns); |
1324 | |
|
1325 | 0 | state->nr_txns = nr_txns; |
1326 | 0 | dlist_init(&state->old_change); |
1327 | |
|
1328 | 0 | for (off = 0; off < state->nr_txns; off++) |
1329 | 0 | { |
1330 | 0 | state->entries[off].file.vfd = -1; |
1331 | 0 | state->entries[off].segno = 0; |
1332 | 0 | } |
1333 | | |
1334 | | /* allocate heap */ |
1335 | 0 | state->heap = binaryheap_allocate(state->nr_txns, |
1336 | 0 | ReorderBufferIterCompare, |
1337 | 0 | state); |
1338 | | |
1339 | | /* Now that the state fields are initialized, it is safe to return it. */ |
1340 | 0 | *iter_state = state; |
1341 | | |
1342 | | /* |
1343 | | * Now insert items into the binary heap, in an unordered fashion. (We |
1344 | | * will run a heap assembly step at the end; this is more efficient.) |
1345 | | */ |
1346 | |
|
1347 | 0 | off = 0; |
1348 | | |
1349 | | /* add toplevel transaction if it contains changes */ |
1350 | 0 | if (txn->nentries > 0) |
1351 | 0 | { |
1352 | 0 | ReorderBufferChange *cur_change; |
1353 | |
|
1354 | 0 | if (rbtxn_is_serialized(txn)) |
1355 | 0 | { |
1356 | | /* serialize remaining changes */ |
1357 | 0 | ReorderBufferSerializeTXN(rb, txn); |
1358 | 0 | ReorderBufferRestoreChanges(rb, txn, &state->entries[off].file, |
1359 | 0 | &state->entries[off].segno); |
1360 | 0 | } |
1361 | |
|
1362 | 0 | cur_change = dlist_head_element(ReorderBufferChange, node, |
1363 | 0 | &txn->changes); |
1364 | |
|
1365 | 0 | state->entries[off].lsn = cur_change->lsn; |
1366 | 0 | state->entries[off].change = cur_change; |
1367 | 0 | state->entries[off].txn = txn; |
1368 | |
|
1369 | 0 | binaryheap_add_unordered(state->heap, Int32GetDatum(off++)); |
1370 | 0 | } |
1371 | | |
1372 | | /* add subtransactions if they contain changes */ |
1373 | 0 | dlist_foreach(cur_txn_i, &txn->subtxns) |
1374 | 0 | { |
1375 | 0 | ReorderBufferTXN *cur_txn; |
1376 | |
|
1377 | 0 | cur_txn = dlist_container(ReorderBufferTXN, node, cur_txn_i.cur); |
1378 | |
|
1379 | 0 | if (cur_txn->nentries > 0) |
1380 | 0 | { |
1381 | 0 | ReorderBufferChange *cur_change; |
1382 | |
|
1383 | 0 | if (rbtxn_is_serialized(cur_txn)) |
1384 | 0 | { |
1385 | | /* serialize remaining changes */ |
1386 | 0 | ReorderBufferSerializeTXN(rb, cur_txn); |
1387 | 0 | ReorderBufferRestoreChanges(rb, cur_txn, |
1388 | 0 | &state->entries[off].file, |
1389 | 0 | &state->entries[off].segno); |
1390 | 0 | } |
1391 | 0 | cur_change = dlist_head_element(ReorderBufferChange, node, |
1392 | 0 | &cur_txn->changes); |
1393 | |
|
1394 | 0 | state->entries[off].lsn = cur_change->lsn; |
1395 | 0 | state->entries[off].change = cur_change; |
1396 | 0 | state->entries[off].txn = cur_txn; |
1397 | |
|
1398 | 0 | binaryheap_add_unordered(state->heap, Int32GetDatum(off++)); |
1399 | 0 | } |
1400 | 0 | } |
1401 | | |
1402 | | /* assemble a valid binary heap */ |
1403 | 0 | binaryheap_build(state->heap); |
1404 | 0 | } |
1405 | | |
1406 | | /* |
1407 | | * Return the next change when iterating over a transaction and its |
1408 | | * subtransactions. |
1409 | | * |
1410 | | * Returns NULL when no further changes exist. |
1411 | | */ |
1412 | | static ReorderBufferChange * |
1413 | | ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state) |
1414 | | { |
1415 | | ReorderBufferChange *change; |
1416 | | ReorderBufferIterTXNEntry *entry; |
1417 | | int32 off; |
1418 | | |
1419 | | /* nothing there anymore */ |
1420 | | if (binaryheap_empty(state->heap)) |
1421 | | return NULL; |
1422 | | |
1423 | | off = DatumGetInt32(binaryheap_first(state->heap)); |
1424 | | entry = &state->entries[off]; |
1425 | | |
1426 | | /* free memory we might have "leaked" in the previous *Next call */ |
1427 | | if (!dlist_is_empty(&state->old_change)) |
1428 | | { |
1429 | | change = dlist_container(ReorderBufferChange, node, |
1430 | | dlist_pop_head_node(&state->old_change)); |
1431 | | ReorderBufferFreeChange(rb, change, true); |
1432 | | Assert(dlist_is_empty(&state->old_change)); |
1433 | | } |
1434 | | |
1435 | | change = entry->change; |
1436 | | |
1437 | | /* |
1438 | | * update heap with information about which transaction has the next |
1439 | | * relevant change in LSN order |
1440 | | */ |
1441 | | |
1442 | | /* there are in-memory changes */ |
1443 | | if (dlist_has_next(&entry->txn->changes, &entry->change->node)) |
1444 | | { |
1445 | | dlist_node *next = dlist_next_node(&entry->txn->changes, &change->node); |
1446 | | ReorderBufferChange *next_change = |
1447 | | dlist_container(ReorderBufferChange, node, next); |
1448 | | |
1449 | | /* txn stays the same */ |
1450 | | state->entries[off].lsn = next_change->lsn; |
1451 | | state->entries[off].change = next_change; |
1452 | | |
1453 | | binaryheap_replace_first(state->heap, Int32GetDatum(off)); |
1454 | | return change; |
1455 | | } |
1456 | | |
1457 | | /* try to load changes from disk */ |
1458 | | if (entry->txn->nentries != entry->txn->nentries_mem) |
1459 | | { |
1460 | | /* |
1461 | | * Ugly: restoring changes will reuse *Change records, thus delete the |
1462 | | * current one from the per-tx list and only free in the next call. |
1463 | | */ |
1464 | | dlist_delete(&change->node); |
1465 | | dlist_push_tail(&state->old_change, &change->node); |
1466 | | |
1467 | | /* |
1468 | | * Update the total bytes processed by the txn for which we are |
1469 | | * releasing the current set of changes and restoring the new set of |
1470 | | * changes. |
1471 | | */ |
1472 | | rb->totalBytes += entry->txn->size; |
1473 | | if (ReorderBufferRestoreChanges(rb, entry->txn, &entry->file, |
1474 | | &state->entries[off].segno)) |
1475 | | { |
1476 | | /* successfully restored changes from disk */ |
1477 | | ReorderBufferChange *next_change = |
1478 | | dlist_head_element(ReorderBufferChange, node, |
1479 | | &entry->txn->changes); |
1480 | | |
1481 | | elog(DEBUG2, "restored %u/%u changes from disk", |
1482 | | (uint32) entry->txn->nentries_mem, |
1483 | | (uint32) entry->txn->nentries); |
1484 | | |
1485 | | Assert(entry->txn->nentries_mem); |
1486 | | /* txn stays the same */ |
1487 | | state->entries[off].lsn = next_change->lsn; |
1488 | | state->entries[off].change = next_change; |
1489 | | binaryheap_replace_first(state->heap, Int32GetDatum(off)); |
1490 | | |
1491 | | return change; |
1492 | | } |
1493 | | } |
1494 | | |
1495 | | /* ok, no changes there anymore, remove */ |
1496 | | binaryheap_remove_first(state->heap); |
1497 | | |
1498 | | return change; |
1499 | | } |
1500 | | |
1501 | | /* |
1502 | | * Deallocate the iterator |
1503 | | */ |
1504 | | static void |
1505 | | ReorderBufferIterTXNFinish(ReorderBuffer *rb, |
1506 | | ReorderBufferIterTXNState *state) |
1507 | 0 | { |
1508 | 0 | Size off; |
1509 | |
|
1510 | 0 | for (off = 0; off < state->nr_txns; off++) |
1511 | 0 | { |
1512 | 0 | if (state->entries[off].file.vfd != -1) |
1513 | 0 | FileClose(state->entries[off].file.vfd); |
1514 | 0 | } |
1515 | | |
1516 | | /* free memory we might have "leaked" in the last *Next call */ |
1517 | 0 | if (!dlist_is_empty(&state->old_change)) |
1518 | 0 | { |
1519 | 0 | ReorderBufferChange *change; |
1520 | |
|
1521 | 0 | change = dlist_container(ReorderBufferChange, node, |
1522 | 0 | dlist_pop_head_node(&state->old_change)); |
1523 | 0 | ReorderBufferFreeChange(rb, change, true); |
1524 | 0 | Assert(dlist_is_empty(&state->old_change)); |
1525 | 0 | } |
1526 | |
|
1527 | 0 | binaryheap_free(state->heap); |
1528 | 0 | pfree(state); |
1529 | 0 | } |
1530 | | |
1531 | | /* |
1532 | | * Cleanup the contents of a transaction, usually after the transaction |
1533 | | * committed or aborted. |
1534 | | */ |
1535 | | static void |
1536 | | ReorderBufferCleanupTXN(ReorderBuffer *rb, ReorderBufferTXN *txn) |
1537 | 0 | { |
1538 | 0 | bool found; |
1539 | 0 | dlist_mutable_iter iter; |
1540 | 0 | Size mem_freed = 0; |
1541 | | |
1542 | | /* cleanup subtransactions & their changes */ |
1543 | 0 | dlist_foreach_modify(iter, &txn->subtxns) |
1544 | 0 | { |
1545 | 0 | ReorderBufferTXN *subtxn; |
1546 | |
|
1547 | 0 | subtxn = dlist_container(ReorderBufferTXN, node, iter.cur); |
1548 | | |
1549 | | /* |
1550 | | * Subtransactions are always associated to the toplevel TXN, even if |
1551 | | * they originally were happening inside another subtxn, so we won't |
1552 | | * ever recurse more than one level deep here. |
1553 | | */ |
1554 | 0 | Assert(rbtxn_is_known_subxact(subtxn)); |
1555 | 0 | Assert(subtxn->nsubtxns == 0); |
1556 | |
|
1557 | 0 | ReorderBufferCleanupTXN(rb, subtxn); |
1558 | 0 | } |
1559 | | |
1560 | | /* cleanup changes in the txn */ |
1561 | 0 | dlist_foreach_modify(iter, &txn->changes) |
1562 | 0 | { |
1563 | 0 | ReorderBufferChange *change; |
1564 | |
|
1565 | 0 | change = dlist_container(ReorderBufferChange, node, iter.cur); |
1566 | | |
1567 | | /* Check we're not mixing changes from different transactions. */ |
1568 | 0 | Assert(change->txn == txn); |
1569 | | |
1570 | | /* |
1571 | | * Instead of updating the memory counter for individual changes, we |
1572 | | * sum up the size of memory to free so we can update the memory |
1573 | | * counter all together below. This saves costs of maintaining the |
1574 | | * max-heap. |
1575 | | */ |
1576 | 0 | mem_freed += ReorderBufferChangeSize(change); |
1577 | |
|
1578 | 0 | ReorderBufferFreeChange(rb, change, false); |
1579 | 0 | } |
1580 | | |
1581 | | /* Update the memory counter */ |
1582 | 0 | ReorderBufferChangeMemoryUpdate(rb, NULL, txn, false, mem_freed); |
1583 | | |
1584 | | /* |
1585 | | * Cleanup the tuplecids we stored for decoding catalog snapshot access. |
1586 | | * They are always stored in the toplevel transaction. |
1587 | | */ |
1588 | 0 | dlist_foreach_modify(iter, &txn->tuplecids) |
1589 | 0 | { |
1590 | 0 | ReorderBufferChange *change; |
1591 | |
|
1592 | 0 | change = dlist_container(ReorderBufferChange, node, iter.cur); |
1593 | | |
1594 | | /* Check we're not mixing changes from different transactions. */ |
1595 | 0 | Assert(change->txn == txn); |
1596 | 0 | Assert(change->action == REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID); |
1597 | |
|
1598 | 0 | ReorderBufferFreeChange(rb, change, true); |
1599 | 0 | } |
1600 | | |
1601 | | /* |
1602 | | * Cleanup the base snapshot, if set. |
1603 | | */ |
1604 | 0 | if (txn->base_snapshot != NULL) |
1605 | 0 | { |
1606 | 0 | SnapBuildSnapDecRefcount(txn->base_snapshot); |
1607 | 0 | dlist_delete(&txn->base_snapshot_node); |
1608 | 0 | } |
1609 | | |
1610 | | /* |
1611 | | * Cleanup the snapshot for the last streamed run. |
1612 | | */ |
1613 | 0 | if (txn->snapshot_now != NULL) |
1614 | 0 | { |
1615 | 0 | Assert(rbtxn_is_streamed(txn)); |
1616 | 0 | ReorderBufferFreeSnap(rb, txn->snapshot_now); |
1617 | 0 | } |
1618 | | |
1619 | | /* |
1620 | | * Remove TXN from its containing lists. |
1621 | | * |
1622 | | * Note: if txn is known as subxact, we are deleting the TXN from its |
1623 | | * parent's list of known subxacts; this leaves the parent's nsubxacts |
1624 | | * count too high, but we don't care. Otherwise, we are deleting the TXN |
1625 | | * from the LSN-ordered list of toplevel TXNs. We remove the TXN from the |
1626 | | * list of catalog modifying transactions as well. |
1627 | | */ |
1628 | 0 | dlist_delete(&txn->node); |
1629 | 0 | if (rbtxn_has_catalog_changes(txn)) |
1630 | 0 | dclist_delete_from(&rb->catchange_txns, &txn->catchange_node); |
1631 | | |
1632 | | /* now remove reference from buffer */ |
1633 | 0 | hash_search(rb->by_txn, &txn->xid, HASH_REMOVE, &found); |
1634 | 0 | Assert(found); |
1635 | | |
1636 | | /* remove entries spilled to disk */ |
1637 | 0 | if (rbtxn_is_serialized(txn)) |
1638 | 0 | ReorderBufferRestoreCleanup(rb, txn); |
1639 | | |
1640 | | /* deallocate */ |
1641 | 0 | ReorderBufferFreeTXN(rb, txn); |
1642 | 0 | } |
1643 | | |
1644 | | /* |
1645 | | * Discard changes from a transaction (and subtransactions), either after |
1646 | | * streaming, decoding them at PREPARE, or detecting the transaction abort. |
1647 | | * Keep the remaining info - transactions, tuplecids, invalidations and |
1648 | | * snapshots. |
1649 | | * |
1650 | | * We additionally remove tuplecids after decoding the transaction at prepare |
1651 | | * time as we only need to perform invalidation at rollback or commit prepared. |
1652 | | * |
1653 | | * 'txn_prepared' indicates that we have decoded the transaction at prepare |
1654 | | * time. |
1655 | | */ |
1656 | | static void |
1657 | | ReorderBufferTruncateTXN(ReorderBuffer *rb, ReorderBufferTXN *txn, bool txn_prepared) |
1658 | 0 | { |
1659 | 0 | dlist_mutable_iter iter; |
1660 | 0 | Size mem_freed = 0; |
1661 | | |
1662 | | /* cleanup subtransactions & their changes */ |
1663 | 0 | dlist_foreach_modify(iter, &txn->subtxns) |
1664 | 0 | { |
1665 | 0 | ReorderBufferTXN *subtxn; |
1666 | |
|
1667 | 0 | subtxn = dlist_container(ReorderBufferTXN, node, iter.cur); |
1668 | | |
1669 | | /* |
1670 | | * Subtransactions are always associated to the toplevel TXN, even if |
1671 | | * they originally were happening inside another subtxn, so we won't |
1672 | | * ever recurse more than one level deep here. |
1673 | | */ |
1674 | 0 | Assert(rbtxn_is_known_subxact(subtxn)); |
1675 | 0 | Assert(subtxn->nsubtxns == 0); |
1676 | |
|
1677 | 0 | ReorderBufferMaybeMarkTXNStreamed(rb, subtxn); |
1678 | 0 | ReorderBufferTruncateTXN(rb, subtxn, txn_prepared); |
1679 | 0 | } |
1680 | | |
1681 | | /* cleanup changes in the txn */ |
1682 | 0 | dlist_foreach_modify(iter, &txn->changes) |
1683 | 0 | { |
1684 | 0 | ReorderBufferChange *change; |
1685 | |
|
1686 | 0 | change = dlist_container(ReorderBufferChange, node, iter.cur); |
1687 | | |
1688 | | /* Check we're not mixing changes from different transactions. */ |
1689 | 0 | Assert(change->txn == txn); |
1690 | | |
1691 | | /* remove the change from its containing list */ |
1692 | 0 | dlist_delete(&change->node); |
1693 | | |
1694 | | /* |
1695 | | * Instead of updating the memory counter for individual changes, we |
1696 | | * sum up the size of memory to free so we can update the memory |
1697 | | * counter all together below. This saves costs of maintaining the |
1698 | | * max-heap. |
1699 | | */ |
1700 | 0 | mem_freed += ReorderBufferChangeSize(change); |
1701 | |
|
1702 | 0 | ReorderBufferFreeChange(rb, change, false); |
1703 | 0 | } |
1704 | | |
1705 | | /* Update the memory counter */ |
1706 | 0 | ReorderBufferChangeMemoryUpdate(rb, NULL, txn, false, mem_freed); |
1707 | |
|
1708 | 0 | if (txn_prepared) |
1709 | 0 | { |
1710 | | /* |
1711 | | * If this is a prepared txn, cleanup the tuplecids we stored for |
1712 | | * decoding catalog snapshot access. They are always stored in the |
1713 | | * toplevel transaction. |
1714 | | */ |
1715 | 0 | dlist_foreach_modify(iter, &txn->tuplecids) |
1716 | 0 | { |
1717 | 0 | ReorderBufferChange *change; |
1718 | |
|
1719 | 0 | change = dlist_container(ReorderBufferChange, node, iter.cur); |
1720 | | |
1721 | | /* Check we're not mixing changes from different transactions. */ |
1722 | 0 | Assert(change->txn == txn); |
1723 | 0 | Assert(change->action == REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID); |
1724 | | |
1725 | | /* Remove the change from its containing list. */ |
1726 | 0 | dlist_delete(&change->node); |
1727 | |
|
1728 | 0 | ReorderBufferFreeChange(rb, change, true); |
1729 | 0 | } |
1730 | 0 | } |
1731 | | |
1732 | | /* |
1733 | | * Destroy the (relfilelocator, ctid) hashtable, so that we don't leak any |
1734 | | * memory. We could also keep the hash table and update it with new ctid |
1735 | | * values, but this seems simpler and good enough for now. |
1736 | | */ |
1737 | 0 | if (txn->tuplecid_hash != NULL) |
1738 | 0 | { |
1739 | 0 | hash_destroy(txn->tuplecid_hash); |
1740 | 0 | txn->tuplecid_hash = NULL; |
1741 | 0 | } |
1742 | | |
1743 | | /* If this txn is serialized then clean the disk space. */ |
1744 | 0 | if (rbtxn_is_serialized(txn)) |
1745 | 0 | { |
1746 | 0 | ReorderBufferRestoreCleanup(rb, txn); |
1747 | 0 | txn->txn_flags &= ~RBTXN_IS_SERIALIZED; |
1748 | | |
1749 | | /* |
1750 | | * We set this flag to indicate if the transaction is ever serialized. |
1751 | | * We need this to accurately update the stats as otherwise the same |
1752 | | * transaction can be counted as serialized multiple times. |
1753 | | */ |
1754 | 0 | txn->txn_flags |= RBTXN_IS_SERIALIZED_CLEAR; |
1755 | 0 | } |
1756 | | |
1757 | | /* also reset the number of entries in the transaction */ |
1758 | 0 | txn->nentries_mem = 0; |
1759 | 0 | txn->nentries = 0; |
1760 | 0 | } |
1761 | | |
1762 | | /* |
1763 | | * Check the transaction status by CLOG lookup and discard all changes if |
1764 | | * the transaction is aborted. The transaction status is cached in |
1765 | | * txn->txn_flags so we can skip future changes and avoid CLOG lookups on the |
1766 | | * next call. |
1767 | | * |
1768 | | * Return true if the transaction is aborted, otherwise return false. |
1769 | | * |
1770 | | * When the 'debug_logical_replication_streaming' is set to "immediate", we |
1771 | | * don't check the transaction status, meaning the caller will always process |
1772 | | * this transaction. |
1773 | | */ |
1774 | | static bool |
1775 | | ReorderBufferCheckAndTruncateAbortedTXN(ReorderBuffer *rb, ReorderBufferTXN *txn) |
1776 | 0 | { |
1777 | | /* Quick return for regression tests */ |
1778 | 0 | if (unlikely(debug_logical_replication_streaming == DEBUG_LOGICAL_REP_STREAMING_IMMEDIATE)) |
1779 | 0 | return false; |
1780 | | |
1781 | | /* |
1782 | | * Quick return if the transaction status is already known. |
1783 | | */ |
1784 | | |
1785 | 0 | if (rbtxn_is_committed(txn)) |
1786 | 0 | return false; |
1787 | 0 | if (rbtxn_is_aborted(txn)) |
1788 | 0 | { |
1789 | | /* Already-aborted transactions should not have any changes */ |
1790 | 0 | Assert(txn->size == 0); |
1791 | |
|
1792 | 0 | return true; |
1793 | 0 | } |
1794 | | |
1795 | | /* Otherwise, check the transaction status using CLOG lookup */ |
1796 | | |
1797 | 0 | if (TransactionIdIsInProgress(txn->xid)) |
1798 | 0 | return false; |
1799 | | |
1800 | 0 | if (TransactionIdDidCommit(txn->xid)) |
1801 | 0 | { |
1802 | | /* |
1803 | | * Remember the transaction is committed so that we can skip CLOG |
1804 | | * check next time, avoiding the pressure on CLOG lookup. |
1805 | | */ |
1806 | 0 | Assert(!rbtxn_is_aborted(txn)); |
1807 | 0 | txn->txn_flags |= RBTXN_IS_COMMITTED; |
1808 | 0 | return false; |
1809 | 0 | } |
1810 | | |
1811 | | /* |
1812 | | * The transaction aborted. We discard both the changes collected so far |
1813 | | * and the toast reconstruction data. The full cleanup will happen as part |
1814 | | * of decoding ABORT record of this transaction. |
1815 | | */ |
1816 | 0 | ReorderBufferTruncateTXN(rb, txn, rbtxn_is_prepared(txn)); |
1817 | 0 | ReorderBufferToastReset(rb, txn); |
1818 | | |
1819 | | /* All changes should be discarded */ |
1820 | 0 | Assert(txn->size == 0); |
1821 | | |
1822 | | /* |
1823 | | * Mark the transaction as aborted so we can ignore future changes of this |
1824 | | * transaction. |
1825 | | */ |
1826 | 0 | Assert(!rbtxn_is_committed(txn)); |
1827 | 0 | txn->txn_flags |= RBTXN_IS_ABORTED; |
1828 | |
|
1829 | 0 | return true; |
1830 | 0 | } |
1831 | | |
1832 | | /* |
1833 | | * Build a hash with a (relfilelocator, ctid) -> (cmin, cmax) mapping for use by |
1834 | | * HeapTupleSatisfiesHistoricMVCC. |
1835 | | */ |
1836 | | static void |
1837 | | ReorderBufferBuildTupleCidHash(ReorderBuffer *rb, ReorderBufferTXN *txn) |
1838 | 0 | { |
1839 | 0 | dlist_iter iter; |
1840 | 0 | HASHCTL hash_ctl; |
1841 | |
|
1842 | 0 | if (!rbtxn_has_catalog_changes(txn) || dlist_is_empty(&txn->tuplecids)) |
1843 | 0 | return; |
1844 | | |
1845 | 0 | hash_ctl.keysize = sizeof(ReorderBufferTupleCidKey); |
1846 | 0 | hash_ctl.entrysize = sizeof(ReorderBufferTupleCidEnt); |
1847 | 0 | hash_ctl.hcxt = rb->context; |
1848 | | |
1849 | | /* |
1850 | | * create the hash with the exact number of to-be-stored tuplecids from |
1851 | | * the start |
1852 | | */ |
1853 | 0 | txn->tuplecid_hash = |
1854 | 0 | hash_create("ReorderBufferTupleCid", txn->ntuplecids, &hash_ctl, |
1855 | 0 | HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); |
1856 | |
|
1857 | 0 | dlist_foreach(iter, &txn->tuplecids) |
1858 | 0 | { |
1859 | 0 | ReorderBufferTupleCidKey key; |
1860 | 0 | ReorderBufferTupleCidEnt *ent; |
1861 | 0 | bool found; |
1862 | 0 | ReorderBufferChange *change; |
1863 | |
|
1864 | 0 | change = dlist_container(ReorderBufferChange, node, iter.cur); |
1865 | |
|
1866 | 0 | Assert(change->action == REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID); |
1867 | | |
1868 | | /* be careful about padding */ |
1869 | 0 | memset(&key, 0, sizeof(ReorderBufferTupleCidKey)); |
1870 | |
|
1871 | 0 | key.rlocator = change->data.tuplecid.locator; |
1872 | |
|
1873 | 0 | ItemPointerCopy(&change->data.tuplecid.tid, |
1874 | 0 | &key.tid); |
1875 | |
|
1876 | 0 | ent = (ReorderBufferTupleCidEnt *) |
1877 | 0 | hash_search(txn->tuplecid_hash, &key, HASH_ENTER, &found); |
1878 | 0 | if (!found) |
1879 | 0 | { |
1880 | 0 | ent->cmin = change->data.tuplecid.cmin; |
1881 | 0 | ent->cmax = change->data.tuplecid.cmax; |
1882 | 0 | ent->combocid = change->data.tuplecid.combocid; |
1883 | 0 | } |
1884 | 0 | else |
1885 | 0 | { |
1886 | | /* |
1887 | | * Maybe we already saw this tuple before in this transaction, but |
1888 | | * if so it must have the same cmin. |
1889 | | */ |
1890 | 0 | Assert(ent->cmin == change->data.tuplecid.cmin); |
1891 | | |
1892 | | /* |
1893 | | * cmax may be initially invalid, but once set it can only grow, |
1894 | | * and never become invalid again. |
1895 | | */ |
1896 | 0 | Assert((ent->cmax == InvalidCommandId) || |
1897 | 0 | ((change->data.tuplecid.cmax != InvalidCommandId) && |
1898 | 0 | (change->data.tuplecid.cmax > ent->cmax))); |
1899 | 0 | ent->cmax = change->data.tuplecid.cmax; |
1900 | 0 | } |
1901 | 0 | } |
1902 | 0 | } |
1903 | | |
1904 | | /* |
1905 | | * Copy a provided snapshot so we can modify it privately. This is needed so |
1906 | | * that catalog modifying transactions can look into intermediate catalog |
1907 | | * states. |
1908 | | */ |
1909 | | static Snapshot |
1910 | | ReorderBufferCopySnap(ReorderBuffer *rb, Snapshot orig_snap, |
1911 | | ReorderBufferTXN *txn, CommandId cid) |
1912 | 0 | { |
1913 | 0 | Snapshot snap; |
1914 | 0 | dlist_iter iter; |
1915 | 0 | int i = 0; |
1916 | 0 | Size size; |
1917 | |
|
1918 | 0 | size = sizeof(SnapshotData) + |
1919 | 0 | sizeof(TransactionId) * orig_snap->xcnt + |
1920 | 0 | sizeof(TransactionId) * (txn->nsubtxns + 1); |
1921 | |
|
1922 | 0 | snap = MemoryContextAllocZero(rb->context, size); |
1923 | 0 | memcpy(snap, orig_snap, sizeof(SnapshotData)); |
1924 | |
|
1925 | 0 | snap->copied = true; |
1926 | 0 | snap->active_count = 1; /* mark as active so nobody frees it */ |
1927 | 0 | snap->regd_count = 0; |
1928 | 0 | snap->xip = (TransactionId *) (snap + 1); |
1929 | |
|
1930 | 0 | memcpy(snap->xip, orig_snap->xip, sizeof(TransactionId) * snap->xcnt); |
1931 | | |
1932 | | /* |
1933 | | * snap->subxip contains all txids that belong to our transaction which we |
1934 | | * need to check via cmin/cmax. That's why we store the toplevel |
1935 | | * transaction in there as well. |
1936 | | */ |
1937 | 0 | snap->subxip = snap->xip + snap->xcnt; |
1938 | 0 | snap->subxip[i++] = txn->xid; |
1939 | | |
1940 | | /* |
1941 | | * txn->nsubtxns isn't decreased when subtransactions abort, so count |
1942 | | * manually. Since it's an upper boundary it is safe to use it for the |
1943 | | * allocation above. |
1944 | | */ |
1945 | 0 | snap->subxcnt = 1; |
1946 | |
|
1947 | 0 | dlist_foreach(iter, &txn->subtxns) |
1948 | 0 | { |
1949 | 0 | ReorderBufferTXN *sub_txn; |
1950 | |
|
1951 | 0 | sub_txn = dlist_container(ReorderBufferTXN, node, iter.cur); |
1952 | 0 | snap->subxip[i++] = sub_txn->xid; |
1953 | 0 | snap->subxcnt++; |
1954 | 0 | } |
1955 | | |
1956 | | /* sort so we can bsearch() later */ |
1957 | 0 | qsort(snap->subxip, snap->subxcnt, sizeof(TransactionId), xidComparator); |
1958 | | |
1959 | | /* store the specified current CommandId */ |
1960 | 0 | snap->curcid = cid; |
1961 | |
|
1962 | 0 | return snap; |
1963 | 0 | } |
1964 | | |
1965 | | /* |
1966 | | * Free a previously ReorderBufferCopySnap'ed snapshot |
1967 | | */ |
1968 | | static void |
1969 | | ReorderBufferFreeSnap(ReorderBuffer *rb, Snapshot snap) |
1970 | 0 | { |
1971 | 0 | if (snap->copied) |
1972 | 0 | pfree(snap); |
1973 | 0 | else |
1974 | 0 | SnapBuildSnapDecRefcount(snap); |
1975 | 0 | } |
1976 | | |
1977 | | /* |
1978 | | * If the transaction was (partially) streamed, we need to prepare or commit |
1979 | | * it in a 'streamed' way. That is, we first stream the remaining part of the |
1980 | | * transaction, and then invoke stream_prepare or stream_commit message as per |
1981 | | * the case. |
1982 | | */ |
1983 | | static void |
1984 | | ReorderBufferStreamCommit(ReorderBuffer *rb, ReorderBufferTXN *txn) |
1985 | 0 | { |
1986 | | /* we should only call this for previously streamed transactions */ |
1987 | 0 | Assert(rbtxn_is_streamed(txn)); |
1988 | |
|
1989 | 0 | ReorderBufferStreamTXN(rb, txn); |
1990 | |
|
1991 | 0 | if (rbtxn_is_prepared(txn)) |
1992 | 0 | { |
1993 | | /* |
1994 | | * Note, we send stream prepare even if a concurrent abort is |
1995 | | * detected. See DecodePrepare for more information. |
1996 | | */ |
1997 | 0 | Assert(!rbtxn_sent_prepare(txn)); |
1998 | 0 | rb->stream_prepare(rb, txn, txn->final_lsn); |
1999 | 0 | txn->txn_flags |= RBTXN_SENT_PREPARE; |
2000 | | |
2001 | | /* |
2002 | | * This is a PREPARED transaction, part of a two-phase commit. The |
2003 | | * full cleanup will happen as part of the COMMIT PREPAREDs, so now |
2004 | | * just truncate txn by removing changes and tuplecids. |
2005 | | */ |
2006 | 0 | ReorderBufferTruncateTXN(rb, txn, true); |
2007 | | /* Reset the CheckXidAlive */ |
2008 | 0 | CheckXidAlive = InvalidTransactionId; |
2009 | 0 | } |
2010 | 0 | else |
2011 | 0 | { |
2012 | 0 | rb->stream_commit(rb, txn, txn->final_lsn); |
2013 | 0 | ReorderBufferCleanupTXN(rb, txn); |
2014 | 0 | } |
2015 | 0 | } |
2016 | | |
2017 | | /* |
2018 | | * Set xid to detect concurrent aborts. |
2019 | | * |
2020 | | * While streaming an in-progress transaction or decoding a prepared |
2021 | | * transaction there is a possibility that the (sub)transaction might get |
2022 | | * aborted concurrently. In such case if the (sub)transaction has catalog |
2023 | | * update then we might decode the tuple using wrong catalog version. For |
2024 | | * example, suppose there is one catalog tuple with (xmin: 500, xmax: 0). Now, |
2025 | | * the transaction 501 updates the catalog tuple and after that we will have |
2026 | | * two tuples (xmin: 500, xmax: 501) and (xmin: 501, xmax: 0). Now, if 501 is |
2027 | | * aborted and some other transaction say 502 updates the same catalog tuple |
2028 | | * then the first tuple will be changed to (xmin: 500, xmax: 502). So, the |
2029 | | * problem is that when we try to decode the tuple inserted/updated in 501 |
2030 | | * after the catalog update, we will see the catalog tuple with (xmin: 500, |
2031 | | * xmax: 502) as visible because it will consider that the tuple is deleted by |
2032 | | * xid 502 which is not visible to our snapshot. And when we will try to |
2033 | | * decode with that catalog tuple, it can lead to a wrong result or a crash. |
2034 | | * So, it is necessary to detect concurrent aborts to allow streaming of |
2035 | | * in-progress transactions or decoding of prepared transactions. |
2036 | | * |
2037 | | * For detecting the concurrent abort we set CheckXidAlive to the current |
2038 | | * (sub)transaction's xid for which this change belongs to. And, during |
2039 | | * catalog scan we can check the status of the xid and if it is aborted we will |
2040 | | * report a specific error so that we can stop streaming current transaction |
2041 | | * and discard the already streamed changes on such an error. We might have |
2042 | | * already streamed some of the changes for the aborted (sub)transaction, but |
2043 | | * that is fine because when we decode the abort we will stream abort message |
2044 | | * to truncate the changes in the subscriber. Similarly, for prepared |
2045 | | * transactions, we stop decoding if concurrent abort is detected and then |
2046 | | * rollback the changes when rollback prepared is encountered. See |
2047 | | * DecodePrepare. |
2048 | | */ |
2049 | | static inline void |
2050 | | SetupCheckXidLive(TransactionId xid) |
2051 | 0 | { |
2052 | | /* |
2053 | | * If the input transaction id is already set as a CheckXidAlive then |
2054 | | * nothing to do. |
2055 | | */ |
2056 | 0 | if (TransactionIdEquals(CheckXidAlive, xid)) |
2057 | 0 | return; |
2058 | | |
2059 | | /* |
2060 | | * setup CheckXidAlive if it's not committed yet. We don't check if the |
2061 | | * xid is aborted. That will happen during catalog access. |
2062 | | */ |
2063 | 0 | if (!TransactionIdDidCommit(xid)) |
2064 | 0 | CheckXidAlive = xid; |
2065 | 0 | else |
2066 | 0 | CheckXidAlive = InvalidTransactionId; |
2067 | 0 | } |
2068 | | |
2069 | | /* |
2070 | | * Helper function for ReorderBufferProcessTXN for applying change. |
2071 | | */ |
2072 | | static inline void |
2073 | | ReorderBufferApplyChange(ReorderBuffer *rb, ReorderBufferTXN *txn, |
2074 | | Relation relation, ReorderBufferChange *change, |
2075 | | bool streaming) |
2076 | 0 | { |
2077 | 0 | if (streaming) |
2078 | 0 | rb->stream_change(rb, txn, relation, change); |
2079 | 0 | else |
2080 | 0 | rb->apply_change(rb, txn, relation, change); |
2081 | 0 | } |
2082 | | |
2083 | | /* |
2084 | | * Helper function for ReorderBufferProcessTXN for applying the truncate. |
2085 | | */ |
2086 | | static inline void |
2087 | | ReorderBufferApplyTruncate(ReorderBuffer *rb, ReorderBufferTXN *txn, |
2088 | | int nrelations, Relation *relations, |
2089 | | ReorderBufferChange *change, bool streaming) |
2090 | 0 | { |
2091 | 0 | if (streaming) |
2092 | 0 | rb->stream_truncate(rb, txn, nrelations, relations, change); |
2093 | 0 | else |
2094 | 0 | rb->apply_truncate(rb, txn, nrelations, relations, change); |
2095 | 0 | } |
2096 | | |
2097 | | /* |
2098 | | * Helper function for ReorderBufferProcessTXN for applying the message. |
2099 | | */ |
2100 | | static inline void |
2101 | | ReorderBufferApplyMessage(ReorderBuffer *rb, ReorderBufferTXN *txn, |
2102 | | ReorderBufferChange *change, bool streaming) |
2103 | 0 | { |
2104 | 0 | if (streaming) |
2105 | 0 | rb->stream_message(rb, txn, change->lsn, true, |
2106 | 0 | change->data.msg.prefix, |
2107 | 0 | change->data.msg.message_size, |
2108 | 0 | change->data.msg.message); |
2109 | 0 | else |
2110 | 0 | rb->message(rb, txn, change->lsn, true, |
2111 | 0 | change->data.msg.prefix, |
2112 | 0 | change->data.msg.message_size, |
2113 | 0 | change->data.msg.message); |
2114 | 0 | } |
2115 | | |
2116 | | /* |
2117 | | * Function to store the command id and snapshot at the end of the current |
2118 | | * stream so that we can reuse the same while sending the next stream. |
2119 | | */ |
2120 | | static inline void |
2121 | | ReorderBufferSaveTXNSnapshot(ReorderBuffer *rb, ReorderBufferTXN *txn, |
2122 | | Snapshot snapshot_now, CommandId command_id) |
2123 | 0 | { |
2124 | 0 | txn->command_id = command_id; |
2125 | | |
2126 | | /* Avoid copying if it's already copied. */ |
2127 | 0 | if (snapshot_now->copied) |
2128 | 0 | txn->snapshot_now = snapshot_now; |
2129 | 0 | else |
2130 | 0 | txn->snapshot_now = ReorderBufferCopySnap(rb, snapshot_now, |
2131 | 0 | txn, command_id); |
2132 | 0 | } |
2133 | | |
2134 | | /* |
2135 | | * Mark the given transaction as streamed if it's a top-level transaction |
2136 | | * or has changes. |
2137 | | */ |
2138 | | static void |
2139 | | ReorderBufferMaybeMarkTXNStreamed(ReorderBuffer *rb, ReorderBufferTXN *txn) |
2140 | 0 | { |
2141 | | /* |
2142 | | * The top-level transaction, is marked as streamed always, even if it |
2143 | | * does not contain any changes (that is, when all the changes are in |
2144 | | * subtransactions). |
2145 | | * |
2146 | | * For subtransactions, we only mark them as streamed when there are |
2147 | | * changes in them. |
2148 | | * |
2149 | | * We do it this way because of aborts - we don't want to send aborts for |
2150 | | * XIDs the downstream is not aware of. And of course, it always knows |
2151 | | * about the top-level xact (we send the XID in all messages), but we |
2152 | | * never stream XIDs of empty subxacts. |
2153 | | */ |
2154 | 0 | if (rbtxn_is_toptxn(txn) || (txn->nentries_mem != 0)) |
2155 | 0 | txn->txn_flags |= RBTXN_IS_STREAMED; |
2156 | 0 | } |
2157 | | |
2158 | | /* |
2159 | | * Helper function for ReorderBufferProcessTXN to handle the concurrent |
2160 | | * abort of the streaming transaction. This resets the TXN such that it |
2161 | | * can be used to stream the remaining data of transaction being processed. |
2162 | | * This can happen when the subtransaction is aborted and we still want to |
2163 | | * continue processing the main or other subtransactions data. |
2164 | | */ |
2165 | | static void |
2166 | | ReorderBufferResetTXN(ReorderBuffer *rb, ReorderBufferTXN *txn, |
2167 | | Snapshot snapshot_now, |
2168 | | CommandId command_id, |
2169 | | XLogRecPtr last_lsn) |
2170 | 0 | { |
2171 | | /* Discard the changes that we just streamed */ |
2172 | 0 | ReorderBufferTruncateTXN(rb, txn, rbtxn_is_prepared(txn)); |
2173 | | |
2174 | | /* Free all resources allocated for toast reconstruction */ |
2175 | 0 | ReorderBufferToastReset(rb, txn); |
2176 | | |
2177 | | /* |
2178 | | * For the streaming case, stop the stream and remember the command ID and |
2179 | | * snapshot for the streaming run. |
2180 | | */ |
2181 | 0 | if (rbtxn_is_streamed(txn)) |
2182 | 0 | { |
2183 | 0 | rb->stream_stop(rb, txn, last_lsn); |
2184 | 0 | ReorderBufferSaveTXNSnapshot(rb, txn, snapshot_now, command_id); |
2185 | 0 | } |
2186 | | |
2187 | | /* All changes must be deallocated */ |
2188 | 0 | Assert(txn->size == 0); |
2189 | 0 | } |
2190 | | |
2191 | | /* |
2192 | | * Helper function for ReorderBufferReplay and ReorderBufferStreamTXN. |
2193 | | * |
2194 | | * Send data of a transaction (and its subtransactions) to the |
2195 | | * output plugin. We iterate over the top and subtransactions (using a k-way |
2196 | | * merge) and replay the changes in lsn order. |
2197 | | * |
2198 | | * If streaming is true then data will be sent using stream API. |
2199 | | * |
2200 | | * Note: "volatile" markers on some parameters are to avoid trouble with |
2201 | | * PG_TRY inside the function. |
2202 | | */ |
2203 | | static void |
2204 | | ReorderBufferProcessTXN(ReorderBuffer *rb, ReorderBufferTXN *txn, |
2205 | | XLogRecPtr commit_lsn, |
2206 | | volatile Snapshot snapshot_now, |
2207 | | volatile CommandId command_id, |
2208 | | bool streaming) |
2209 | 0 | { |
2210 | 0 | bool using_subtxn; |
2211 | 0 | MemoryContext ccxt = CurrentMemoryContext; |
2212 | 0 | ResourceOwner cowner = CurrentResourceOwner; |
2213 | 0 | ReorderBufferIterTXNState *volatile iterstate = NULL; |
2214 | 0 | volatile XLogRecPtr prev_lsn = InvalidXLogRecPtr; |
2215 | 0 | ReorderBufferChange *volatile specinsert = NULL; |
2216 | 0 | volatile bool stream_started = false; |
2217 | 0 | ReorderBufferTXN *volatile curtxn = NULL; |
2218 | | |
2219 | | /* build data to be able to lookup the CommandIds of catalog tuples */ |
2220 | 0 | ReorderBufferBuildTupleCidHash(rb, txn); |
2221 | | |
2222 | | /* setup the initial snapshot */ |
2223 | 0 | SetupHistoricSnapshot(snapshot_now, txn->tuplecid_hash); |
2224 | | |
2225 | | /* |
2226 | | * Decoding needs access to syscaches et al., which in turn use |
2227 | | * heavyweight locks and such. Thus we need to have enough state around to |
2228 | | * keep track of those. The easiest way is to simply use a transaction |
2229 | | * internally. That also allows us to easily enforce that nothing writes |
2230 | | * to the database by checking for xid assignments. |
2231 | | * |
2232 | | * When we're called via the SQL SRF there's already a transaction |
2233 | | * started, so start an explicit subtransaction there. |
2234 | | */ |
2235 | 0 | using_subtxn = IsTransactionOrTransactionBlock(); |
2236 | |
|
2237 | 0 | PG_TRY(); |
2238 | 0 | { |
2239 | 0 | ReorderBufferChange *change; |
2240 | 0 | int changes_count = 0; /* used to accumulate the number of |
2241 | | * changes */ |
2242 | |
|
2243 | 0 | if (using_subtxn) |
2244 | 0 | BeginInternalSubTransaction(streaming ? "stream" : "replay"); |
2245 | 0 | else |
2246 | 0 | StartTransactionCommand(); |
2247 | | |
2248 | | /* |
2249 | | * We only need to send begin/begin-prepare for non-streamed |
2250 | | * transactions. |
2251 | | */ |
2252 | 0 | if (!streaming) |
2253 | 0 | { |
2254 | 0 | if (rbtxn_is_prepared(txn)) |
2255 | 0 | rb->begin_prepare(rb, txn); |
2256 | 0 | else |
2257 | 0 | rb->begin(rb, txn); |
2258 | 0 | } |
2259 | |
|
2260 | 0 | ReorderBufferIterTXNInit(rb, txn, &iterstate); |
2261 | 0 | while ((change = ReorderBufferIterTXNNext(rb, iterstate)) != NULL) |
2262 | 0 | { |
2263 | 0 | Relation relation = NULL; |
2264 | 0 | Oid reloid; |
2265 | |
|
2266 | 0 | CHECK_FOR_INTERRUPTS(); |
2267 | | |
2268 | | /* |
2269 | | * We can't call start stream callback before processing first |
2270 | | * change. |
2271 | | */ |
2272 | 0 | if (!XLogRecPtrIsValid(prev_lsn)) |
2273 | 0 | { |
2274 | 0 | if (streaming) |
2275 | 0 | { |
2276 | 0 | txn->origin_id = change->origin_id; |
2277 | 0 | rb->stream_start(rb, txn, change->lsn); |
2278 | 0 | stream_started = true; |
2279 | 0 | } |
2280 | 0 | } |
2281 | | |
2282 | | /* |
2283 | | * Enforce correct ordering of changes, merged from multiple |
2284 | | * subtransactions. The changes may have the same LSN due to |
2285 | | * MULTI_INSERT xlog records. |
2286 | | */ |
2287 | 0 | Assert(!XLogRecPtrIsValid(prev_lsn) || prev_lsn <= change->lsn); |
2288 | |
|
2289 | 0 | prev_lsn = change->lsn; |
2290 | | |
2291 | | /* |
2292 | | * Set the current xid to detect concurrent aborts. This is |
2293 | | * required for the cases when we decode the changes before the |
2294 | | * COMMIT record is processed. |
2295 | | */ |
2296 | 0 | if (streaming || rbtxn_is_prepared(change->txn)) |
2297 | 0 | { |
2298 | 0 | curtxn = change->txn; |
2299 | 0 | SetupCheckXidLive(curtxn->xid); |
2300 | 0 | } |
2301 | |
|
2302 | 0 | switch (change->action) |
2303 | 0 | { |
2304 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM: |
2305 | | |
2306 | | /* |
2307 | | * Confirmation for speculative insertion arrived. Simply |
2308 | | * use as a normal record. It'll be cleaned up at the end |
2309 | | * of INSERT processing. |
2310 | | */ |
2311 | 0 | if (specinsert == NULL) |
2312 | 0 | elog(ERROR, "invalid ordering of speculative insertion changes"); |
2313 | 0 | Assert(specinsert->data.tp.oldtuple == NULL); |
2314 | 0 | change = specinsert; |
2315 | 0 | change->action = REORDER_BUFFER_CHANGE_INSERT; |
2316 | | |
2317 | | /* intentionally fall through */ |
2318 | 0 | pg_fallthrough; |
2319 | 0 | case REORDER_BUFFER_CHANGE_INSERT: |
2320 | 0 | case REORDER_BUFFER_CHANGE_UPDATE: |
2321 | 0 | case REORDER_BUFFER_CHANGE_DELETE: |
2322 | 0 | Assert(snapshot_now); |
2323 | |
|
2324 | 0 | reloid = RelidByRelfilenumber(change->data.tp.rlocator.spcOid, |
2325 | 0 | change->data.tp.rlocator.relNumber); |
2326 | | |
2327 | | /* |
2328 | | * Mapped catalog tuple without data, emitted while |
2329 | | * catalog table was in the process of being rewritten. We |
2330 | | * can fail to look up the relfilenumber, because the |
2331 | | * relmapper has no "historic" view, in contrast to the |
2332 | | * normal catalog during decoding. Thus repeated rewrites |
2333 | | * can cause a lookup failure. That's OK because we do not |
2334 | | * decode catalog changes anyway. Normally such tuples |
2335 | | * would be skipped over below, but we can't identify |
2336 | | * whether the table should be logically logged without |
2337 | | * mapping the relfilenumber to the oid. |
2338 | | */ |
2339 | 0 | if (reloid == InvalidOid && |
2340 | 0 | change->data.tp.newtuple == NULL && |
2341 | 0 | change->data.tp.oldtuple == NULL) |
2342 | 0 | goto change_done; |
2343 | 0 | else if (reloid == InvalidOid) |
2344 | 0 | elog(ERROR, "could not map filenumber \"%s\" to relation OID", |
2345 | 0 | relpathperm(change->data.tp.rlocator, |
2346 | 0 | MAIN_FORKNUM).str); |
2347 | | |
2348 | 0 | relation = RelationIdGetRelation(reloid); |
2349 | |
|
2350 | 0 | if (!RelationIsValid(relation)) |
2351 | 0 | elog(ERROR, "could not open relation with OID %u (for filenumber \"%s\")", |
2352 | 0 | reloid, |
2353 | 0 | relpathperm(change->data.tp.rlocator, |
2354 | 0 | MAIN_FORKNUM).str); |
2355 | | |
2356 | 0 | if (!RelationIsLogicallyLogged(relation)) |
2357 | 0 | goto change_done; |
2358 | | |
2359 | | /* |
2360 | | * Ignore temporary heaps created during DDL unless the |
2361 | | * plugin has asked for them. |
2362 | | */ |
2363 | 0 | if (relation->rd_rel->relrewrite && !rb->output_rewrites) |
2364 | 0 | goto change_done; |
2365 | | |
2366 | | /* |
2367 | | * For now ignore sequence changes entirely. Most of the |
2368 | | * time they don't log changes using records we |
2369 | | * understand, so it doesn't make sense to handle the few |
2370 | | * cases we do. |
2371 | | */ |
2372 | 0 | if (relation->rd_rel->relkind == RELKIND_SEQUENCE) |
2373 | 0 | goto change_done; |
2374 | | |
2375 | | /* user-triggered change */ |
2376 | 0 | if (!IsToastRelation(relation)) |
2377 | 0 | { |
2378 | 0 | ReorderBufferToastReplace(rb, txn, relation, change); |
2379 | 0 | ReorderBufferApplyChange(rb, txn, relation, change, |
2380 | 0 | streaming); |
2381 | | |
2382 | | /* |
2383 | | * Only clear reassembled toast chunks if we're sure |
2384 | | * they're not required anymore. The creator of the |
2385 | | * tuple tells us. |
2386 | | */ |
2387 | 0 | if (change->data.tp.clear_toast_afterwards) |
2388 | 0 | ReorderBufferToastReset(rb, txn); |
2389 | 0 | } |
2390 | | /* we're not interested in toast deletions */ |
2391 | 0 | else if (change->action == REORDER_BUFFER_CHANGE_INSERT) |
2392 | 0 | { |
2393 | | /* |
2394 | | * Need to reassemble the full toasted Datum in |
2395 | | * memory, to ensure the chunks don't get reused till |
2396 | | * we're done remove it from the list of this |
2397 | | * transaction's changes. Otherwise it will get |
2398 | | * freed/reused while restoring spooled data from |
2399 | | * disk. |
2400 | | */ |
2401 | 0 | Assert(change->data.tp.newtuple != NULL); |
2402 | |
|
2403 | 0 | dlist_delete(&change->node); |
2404 | 0 | ReorderBufferToastAppendChunk(rb, txn, relation, |
2405 | 0 | change); |
2406 | 0 | } |
2407 | |
|
2408 | 0 | change_done: |
2409 | | |
2410 | | /* |
2411 | | * If speculative insertion was confirmed, the record |
2412 | | * isn't needed anymore. |
2413 | | */ |
2414 | 0 | if (specinsert != NULL) |
2415 | 0 | { |
2416 | 0 | ReorderBufferFreeChange(rb, specinsert, true); |
2417 | 0 | specinsert = NULL; |
2418 | 0 | } |
2419 | |
|
2420 | 0 | if (RelationIsValid(relation)) |
2421 | 0 | { |
2422 | 0 | RelationClose(relation); |
2423 | 0 | relation = NULL; |
2424 | 0 | } |
2425 | 0 | break; |
2426 | | |
2427 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT: |
2428 | | |
2429 | | /* |
2430 | | * Speculative insertions are dealt with by delaying the |
2431 | | * processing of the insert until the confirmation record |
2432 | | * arrives. For that we simply unlink the record from the |
2433 | | * chain, so it does not get freed/reused while restoring |
2434 | | * spooled data from disk. |
2435 | | * |
2436 | | * This is safe in the face of concurrent catalog changes |
2437 | | * because the relevant relation can't be changed between |
2438 | | * speculative insertion and confirmation due to |
2439 | | * CheckTableNotInUse() and locking. |
2440 | | */ |
2441 | | |
2442 | | /* Previous speculative insertion must be aborted */ |
2443 | 0 | Assert(specinsert == NULL); |
2444 | | |
2445 | | /* and memorize the pending insertion */ |
2446 | 0 | dlist_delete(&change->node); |
2447 | 0 | specinsert = change; |
2448 | 0 | break; |
2449 | | |
2450 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_ABORT: |
2451 | | |
2452 | | /* |
2453 | | * Abort for speculative insertion arrived. So cleanup the |
2454 | | * specinsert tuple and toast hash. |
2455 | | * |
2456 | | * Note that we get the spec abort change for each toast |
2457 | | * entry but we need to perform the cleanup only the first |
2458 | | * time we get it for the main table. |
2459 | | */ |
2460 | 0 | if (specinsert != NULL) |
2461 | 0 | { |
2462 | | /* |
2463 | | * We must clean the toast hash before processing a |
2464 | | * completely new tuple to avoid confusion about the |
2465 | | * previous tuple's toast chunks. |
2466 | | */ |
2467 | 0 | Assert(change->data.tp.clear_toast_afterwards); |
2468 | 0 | ReorderBufferToastReset(rb, txn); |
2469 | | |
2470 | | /* We don't need this record anymore. */ |
2471 | 0 | ReorderBufferFreeChange(rb, specinsert, true); |
2472 | 0 | specinsert = NULL; |
2473 | 0 | } |
2474 | 0 | break; |
2475 | | |
2476 | 0 | case REORDER_BUFFER_CHANGE_TRUNCATE: |
2477 | 0 | { |
2478 | 0 | int i; |
2479 | 0 | int nrelids = change->data.truncate.nrelids; |
2480 | 0 | int nrelations = 0; |
2481 | 0 | Relation *relations; |
2482 | |
|
2483 | 0 | relations = palloc0_array(Relation, nrelids); |
2484 | 0 | for (i = 0; i < nrelids; i++) |
2485 | 0 | { |
2486 | 0 | Oid relid = change->data.truncate.relids[i]; |
2487 | 0 | Relation rel; |
2488 | |
|
2489 | 0 | rel = RelationIdGetRelation(relid); |
2490 | |
|
2491 | 0 | if (!RelationIsValid(rel)) |
2492 | 0 | elog(ERROR, "could not open relation with OID %u", relid); |
2493 | | |
2494 | 0 | if (!RelationIsLogicallyLogged(rel)) |
2495 | 0 | continue; |
2496 | | |
2497 | 0 | relations[nrelations++] = rel; |
2498 | 0 | } |
2499 | | |
2500 | | /* Apply the truncate. */ |
2501 | 0 | ReorderBufferApplyTruncate(rb, txn, nrelations, |
2502 | 0 | relations, change, |
2503 | 0 | streaming); |
2504 | |
|
2505 | 0 | for (i = 0; i < nrelations; i++) |
2506 | 0 | RelationClose(relations[i]); |
2507 | |
|
2508 | 0 | break; |
2509 | 0 | } |
2510 | | |
2511 | 0 | case REORDER_BUFFER_CHANGE_MESSAGE: |
2512 | 0 | ReorderBufferApplyMessage(rb, txn, change, streaming); |
2513 | 0 | break; |
2514 | | |
2515 | 0 | case REORDER_BUFFER_CHANGE_INVALIDATION: |
2516 | | /* Execute the invalidation messages locally */ |
2517 | 0 | ReorderBufferExecuteInvalidations(change->data.inval.ninvalidations, |
2518 | 0 | change->data.inval.invalidations); |
2519 | 0 | break; |
2520 | | |
2521 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT: |
2522 | | /* get rid of the old */ |
2523 | 0 | TeardownHistoricSnapshot(false); |
2524 | |
|
2525 | 0 | if (snapshot_now->copied) |
2526 | 0 | { |
2527 | 0 | ReorderBufferFreeSnap(rb, snapshot_now); |
2528 | 0 | snapshot_now = |
2529 | 0 | ReorderBufferCopySnap(rb, change->data.snapshot, |
2530 | 0 | txn, command_id); |
2531 | 0 | } |
2532 | | |
2533 | | /* |
2534 | | * Restored from disk, need to be careful not to double |
2535 | | * free. We could introduce refcounting for that, but for |
2536 | | * now this seems infrequent enough not to care. |
2537 | | */ |
2538 | 0 | else if (change->data.snapshot->copied) |
2539 | 0 | { |
2540 | 0 | snapshot_now = |
2541 | 0 | ReorderBufferCopySnap(rb, change->data.snapshot, |
2542 | 0 | txn, command_id); |
2543 | 0 | } |
2544 | 0 | else |
2545 | 0 | { |
2546 | 0 | snapshot_now = change->data.snapshot; |
2547 | 0 | } |
2548 | | |
2549 | | /* and continue with the new one */ |
2550 | 0 | SetupHistoricSnapshot(snapshot_now, txn->tuplecid_hash); |
2551 | 0 | break; |
2552 | | |
2553 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID: |
2554 | 0 | Assert(change->data.command_id != InvalidCommandId); |
2555 | |
|
2556 | 0 | if (command_id < change->data.command_id) |
2557 | 0 | { |
2558 | 0 | command_id = change->data.command_id; |
2559 | |
|
2560 | 0 | if (!snapshot_now->copied) |
2561 | 0 | { |
2562 | | /* we don't use the global one anymore */ |
2563 | 0 | snapshot_now = ReorderBufferCopySnap(rb, snapshot_now, |
2564 | 0 | txn, command_id); |
2565 | 0 | } |
2566 | |
|
2567 | 0 | snapshot_now->curcid = command_id; |
2568 | |
|
2569 | 0 | TeardownHistoricSnapshot(false); |
2570 | 0 | SetupHistoricSnapshot(snapshot_now, txn->tuplecid_hash); |
2571 | 0 | } |
2572 | |
|
2573 | 0 | break; |
2574 | | |
2575 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID: |
2576 | 0 | elog(ERROR, "tuplecid value in changequeue"); |
2577 | 0 | break; |
2578 | 0 | } |
2579 | | |
2580 | | /* |
2581 | | * It is possible that the data is not sent to downstream for a |
2582 | | * long time either because the output plugin filtered it or there |
2583 | | * is a DDL that generates a lot of data that is not processed by |
2584 | | * the plugin. So, in such cases, the downstream can timeout. To |
2585 | | * avoid that we try to send a keepalive message if required. |
2586 | | * Trying to send a keepalive message after every change has some |
2587 | | * overhead, but testing showed there is no noticeable overhead if |
2588 | | * we do it after every ~100 changes. |
2589 | | */ |
2590 | 0 | #define CHANGES_THRESHOLD 100 |
2591 | | |
2592 | 0 | if (++changes_count >= CHANGES_THRESHOLD) |
2593 | 0 | { |
2594 | 0 | rb->update_progress_txn(rb, txn, prev_lsn); |
2595 | 0 | changes_count = 0; |
2596 | 0 | } |
2597 | 0 | } |
2598 | | |
2599 | | /* speculative insertion record must be freed by now */ |
2600 | 0 | Assert(!specinsert); |
2601 | | |
2602 | | /* clean up the iterator */ |
2603 | 0 | ReorderBufferIterTXNFinish(rb, iterstate); |
2604 | 0 | iterstate = NULL; |
2605 | | |
2606 | | /* |
2607 | | * Update total transaction count and total bytes processed by the |
2608 | | * transaction and its subtransactions. Ensure to not count the |
2609 | | * streamed transaction multiple times. |
2610 | | * |
2611 | | * Note that the statistics computation has to be done after |
2612 | | * ReorderBufferIterTXNFinish as it releases the serialized change |
2613 | | * which we have already accounted in ReorderBufferIterTXNNext. |
2614 | | */ |
2615 | 0 | if (!rbtxn_is_streamed(txn)) |
2616 | 0 | rb->totalTxns++; |
2617 | |
|
2618 | 0 | rb->totalBytes += txn->total_size; |
2619 | | |
2620 | | /* |
2621 | | * Done with current changes, send the last message for this set of |
2622 | | * changes depending upon streaming mode. |
2623 | | */ |
2624 | 0 | if (streaming) |
2625 | 0 | { |
2626 | 0 | if (stream_started) |
2627 | 0 | { |
2628 | 0 | rb->stream_stop(rb, txn, prev_lsn); |
2629 | 0 | stream_started = false; |
2630 | 0 | } |
2631 | 0 | } |
2632 | 0 | else |
2633 | 0 | { |
2634 | | /* |
2635 | | * Call either PREPARE (for two-phase transactions) or COMMIT (for |
2636 | | * regular ones). |
2637 | | */ |
2638 | 0 | if (rbtxn_is_prepared(txn)) |
2639 | 0 | { |
2640 | 0 | Assert(!rbtxn_sent_prepare(txn)); |
2641 | 0 | rb->prepare(rb, txn, commit_lsn); |
2642 | 0 | txn->txn_flags |= RBTXN_SENT_PREPARE; |
2643 | 0 | } |
2644 | 0 | else |
2645 | 0 | rb->commit(rb, txn, commit_lsn); |
2646 | 0 | } |
2647 | | |
2648 | | /* this is just a sanity check against bad output plugin behaviour */ |
2649 | 0 | if (GetCurrentTransactionIdIfAny() != InvalidTransactionId) |
2650 | 0 | elog(ERROR, "output plugin used XID %u", |
2651 | 0 | GetCurrentTransactionId()); |
2652 | | |
2653 | | /* |
2654 | | * Remember the command ID and snapshot for the next set of changes in |
2655 | | * streaming mode. |
2656 | | */ |
2657 | 0 | if (streaming) |
2658 | 0 | ReorderBufferSaveTXNSnapshot(rb, txn, snapshot_now, command_id); |
2659 | 0 | else if (snapshot_now->copied) |
2660 | 0 | ReorderBufferFreeSnap(rb, snapshot_now); |
2661 | | |
2662 | | /* cleanup */ |
2663 | 0 | TeardownHistoricSnapshot(false); |
2664 | | |
2665 | | /* |
2666 | | * Aborting the current (sub-)transaction as a whole has the right |
2667 | | * semantics. We want all locks acquired in here to be released, not |
2668 | | * reassigned to the parent and we do not want any database access |
2669 | | * have persistent effects. |
2670 | | */ |
2671 | 0 | AbortCurrentTransaction(); |
2672 | | |
2673 | | /* make sure there's no cache pollution */ |
2674 | 0 | if (rbtxn_distr_inval_overflowed(txn)) |
2675 | 0 | { |
2676 | 0 | Assert(txn->ninvalidations_distributed == 0); |
2677 | 0 | InvalidateSystemCaches(); |
2678 | 0 | } |
2679 | 0 | else |
2680 | 0 | { |
2681 | 0 | ReorderBufferExecuteInvalidations(txn->ninvalidations, txn->invalidations); |
2682 | 0 | ReorderBufferExecuteInvalidations(txn->ninvalidations_distributed, |
2683 | 0 | txn->invalidations_distributed); |
2684 | 0 | } |
2685 | |
|
2686 | 0 | if (using_subtxn) |
2687 | 0 | { |
2688 | 0 | RollbackAndReleaseCurrentSubTransaction(); |
2689 | 0 | MemoryContextSwitchTo(ccxt); |
2690 | 0 | CurrentResourceOwner = cowner; |
2691 | 0 | } |
2692 | | |
2693 | | /* |
2694 | | * We are here due to one of the four reasons: 1. Decoding an |
2695 | | * in-progress txn. 2. Decoding a prepared txn. 3. Decoding of a |
2696 | | * prepared txn that was (partially) streamed. 4. Decoding a committed |
2697 | | * txn. |
2698 | | * |
2699 | | * For 1, we allow truncation of txn data by removing the changes |
2700 | | * already streamed but still keeping other things like invalidations, |
2701 | | * snapshot, and tuplecids. For 2 and 3, we indicate |
2702 | | * ReorderBufferTruncateTXN to do more elaborate truncation of txn |
2703 | | * data as the entire transaction has been decoded except for commit. |
2704 | | * For 4, as the entire txn has been decoded, we can fully clean up |
2705 | | * the TXN reorder buffer. |
2706 | | */ |
2707 | 0 | if (streaming || rbtxn_is_prepared(txn)) |
2708 | 0 | { |
2709 | 0 | if (streaming) |
2710 | 0 | ReorderBufferMaybeMarkTXNStreamed(rb, txn); |
2711 | |
|
2712 | 0 | ReorderBufferTruncateTXN(rb, txn, rbtxn_is_prepared(txn)); |
2713 | | /* Reset the CheckXidAlive */ |
2714 | 0 | CheckXidAlive = InvalidTransactionId; |
2715 | 0 | } |
2716 | 0 | else |
2717 | 0 | ReorderBufferCleanupTXN(rb, txn); |
2718 | 0 | } |
2719 | 0 | PG_CATCH(); |
2720 | 0 | { |
2721 | 0 | MemoryContext ecxt = MemoryContextSwitchTo(ccxt); |
2722 | 0 | ErrorData *errdata = CopyErrorData(); |
2723 | | |
2724 | | /* TODO: Encapsulate cleanup from the PG_TRY and PG_CATCH blocks */ |
2725 | 0 | if (iterstate) |
2726 | 0 | ReorderBufferIterTXNFinish(rb, iterstate); |
2727 | |
|
2728 | 0 | TeardownHistoricSnapshot(true); |
2729 | | |
2730 | | /* |
2731 | | * Force cache invalidation to happen outside of a valid transaction |
2732 | | * to prevent catalog access as we just caught an error. |
2733 | | */ |
2734 | 0 | AbortCurrentTransaction(); |
2735 | | |
2736 | | /* make sure there's no cache pollution */ |
2737 | 0 | if (rbtxn_distr_inval_overflowed(txn)) |
2738 | 0 | { |
2739 | 0 | Assert(txn->ninvalidations_distributed == 0); |
2740 | 0 | InvalidateSystemCaches(); |
2741 | 0 | } |
2742 | 0 | else |
2743 | 0 | { |
2744 | 0 | ReorderBufferExecuteInvalidations(txn->ninvalidations, txn->invalidations); |
2745 | 0 | ReorderBufferExecuteInvalidations(txn->ninvalidations_distributed, |
2746 | 0 | txn->invalidations_distributed); |
2747 | 0 | } |
2748 | |
|
2749 | 0 | if (using_subtxn) |
2750 | 0 | { |
2751 | 0 | RollbackAndReleaseCurrentSubTransaction(); |
2752 | 0 | MemoryContextSwitchTo(ccxt); |
2753 | 0 | CurrentResourceOwner = cowner; |
2754 | 0 | } |
2755 | | |
2756 | | /* Free the specinsert change before freeing the ReorderBufferTXN */ |
2757 | 0 | if (specinsert != NULL) |
2758 | 0 | { |
2759 | 0 | ReorderBufferFreeChange(rb, specinsert, true); |
2760 | 0 | specinsert = NULL; |
2761 | 0 | } |
2762 | | |
2763 | | /* |
2764 | | * The error code ERRCODE_TRANSACTION_ROLLBACK indicates a concurrent |
2765 | | * abort of the (sub)transaction we are streaming or preparing. We |
2766 | | * need to do the cleanup and return gracefully on this error, see |
2767 | | * SetupCheckXidLive. |
2768 | | * |
2769 | | * This error code can be thrown by one of the callbacks we call |
2770 | | * during decoding so we need to ensure that we return gracefully only |
2771 | | * when we are sending the data in streaming mode and the streaming is |
2772 | | * not finished yet or when we are sending the data out on a PREPARE |
2773 | | * during a two-phase commit. |
2774 | | */ |
2775 | 0 | if (errdata->sqlerrcode == ERRCODE_TRANSACTION_ROLLBACK && |
2776 | 0 | (stream_started || rbtxn_is_prepared(txn))) |
2777 | 0 | { |
2778 | | /* curtxn must be set for streaming or prepared transactions */ |
2779 | 0 | Assert(curtxn); |
2780 | | |
2781 | | /* Cleanup the temporary error state. */ |
2782 | 0 | FlushErrorState(); |
2783 | 0 | FreeErrorData(errdata); |
2784 | 0 | errdata = NULL; |
2785 | | |
2786 | | /* Remember the transaction is aborted. */ |
2787 | 0 | Assert(!rbtxn_is_committed(curtxn)); |
2788 | 0 | curtxn->txn_flags |= RBTXN_IS_ABORTED; |
2789 | | |
2790 | | /* Mark the transaction is streamed if appropriate */ |
2791 | 0 | if (stream_started) |
2792 | 0 | ReorderBufferMaybeMarkTXNStreamed(rb, txn); |
2793 | | |
2794 | | /* Reset the TXN so that it is allowed to stream remaining data. */ |
2795 | 0 | ReorderBufferResetTXN(rb, txn, snapshot_now, |
2796 | 0 | command_id, prev_lsn); |
2797 | 0 | } |
2798 | 0 | else |
2799 | 0 | { |
2800 | 0 | ReorderBufferCleanupTXN(rb, txn); |
2801 | 0 | MemoryContextSwitchTo(ecxt); |
2802 | 0 | PG_RE_THROW(); |
2803 | 0 | } |
2804 | 0 | } |
2805 | 0 | PG_END_TRY(); |
2806 | 0 | } |
2807 | | |
2808 | | /* |
2809 | | * Perform the replay of a transaction and its non-aborted subtransactions. |
2810 | | * |
2811 | | * Subtransactions previously have to be processed by |
2812 | | * ReorderBufferCommitChild(), even if previously assigned to the toplevel |
2813 | | * transaction with ReorderBufferAssignChild. |
2814 | | * |
2815 | | * This interface is called once a prepare or toplevel commit is read for both |
2816 | | * streamed as well as non-streamed transactions. |
2817 | | */ |
2818 | | static void |
2819 | | ReorderBufferReplay(ReorderBufferTXN *txn, |
2820 | | ReorderBuffer *rb, TransactionId xid, |
2821 | | XLogRecPtr commit_lsn, XLogRecPtr end_lsn, |
2822 | | TimestampTz commit_time, |
2823 | | ReplOriginId origin_id, XLogRecPtr origin_lsn) |
2824 | 0 | { |
2825 | 0 | Snapshot snapshot_now; |
2826 | 0 | CommandId command_id = FirstCommandId; |
2827 | |
|
2828 | 0 | txn->final_lsn = commit_lsn; |
2829 | 0 | txn->end_lsn = end_lsn; |
2830 | 0 | txn->commit_time = commit_time; |
2831 | 0 | txn->origin_id = origin_id; |
2832 | 0 | txn->origin_lsn = origin_lsn; |
2833 | | |
2834 | | /* |
2835 | | * If the transaction was (partially) streamed, we need to commit it in a |
2836 | | * 'streamed' way. That is, we first stream the remaining part of the |
2837 | | * transaction, and then invoke stream_commit message. |
2838 | | * |
2839 | | * Called after everything (origin ID, LSN, ...) is stored in the |
2840 | | * transaction to avoid passing that information directly. |
2841 | | */ |
2842 | 0 | if (rbtxn_is_streamed(txn)) |
2843 | 0 | { |
2844 | 0 | ReorderBufferStreamCommit(rb, txn); |
2845 | 0 | return; |
2846 | 0 | } |
2847 | | |
2848 | | /* |
2849 | | * If this transaction has no snapshot, it didn't make any changes to the |
2850 | | * database, so there's nothing to decode. Note that |
2851 | | * ReorderBufferCommitChild will have transferred any snapshots from |
2852 | | * subtransactions if there were any. |
2853 | | */ |
2854 | 0 | if (txn->base_snapshot == NULL) |
2855 | 0 | { |
2856 | 0 | Assert(txn->ninvalidations == 0); |
2857 | | |
2858 | | /* |
2859 | | * Removing this txn before a commit might result in the computation |
2860 | | * of an incorrect restart_lsn. See SnapBuildProcessRunningXacts. |
2861 | | */ |
2862 | 0 | if (!rbtxn_is_prepared(txn)) |
2863 | 0 | ReorderBufferCleanupTXN(rb, txn); |
2864 | 0 | return; |
2865 | 0 | } |
2866 | | |
2867 | 0 | snapshot_now = txn->base_snapshot; |
2868 | | |
2869 | | /* Process and send the changes to output plugin. */ |
2870 | 0 | ReorderBufferProcessTXN(rb, txn, commit_lsn, snapshot_now, |
2871 | 0 | command_id, false); |
2872 | 0 | } |
2873 | | |
2874 | | /* |
2875 | | * Commit a transaction. |
2876 | | * |
2877 | | * See comments for ReorderBufferReplay(). |
2878 | | */ |
2879 | | void |
2880 | | ReorderBufferCommit(ReorderBuffer *rb, TransactionId xid, |
2881 | | XLogRecPtr commit_lsn, XLogRecPtr end_lsn, |
2882 | | TimestampTz commit_time, |
2883 | | ReplOriginId origin_id, XLogRecPtr origin_lsn) |
2884 | 0 | { |
2885 | 0 | ReorderBufferTXN *txn; |
2886 | |
|
2887 | 0 | txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr, |
2888 | 0 | false); |
2889 | | |
2890 | | /* unknown transaction, nothing to replay */ |
2891 | 0 | if (txn == NULL) |
2892 | 0 | return; |
2893 | | |
2894 | 0 | ReorderBufferReplay(txn, rb, xid, commit_lsn, end_lsn, commit_time, |
2895 | 0 | origin_id, origin_lsn); |
2896 | 0 | } |
2897 | | |
2898 | | /* |
2899 | | * Record the prepare information for a transaction. Also, mark the transaction |
2900 | | * as a prepared transaction. |
2901 | | */ |
2902 | | bool |
2903 | | ReorderBufferRememberPrepareInfo(ReorderBuffer *rb, TransactionId xid, |
2904 | | XLogRecPtr prepare_lsn, XLogRecPtr end_lsn, |
2905 | | TimestampTz prepare_time, |
2906 | | ReplOriginId origin_id, XLogRecPtr origin_lsn) |
2907 | 0 | { |
2908 | 0 | ReorderBufferTXN *txn; |
2909 | |
|
2910 | 0 | txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr, false); |
2911 | | |
2912 | | /* unknown transaction, nothing to do */ |
2913 | 0 | if (txn == NULL) |
2914 | 0 | return false; |
2915 | | |
2916 | | /* |
2917 | | * Remember the prepare information to be later used by commit prepared in |
2918 | | * case we skip doing prepare. |
2919 | | */ |
2920 | 0 | txn->final_lsn = prepare_lsn; |
2921 | 0 | txn->end_lsn = end_lsn; |
2922 | 0 | txn->prepare_time = prepare_time; |
2923 | 0 | txn->origin_id = origin_id; |
2924 | 0 | txn->origin_lsn = origin_lsn; |
2925 | | |
2926 | | /* Mark this transaction as a prepared transaction */ |
2927 | 0 | Assert((txn->txn_flags & RBTXN_PREPARE_STATUS_MASK) == 0); |
2928 | 0 | txn->txn_flags |= RBTXN_IS_PREPARED; |
2929 | |
|
2930 | 0 | return true; |
2931 | 0 | } |
2932 | | |
2933 | | /* Remember that we have skipped prepare */ |
2934 | | void |
2935 | | ReorderBufferSkipPrepare(ReorderBuffer *rb, TransactionId xid) |
2936 | 0 | { |
2937 | 0 | ReorderBufferTXN *txn; |
2938 | |
|
2939 | 0 | txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr, false); |
2940 | | |
2941 | | /* unknown transaction, nothing to do */ |
2942 | 0 | if (txn == NULL) |
2943 | 0 | return; |
2944 | | |
2945 | | /* txn must have been marked as a prepared transaction */ |
2946 | 0 | Assert((txn->txn_flags & RBTXN_PREPARE_STATUS_MASK) == RBTXN_IS_PREPARED); |
2947 | 0 | txn->txn_flags |= RBTXN_SKIPPED_PREPARE; |
2948 | 0 | } |
2949 | | |
2950 | | /* |
2951 | | * Prepare a two-phase transaction. |
2952 | | * |
2953 | | * See comments for ReorderBufferReplay(). |
2954 | | */ |
2955 | | void |
2956 | | ReorderBufferPrepare(ReorderBuffer *rb, TransactionId xid, |
2957 | | char *gid) |
2958 | 0 | { |
2959 | 0 | ReorderBufferTXN *txn; |
2960 | |
|
2961 | 0 | txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr, |
2962 | 0 | false); |
2963 | | |
2964 | | /* unknown transaction, nothing to replay */ |
2965 | 0 | if (txn == NULL) |
2966 | 0 | return; |
2967 | | |
2968 | | /* |
2969 | | * txn must have been marked as a prepared transaction and must have |
2970 | | * neither been skipped nor sent a prepare. Also, the prepare info must |
2971 | | * have been updated in it by now. |
2972 | | */ |
2973 | 0 | Assert((txn->txn_flags & RBTXN_PREPARE_STATUS_MASK) == RBTXN_IS_PREPARED); |
2974 | 0 | Assert(XLogRecPtrIsValid(txn->final_lsn)); |
2975 | |
|
2976 | 0 | txn->gid = pstrdup(gid); |
2977 | |
|
2978 | 0 | ReorderBufferReplay(txn, rb, xid, txn->final_lsn, txn->end_lsn, |
2979 | 0 | txn->prepare_time, txn->origin_id, txn->origin_lsn); |
2980 | | |
2981 | | /* |
2982 | | * Send a prepare if not already done so. The "not already sent" case can |
2983 | | * occur if we have detected a concurrent abort while replaying the |
2984 | | * non-streaming transaction; we still send the prepare so that later when |
2985 | | * rollback prepared is decoded and sent, the downstream should be able to |
2986 | | * rollback such a xact. See comments atop DecodePrepare. |
2987 | | * |
2988 | | * Skip this for a transaction that made no changes to the database (i.e. |
2989 | | * has no base snapshot), as we haven't sent any changes for it. Such a |
2990 | | * transaction is cleaned up without invoking the commit/rollback prepared |
2991 | | * callbacks in ReorderBufferFinishPrepared(). |
2992 | | */ |
2993 | 0 | if (!rbtxn_sent_prepare(txn) && txn->base_snapshot != NULL) |
2994 | 0 | { |
2995 | 0 | rb->prepare(rb, txn, txn->final_lsn); |
2996 | 0 | txn->txn_flags |= RBTXN_SENT_PREPARE; |
2997 | 0 | } |
2998 | 0 | } |
2999 | | |
3000 | | /* |
3001 | | * This is used to handle COMMIT/ROLLBACK PREPARED. |
3002 | | */ |
3003 | | void |
3004 | | ReorderBufferFinishPrepared(ReorderBuffer *rb, TransactionId xid, |
3005 | | XLogRecPtr commit_lsn, XLogRecPtr end_lsn, |
3006 | | XLogRecPtr two_phase_at, |
3007 | | TimestampTz commit_time, ReplOriginId origin_id, |
3008 | | XLogRecPtr origin_lsn, char *gid, bool is_commit) |
3009 | 0 | { |
3010 | 0 | ReorderBufferTXN *txn; |
3011 | 0 | XLogRecPtr prepare_end_lsn; |
3012 | 0 | TimestampTz prepare_time; |
3013 | |
|
3014 | 0 | txn = ReorderBufferTXNByXid(rb, xid, false, NULL, commit_lsn, false); |
3015 | | |
3016 | | /* unknown transaction, nothing to do */ |
3017 | 0 | if (txn == NULL) |
3018 | 0 | return; |
3019 | | |
3020 | | /* |
3021 | | * By this time the txn has the prepare record information, remember it to |
3022 | | * be later used for rollback. |
3023 | | */ |
3024 | 0 | prepare_end_lsn = txn->end_lsn; |
3025 | 0 | prepare_time = txn->prepare_time; |
3026 | | |
3027 | | /* add the gid in the txn */ |
3028 | 0 | txn->gid = pstrdup(gid); |
3029 | | |
3030 | | /* |
3031 | | * It is possible that this transaction is not decoded at prepare time |
3032 | | * either because by that time we didn't have a consistent snapshot, or |
3033 | | * two_phase was not enabled, or it was decoded earlier but we have |
3034 | | * restarted. We only need to send the prepare if it was not decoded |
3035 | | * earlier. We don't need to decode the xact for aborts if it is not done |
3036 | | * already. |
3037 | | */ |
3038 | 0 | if ((txn->final_lsn < two_phase_at) && is_commit) |
3039 | 0 | { |
3040 | | /* |
3041 | | * txn must have been marked as a prepared transaction and skipped but |
3042 | | * not sent a prepare. Also, the prepare info must have been updated |
3043 | | * in txn even if we skip prepare. |
3044 | | */ |
3045 | 0 | Assert((txn->txn_flags & RBTXN_PREPARE_STATUS_MASK) == |
3046 | 0 | (RBTXN_IS_PREPARED | RBTXN_SKIPPED_PREPARE)); |
3047 | 0 | Assert(XLogRecPtrIsValid(txn->final_lsn)); |
3048 | | |
3049 | | /* |
3050 | | * By this time the txn has the prepare record information and it is |
3051 | | * important to use that so that downstream gets the accurate |
3052 | | * information. If instead, we have passed commit information here |
3053 | | * then downstream can behave as it has already replayed commit |
3054 | | * prepared after the restart. |
3055 | | */ |
3056 | 0 | ReorderBufferReplay(txn, rb, xid, txn->final_lsn, txn->end_lsn, |
3057 | 0 | txn->prepare_time, txn->origin_id, txn->origin_lsn); |
3058 | 0 | } |
3059 | | |
3060 | | /* |
3061 | | * If this transaction has no snapshot, it didn't make any changes to the |
3062 | | * database, so there's nothing to decode. Note that |
3063 | | * ReorderBufferCommitChild will have transferred any snapshots from |
3064 | | * subtransactions if there were any. |
3065 | | */ |
3066 | 0 | if (txn->base_snapshot == NULL) |
3067 | 0 | { |
3068 | 0 | Assert(txn->ninvalidations == 0); |
3069 | 0 | Assert(!rbtxn_sent_prepare(txn)); |
3070 | | |
3071 | | /* |
3072 | | * Removing this txn before a commit might result in the computation |
3073 | | * of an incorrect restart_lsn. See SnapBuildProcessRunningXacts. |
3074 | | */ |
3075 | 0 | ReorderBufferCleanupTXN(rb, txn); |
3076 | 0 | return; |
3077 | 0 | } |
3078 | | |
3079 | 0 | txn->final_lsn = commit_lsn; |
3080 | 0 | txn->end_lsn = end_lsn; |
3081 | 0 | txn->commit_time = commit_time; |
3082 | 0 | txn->origin_id = origin_id; |
3083 | 0 | txn->origin_lsn = origin_lsn; |
3084 | |
|
3085 | 0 | if (is_commit) |
3086 | 0 | rb->commit_prepared(rb, txn, commit_lsn); |
3087 | 0 | else |
3088 | 0 | rb->rollback_prepared(rb, txn, prepare_end_lsn, prepare_time); |
3089 | | |
3090 | | /* cleanup: make sure there's no cache pollution */ |
3091 | 0 | ReorderBufferExecuteInvalidations(txn->ninvalidations, |
3092 | 0 | txn->invalidations); |
3093 | 0 | ReorderBufferCleanupTXN(rb, txn); |
3094 | 0 | } |
3095 | | |
3096 | | /* |
3097 | | * Abort a transaction that possibly has previous changes. Needs to be first |
3098 | | * called for subtransactions and then for the toplevel xid. |
3099 | | * |
3100 | | * NB: Transactions handled here have to have actively aborted (i.e. have |
3101 | | * produced an abort record). Implicitly aborted transactions are handled via |
3102 | | * ReorderBufferAbortOld(); transactions we're just not interested in, but |
3103 | | * which have committed are handled in ReorderBufferForget(). |
3104 | | * |
3105 | | * This function purges this transaction and its contents from memory and |
3106 | | * disk. |
3107 | | */ |
3108 | | void |
3109 | | ReorderBufferAbort(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn, |
3110 | | TimestampTz abort_time) |
3111 | 0 | { |
3112 | 0 | ReorderBufferTXN *txn; |
3113 | |
|
3114 | 0 | txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr, |
3115 | 0 | false); |
3116 | | |
3117 | | /* unknown, nothing to remove */ |
3118 | 0 | if (txn == NULL) |
3119 | 0 | return; |
3120 | | |
3121 | 0 | txn->abort_time = abort_time; |
3122 | | |
3123 | | /* For streamed transactions notify the remote node about the abort. */ |
3124 | 0 | if (rbtxn_is_streamed(txn)) |
3125 | 0 | { |
3126 | 0 | rb->stream_abort(rb, txn, lsn); |
3127 | | |
3128 | | /* |
3129 | | * We might have decoded changes for this transaction that could load |
3130 | | * the cache as per the current transaction's view (consider DDL's |
3131 | | * happened in this transaction). We don't want the decoding of future |
3132 | | * transactions to use those cache entries so execute only the inval |
3133 | | * messages in this transaction. |
3134 | | */ |
3135 | 0 | if (txn->ninvalidations > 0) |
3136 | 0 | ReorderBufferImmediateInvalidation(rb, txn->ninvalidations, |
3137 | 0 | txn->invalidations); |
3138 | 0 | } |
3139 | | |
3140 | | /* cosmetic... */ |
3141 | 0 | txn->final_lsn = lsn; |
3142 | | |
3143 | | /* remove potential on-disk data, and deallocate */ |
3144 | 0 | ReorderBufferCleanupTXN(rb, txn); |
3145 | 0 | } |
3146 | | |
3147 | | /* |
3148 | | * Abort all transactions that aren't actually running anymore because the |
3149 | | * server restarted. |
3150 | | * |
3151 | | * NB: These really have to be transactions that have aborted due to a server |
3152 | | * crash/immediate restart, as we don't deal with invalidations here. |
3153 | | */ |
3154 | | void |
3155 | | ReorderBufferAbortOld(ReorderBuffer *rb, TransactionId oldestRunningXid) |
3156 | 0 | { |
3157 | 0 | dlist_mutable_iter it; |
3158 | | |
3159 | | /* |
3160 | | * Iterate through all (potential) toplevel TXNs and abort all that are |
3161 | | * older than what possibly can be running. Once we've found the first |
3162 | | * that is alive we stop, there might be some that acquired an xid earlier |
3163 | | * but started writing later, but it's unlikely and they will be cleaned |
3164 | | * up in a later call to this function. |
3165 | | */ |
3166 | 0 | dlist_foreach_modify(it, &rb->toplevel_by_lsn) |
3167 | 0 | { |
3168 | 0 | ReorderBufferTXN *txn; |
3169 | |
|
3170 | 0 | txn = dlist_container(ReorderBufferTXN, node, it.cur); |
3171 | |
|
3172 | 0 | if (TransactionIdPrecedes(txn->xid, oldestRunningXid)) |
3173 | 0 | { |
3174 | 0 | elog(DEBUG2, "aborting old transaction %u", txn->xid); |
3175 | | |
3176 | | /* Notify the remote node about the crash/immediate restart. */ |
3177 | 0 | if (rbtxn_is_streamed(txn)) |
3178 | 0 | rb->stream_abort(rb, txn, InvalidXLogRecPtr); |
3179 | | |
3180 | | /* remove potential on-disk data, and deallocate this tx */ |
3181 | 0 | ReorderBufferCleanupTXN(rb, txn); |
3182 | 0 | } |
3183 | 0 | else |
3184 | 0 | return; |
3185 | 0 | } |
3186 | 0 | } |
3187 | | |
3188 | | /* |
3189 | | * Forget the contents of a transaction if we aren't interested in its |
3190 | | * contents. Needs to be first called for subtransactions and then for the |
3191 | | * toplevel xid. |
3192 | | * |
3193 | | * This is significantly different to ReorderBufferAbort() because |
3194 | | * transactions that have committed need to be treated differently from aborted |
3195 | | * ones since they may have modified the catalog. |
3196 | | * |
3197 | | * Note that this is only allowed to be called in the moment a transaction |
3198 | | * commit has just been read, not earlier; otherwise later records referring |
3199 | | * to this xid might re-create the transaction incompletely. |
3200 | | */ |
3201 | | void |
3202 | | ReorderBufferForget(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn) |
3203 | 0 | { |
3204 | 0 | ReorderBufferTXN *txn; |
3205 | |
|
3206 | 0 | txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr, |
3207 | 0 | false); |
3208 | | |
3209 | | /* unknown, nothing to forget */ |
3210 | 0 | if (txn == NULL) |
3211 | 0 | return; |
3212 | | |
3213 | | /* this transaction mustn't be streamed */ |
3214 | 0 | Assert(!rbtxn_is_streamed(txn)); |
3215 | | |
3216 | | /* cosmetic... */ |
3217 | 0 | txn->final_lsn = lsn; |
3218 | | |
3219 | | /* |
3220 | | * Process only cache invalidation messages in this transaction if there |
3221 | | * are any. Even if we're not interested in the transaction's contents, it |
3222 | | * could have manipulated the catalog and we need to update the caches |
3223 | | * according to that. |
3224 | | */ |
3225 | 0 | if (txn->base_snapshot != NULL && txn->ninvalidations > 0) |
3226 | 0 | ReorderBufferImmediateInvalidation(rb, txn->ninvalidations, |
3227 | 0 | txn->invalidations); |
3228 | 0 | else |
3229 | 0 | Assert(txn->ninvalidations == 0); |
3230 | | |
3231 | | /* remove potential on-disk data, and deallocate */ |
3232 | 0 | ReorderBufferCleanupTXN(rb, txn); |
3233 | 0 | } |
3234 | | |
3235 | | /* |
3236 | | * Invalidate cache for those transactions that need to be skipped just in case |
3237 | | * catalogs were manipulated as part of the transaction. |
3238 | | * |
3239 | | * Note that this is a special-purpose function for prepared transactions where |
3240 | | * we don't want to clean up the TXN even when we decide to skip it. See |
3241 | | * DecodePrepare. |
3242 | | */ |
3243 | | void |
3244 | | ReorderBufferInvalidate(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn) |
3245 | 0 | { |
3246 | 0 | ReorderBufferTXN *txn; |
3247 | |
|
3248 | 0 | txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr, |
3249 | 0 | false); |
3250 | | |
3251 | | /* unknown, nothing to do */ |
3252 | 0 | if (txn == NULL) |
3253 | 0 | return; |
3254 | | |
3255 | | /* |
3256 | | * Process cache invalidation messages if there are any. Even if we're not |
3257 | | * interested in the transaction's contents, it could have manipulated the |
3258 | | * catalog and we need to update the caches according to that. |
3259 | | */ |
3260 | 0 | if (txn->base_snapshot != NULL && txn->ninvalidations > 0) |
3261 | 0 | ReorderBufferImmediateInvalidation(rb, txn->ninvalidations, |
3262 | 0 | txn->invalidations); |
3263 | 0 | else |
3264 | 0 | Assert(txn->ninvalidations == 0); |
3265 | 0 | } |
3266 | | |
3267 | | |
3268 | | /* |
3269 | | * Execute invalidations happening outside the context of a decoded |
3270 | | * transaction. That currently happens either for xid-less commits |
3271 | | * (cf. RecordTransactionCommit()) or for invalidations in uninteresting |
3272 | | * transactions (via ReorderBufferForget()). |
3273 | | */ |
3274 | | void |
3275 | | ReorderBufferImmediateInvalidation(ReorderBuffer *rb, uint32 ninvalidations, |
3276 | | SharedInvalidationMessage *invalidations) |
3277 | 0 | { |
3278 | 0 | bool use_subtxn = IsTransactionOrTransactionBlock(); |
3279 | 0 | MemoryContext ccxt = CurrentMemoryContext; |
3280 | 0 | ResourceOwner cowner = CurrentResourceOwner; |
3281 | |
|
3282 | 0 | if (use_subtxn) |
3283 | 0 | BeginInternalSubTransaction("replay"); |
3284 | | |
3285 | | /* |
3286 | | * Force invalidations to happen outside of a valid transaction - that way |
3287 | | * entries will just be marked as invalid without accessing the catalog. |
3288 | | * That's advantageous because we don't need to setup the full state |
3289 | | * necessary for catalog access. |
3290 | | */ |
3291 | 0 | if (use_subtxn) |
3292 | 0 | AbortCurrentTransaction(); |
3293 | |
|
3294 | 0 | for (uint32 i = 0; i < ninvalidations; i++) |
3295 | 0 | LocalExecuteInvalidationMessage(&invalidations[i]); |
3296 | |
|
3297 | 0 | if (use_subtxn) |
3298 | 0 | { |
3299 | 0 | RollbackAndReleaseCurrentSubTransaction(); |
3300 | 0 | MemoryContextSwitchTo(ccxt); |
3301 | 0 | CurrentResourceOwner = cowner; |
3302 | 0 | } |
3303 | 0 | } |
3304 | | |
3305 | | /* |
3306 | | * Tell reorderbuffer about an xid seen in the WAL stream. Has to be called at |
3307 | | * least once for every xid in XLogRecord->xl_xid (other places in records |
3308 | | * may, but do not have to be passed through here). |
3309 | | * |
3310 | | * Reorderbuffer keeps some data structures about transactions in LSN order, |
3311 | | * for efficiency. To do that it has to know about when transactions are seen |
3312 | | * first in the WAL. As many types of records are not actually interesting for |
3313 | | * logical decoding, they do not necessarily pass through here. |
3314 | | */ |
3315 | | void |
3316 | | ReorderBufferProcessXid(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn) |
3317 | 0 | { |
3318 | | /* many records won't have an xid assigned, centralize check here */ |
3319 | 0 | if (xid != InvalidTransactionId) |
3320 | 0 | ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true); |
3321 | 0 | } |
3322 | | |
3323 | | /* |
3324 | | * Add a new snapshot to this transaction that may only used after lsn 'lsn' |
3325 | | * because the previous snapshot doesn't describe the catalog correctly for |
3326 | | * following rows. |
3327 | | */ |
3328 | | void |
3329 | | ReorderBufferAddSnapshot(ReorderBuffer *rb, TransactionId xid, |
3330 | | XLogRecPtr lsn, Snapshot snap) |
3331 | 0 | { |
3332 | 0 | ReorderBufferChange *change = ReorderBufferAllocChange(rb); |
3333 | |
|
3334 | 0 | change->data.snapshot = snap; |
3335 | 0 | change->action = REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT; |
3336 | |
|
3337 | 0 | ReorderBufferQueueChange(rb, xid, lsn, change, false); |
3338 | 0 | } |
3339 | | |
3340 | | /* |
3341 | | * Set up the transaction's base snapshot. |
3342 | | * |
3343 | | * If we know that xid is a subtransaction, set the base snapshot on the |
3344 | | * top-level transaction instead. |
3345 | | */ |
3346 | | void |
3347 | | ReorderBufferSetBaseSnapshot(ReorderBuffer *rb, TransactionId xid, |
3348 | | XLogRecPtr lsn, Snapshot snap) |
3349 | 0 | { |
3350 | 0 | ReorderBufferTXN *txn; |
3351 | 0 | bool is_new; |
3352 | |
|
3353 | 0 | Assert(snap != NULL); |
3354 | | |
3355 | | /* |
3356 | | * Fetch the transaction to operate on. If we know it's a subtransaction, |
3357 | | * operate on its top-level transaction instead. |
3358 | | */ |
3359 | 0 | txn = ReorderBufferTXNByXid(rb, xid, true, &is_new, lsn, true); |
3360 | 0 | if (rbtxn_is_known_subxact(txn)) |
3361 | 0 | txn = ReorderBufferTXNByXid(rb, txn->toplevel_xid, false, |
3362 | 0 | NULL, InvalidXLogRecPtr, false); |
3363 | 0 | Assert(txn->base_snapshot == NULL); |
3364 | |
|
3365 | 0 | txn->base_snapshot = snap; |
3366 | 0 | txn->base_snapshot_lsn = lsn; |
3367 | 0 | dlist_push_tail(&rb->txns_by_base_snapshot_lsn, &txn->base_snapshot_node); |
3368 | |
|
3369 | 0 | AssertTXNLsnOrder(rb); |
3370 | 0 | } |
3371 | | |
3372 | | /* |
3373 | | * Access the catalog with this CommandId at this point in the changestream. |
3374 | | * |
3375 | | * May only be called for command ids > 1 |
3376 | | */ |
3377 | | void |
3378 | | ReorderBufferAddNewCommandId(ReorderBuffer *rb, TransactionId xid, |
3379 | | XLogRecPtr lsn, CommandId cid) |
3380 | 0 | { |
3381 | 0 | ReorderBufferChange *change = ReorderBufferAllocChange(rb); |
3382 | |
|
3383 | 0 | change->data.command_id = cid; |
3384 | 0 | change->action = REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID; |
3385 | |
|
3386 | 0 | ReorderBufferQueueChange(rb, xid, lsn, change, false); |
3387 | 0 | } |
3388 | | |
3389 | | /* |
3390 | | * Update memory counters to account for the new or removed change. |
3391 | | * |
3392 | | * We update two counters - in the reorder buffer, and in the transaction |
3393 | | * containing the change. The reorder buffer counter allows us to quickly |
3394 | | * decide if we reached the memory limit, the transaction counter allows |
3395 | | * us to quickly pick the largest transaction for eviction. |
3396 | | * |
3397 | | * Either txn or change must be non-NULL at least. We update the memory |
3398 | | * counter of txn if it's non-NULL, otherwise change->txn. |
3399 | | * |
3400 | | * When streaming is enabled, we need to update the toplevel transaction |
3401 | | * counters instead - we don't really care about subtransactions as we |
3402 | | * can't stream them individually anyway, and we only pick toplevel |
3403 | | * transactions for eviction. So only toplevel transactions matter. |
3404 | | */ |
3405 | | static void |
3406 | | ReorderBufferChangeMemoryUpdate(ReorderBuffer *rb, |
3407 | | ReorderBufferChange *change, |
3408 | | ReorderBufferTXN *txn, |
3409 | | bool addition, Size sz) |
3410 | 0 | { |
3411 | 0 | ReorderBufferTXN *toptxn; |
3412 | |
|
3413 | 0 | Assert(txn || change); |
3414 | | |
3415 | | /* |
3416 | | * Ignore tuple CID changes, because those are not evicted when reaching |
3417 | | * memory limit. So we just don't count them, because it might easily |
3418 | | * trigger a pointless attempt to spill. |
3419 | | */ |
3420 | 0 | if (change && change->action == REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID) |
3421 | 0 | return; |
3422 | | |
3423 | 0 | if (sz == 0) |
3424 | 0 | return; |
3425 | | |
3426 | 0 | if (txn == NULL) |
3427 | 0 | txn = change->txn; |
3428 | 0 | Assert(txn != NULL); |
3429 | | |
3430 | | /* |
3431 | | * Update the total size in top level as well. This is later used to |
3432 | | * compute the decoding stats. |
3433 | | */ |
3434 | 0 | toptxn = rbtxn_get_toptxn(txn); |
3435 | |
|
3436 | 0 | if (addition) |
3437 | 0 | { |
3438 | 0 | Size oldsize = txn->size; |
3439 | |
|
3440 | 0 | txn->size += sz; |
3441 | 0 | rb->size += sz; |
3442 | | |
3443 | | /* Update the total size in the top transaction. */ |
3444 | 0 | toptxn->total_size += sz; |
3445 | | |
3446 | | /* Update the max-heap */ |
3447 | 0 | if (oldsize != 0) |
3448 | 0 | pairingheap_remove(rb->txn_heap, &txn->txn_node); |
3449 | 0 | pairingheap_add(rb->txn_heap, &txn->txn_node); |
3450 | 0 | } |
3451 | 0 | else |
3452 | 0 | { |
3453 | 0 | Assert((rb->size >= sz) && (txn->size >= sz)); |
3454 | 0 | txn->size -= sz; |
3455 | 0 | rb->size -= sz; |
3456 | | |
3457 | | /* Update the total size in the top transaction. */ |
3458 | 0 | toptxn->total_size -= sz; |
3459 | | |
3460 | | /* Update the max-heap */ |
3461 | 0 | pairingheap_remove(rb->txn_heap, &txn->txn_node); |
3462 | 0 | if (txn->size != 0) |
3463 | 0 | pairingheap_add(rb->txn_heap, &txn->txn_node); |
3464 | 0 | } |
3465 | |
|
3466 | 0 | Assert(txn->size <= rb->size); |
3467 | 0 | } |
3468 | | |
3469 | | /* |
3470 | | * Add new (relfilelocator, tid) -> (cmin, cmax) mappings. |
3471 | | * |
3472 | | * We do not include this change type in memory accounting, because we |
3473 | | * keep CIDs in a separate list and do not evict them when reaching |
3474 | | * the memory limit. |
3475 | | */ |
3476 | | void |
3477 | | ReorderBufferAddNewTupleCids(ReorderBuffer *rb, TransactionId xid, |
3478 | | XLogRecPtr lsn, RelFileLocator locator, |
3479 | | ItemPointerData tid, CommandId cmin, |
3480 | | CommandId cmax, CommandId combocid) |
3481 | 0 | { |
3482 | 0 | ReorderBufferChange *change = ReorderBufferAllocChange(rb); |
3483 | 0 | ReorderBufferTXN *txn; |
3484 | |
|
3485 | 0 | txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true); |
3486 | |
|
3487 | 0 | change->data.tuplecid.locator = locator; |
3488 | 0 | change->data.tuplecid.tid = tid; |
3489 | 0 | change->data.tuplecid.cmin = cmin; |
3490 | 0 | change->data.tuplecid.cmax = cmax; |
3491 | 0 | change->data.tuplecid.combocid = combocid; |
3492 | 0 | change->lsn = lsn; |
3493 | 0 | change->txn = txn; |
3494 | 0 | change->action = REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID; |
3495 | |
|
3496 | 0 | dlist_push_tail(&txn->tuplecids, &change->node); |
3497 | 0 | txn->ntuplecids++; |
3498 | 0 | } |
3499 | | |
3500 | | /* |
3501 | | * Add new invalidation messages to the reorder buffer queue. |
3502 | | */ |
3503 | | static void |
3504 | | ReorderBufferQueueInvalidations(ReorderBuffer *rb, TransactionId xid, |
3505 | | XLogRecPtr lsn, Size nmsgs, |
3506 | | SharedInvalidationMessage *msgs) |
3507 | 0 | { |
3508 | 0 | ReorderBufferChange *change; |
3509 | |
|
3510 | 0 | change = ReorderBufferAllocChange(rb); |
3511 | 0 | change->action = REORDER_BUFFER_CHANGE_INVALIDATION; |
3512 | 0 | change->data.inval.ninvalidations = nmsgs; |
3513 | 0 | change->data.inval.invalidations = palloc_array(SharedInvalidationMessage, nmsgs); |
3514 | 0 | memcpy(change->data.inval.invalidations, msgs, |
3515 | 0 | sizeof(SharedInvalidationMessage) * nmsgs); |
3516 | |
|
3517 | 0 | ReorderBufferQueueChange(rb, xid, lsn, change, false); |
3518 | 0 | } |
3519 | | |
3520 | | /* |
3521 | | * A helper function for ReorderBufferAddInvalidations() and |
3522 | | * ReorderBufferAddDistributedInvalidations() to accumulate the invalidation |
3523 | | * messages to the **invals_out. |
3524 | | */ |
3525 | | static void |
3526 | | ReorderBufferAccumulateInvalidations(SharedInvalidationMessage **invals_out, |
3527 | | uint32 *ninvals_out, |
3528 | | SharedInvalidationMessage *msgs_new, |
3529 | | Size nmsgs_new) |
3530 | 0 | { |
3531 | 0 | if (*ninvals_out == 0) |
3532 | 0 | { |
3533 | 0 | *ninvals_out = nmsgs_new; |
3534 | 0 | *invals_out = palloc_array(SharedInvalidationMessage, nmsgs_new); |
3535 | 0 | memcpy(*invals_out, msgs_new, sizeof(SharedInvalidationMessage) * nmsgs_new); |
3536 | 0 | } |
3537 | 0 | else |
3538 | 0 | { |
3539 | | /* Enlarge the array of inval messages */ |
3540 | 0 | *invals_out = |
3541 | 0 | repalloc_array(*invals_out, SharedInvalidationMessage, |
3542 | 0 | (*ninvals_out + nmsgs_new)); |
3543 | 0 | memcpy(*invals_out + *ninvals_out, msgs_new, |
3544 | 0 | nmsgs_new * sizeof(SharedInvalidationMessage)); |
3545 | 0 | *ninvals_out += nmsgs_new; |
3546 | 0 | } |
3547 | 0 | } |
3548 | | |
3549 | | /* |
3550 | | * Accumulate the invalidations for executing them later. |
3551 | | * |
3552 | | * This needs to be called for each XLOG_XACT_INVALIDATIONS message and |
3553 | | * accumulates all the invalidation messages in the toplevel transaction, if |
3554 | | * available, otherwise in the current transaction, as well as in the form of |
3555 | | * change in reorder buffer. We require to record it in form of the change |
3556 | | * so that we can execute only the required invalidations instead of executing |
3557 | | * all the invalidations on each CommandId increment. We also need to |
3558 | | * accumulate these in the txn buffer because in some cases where we skip |
3559 | | * processing the transaction (see ReorderBufferForget), we need to execute |
3560 | | * all the invalidations together. |
3561 | | */ |
3562 | | void |
3563 | | ReorderBufferAddInvalidations(ReorderBuffer *rb, TransactionId xid, |
3564 | | XLogRecPtr lsn, Size nmsgs, |
3565 | | SharedInvalidationMessage *msgs) |
3566 | 0 | { |
3567 | 0 | ReorderBufferTXN *txn; |
3568 | 0 | MemoryContext oldcontext; |
3569 | |
|
3570 | 0 | txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true); |
3571 | |
|
3572 | 0 | oldcontext = MemoryContextSwitchTo(rb->context); |
3573 | | |
3574 | | /* |
3575 | | * Collect all the invalidations under the top transaction, if available, |
3576 | | * so that we can execute them all together. See comments atop this |
3577 | | * function. |
3578 | | */ |
3579 | 0 | txn = rbtxn_get_toptxn(txn); |
3580 | |
|
3581 | 0 | Assert(nmsgs > 0); |
3582 | |
|
3583 | 0 | ReorderBufferAccumulateInvalidations(&txn->invalidations, |
3584 | 0 | &txn->ninvalidations, |
3585 | 0 | msgs, nmsgs); |
3586 | |
|
3587 | 0 | ReorderBufferQueueInvalidations(rb, xid, lsn, nmsgs, msgs); |
3588 | |
|
3589 | 0 | MemoryContextSwitchTo(oldcontext); |
3590 | 0 | } |
3591 | | |
3592 | | /* |
3593 | | * Accumulate the invalidations distributed by other committed transactions |
3594 | | * for executing them later. |
3595 | | * |
3596 | | * This function is similar to ReorderBufferAddInvalidations() but stores |
3597 | | * the given inval messages to the txn->invalidations_distributed with the |
3598 | | * overflow check. |
3599 | | * |
3600 | | * This needs to be called by committed transactions to distribute their |
3601 | | * inval messages to in-progress transactions. |
3602 | | */ |
3603 | | void |
3604 | | ReorderBufferAddDistributedInvalidations(ReorderBuffer *rb, TransactionId xid, |
3605 | | XLogRecPtr lsn, Size nmsgs, |
3606 | | SharedInvalidationMessage *msgs) |
3607 | 0 | { |
3608 | 0 | ReorderBufferTXN *txn; |
3609 | 0 | MemoryContext oldcontext; |
3610 | |
|
3611 | 0 | txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true); |
3612 | |
|
3613 | 0 | oldcontext = MemoryContextSwitchTo(rb->context); |
3614 | | |
3615 | | /* |
3616 | | * Collect all the invalidations under the top transaction, if available, |
3617 | | * so that we can execute them all together. See comments |
3618 | | * ReorderBufferAddInvalidations. |
3619 | | */ |
3620 | 0 | txn = rbtxn_get_toptxn(txn); |
3621 | |
|
3622 | 0 | Assert(nmsgs > 0); |
3623 | |
|
3624 | 0 | if (!rbtxn_distr_inval_overflowed(txn)) |
3625 | 0 | { |
3626 | | /* |
3627 | | * Check the transaction has enough space for storing distributed |
3628 | | * invalidation messages. |
3629 | | */ |
3630 | 0 | if (txn->ninvalidations_distributed + nmsgs >= MAX_DISTR_INVAL_MSG_PER_TXN) |
3631 | 0 | { |
3632 | | /* |
3633 | | * Mark the invalidation message as overflowed and free up the |
3634 | | * messages accumulated so far. |
3635 | | */ |
3636 | 0 | txn->txn_flags |= RBTXN_DISTR_INVAL_OVERFLOWED; |
3637 | |
|
3638 | 0 | if (txn->invalidations_distributed) |
3639 | 0 | { |
3640 | 0 | pfree(txn->invalidations_distributed); |
3641 | 0 | txn->invalidations_distributed = NULL; |
3642 | 0 | txn->ninvalidations_distributed = 0; |
3643 | 0 | } |
3644 | 0 | } |
3645 | 0 | else |
3646 | 0 | ReorderBufferAccumulateInvalidations(&txn->invalidations_distributed, |
3647 | 0 | &txn->ninvalidations_distributed, |
3648 | 0 | msgs, nmsgs); |
3649 | 0 | } |
3650 | | |
3651 | | /* Queue the invalidation messages into the transaction */ |
3652 | 0 | ReorderBufferQueueInvalidations(rb, xid, lsn, nmsgs, msgs); |
3653 | |
|
3654 | 0 | MemoryContextSwitchTo(oldcontext); |
3655 | 0 | } |
3656 | | |
3657 | | /* |
3658 | | * Apply all invalidations we know. Possibly we only need parts at this point |
3659 | | * in the changestream but we don't know which those are. |
3660 | | */ |
3661 | | static void |
3662 | | ReorderBufferExecuteInvalidations(uint32 nmsgs, SharedInvalidationMessage *msgs) |
3663 | 0 | { |
3664 | 0 | for (uint32 i = 0; i < nmsgs; i++) |
3665 | 0 | LocalExecuteInvalidationMessage(&msgs[i]); |
3666 | 0 | } |
3667 | | |
3668 | | /* |
3669 | | * Mark a transaction as containing catalog changes |
3670 | | */ |
3671 | | void |
3672 | | ReorderBufferXidSetCatalogChanges(ReorderBuffer *rb, TransactionId xid, |
3673 | | XLogRecPtr lsn) |
3674 | 0 | { |
3675 | 0 | ReorderBufferTXN *txn; |
3676 | |
|
3677 | 0 | txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true); |
3678 | |
|
3679 | 0 | if (!rbtxn_has_catalog_changes(txn)) |
3680 | 0 | { |
3681 | 0 | txn->txn_flags |= RBTXN_HAS_CATALOG_CHANGES; |
3682 | 0 | dclist_push_tail(&rb->catchange_txns, &txn->catchange_node); |
3683 | 0 | } |
3684 | | |
3685 | | /* |
3686 | | * Mark top-level transaction as having catalog changes too if one of its |
3687 | | * children has so that the ReorderBufferBuildTupleCidHash can |
3688 | | * conveniently check just top-level transaction and decide whether to |
3689 | | * build the hash table or not. |
3690 | | */ |
3691 | 0 | if (rbtxn_is_subtxn(txn)) |
3692 | 0 | { |
3693 | 0 | ReorderBufferTXN *toptxn = rbtxn_get_toptxn(txn); |
3694 | |
|
3695 | 0 | if (!rbtxn_has_catalog_changes(toptxn)) |
3696 | 0 | { |
3697 | 0 | toptxn->txn_flags |= RBTXN_HAS_CATALOG_CHANGES; |
3698 | 0 | dclist_push_tail(&rb->catchange_txns, &toptxn->catchange_node); |
3699 | 0 | } |
3700 | 0 | } |
3701 | 0 | } |
3702 | | |
3703 | | /* |
3704 | | * Return palloc'ed array of the transactions that have changed catalogs. |
3705 | | * The returned array is sorted in xidComparator order. |
3706 | | * |
3707 | | * The caller must free the returned array when done with it. |
3708 | | */ |
3709 | | TransactionId * |
3710 | | ReorderBufferGetCatalogChangesXacts(ReorderBuffer *rb) |
3711 | 0 | { |
3712 | 0 | dlist_iter iter; |
3713 | 0 | TransactionId *xids = NULL; |
3714 | 0 | size_t xcnt = 0; |
3715 | | |
3716 | | /* Quick return if the list is empty */ |
3717 | 0 | if (dclist_count(&rb->catchange_txns) == 0) |
3718 | 0 | return NULL; |
3719 | | |
3720 | | /* Initialize XID array */ |
3721 | 0 | xids = palloc_array(TransactionId, dclist_count(&rb->catchange_txns)); |
3722 | 0 | dclist_foreach(iter, &rb->catchange_txns) |
3723 | 0 | { |
3724 | 0 | ReorderBufferTXN *txn = dclist_container(ReorderBufferTXN, |
3725 | 0 | catchange_node, |
3726 | 0 | iter.cur); |
3727 | |
|
3728 | 0 | Assert(rbtxn_has_catalog_changes(txn)); |
3729 | |
|
3730 | 0 | xids[xcnt++] = txn->xid; |
3731 | 0 | } |
3732 | |
|
3733 | 0 | qsort(xids, xcnt, sizeof(TransactionId), xidComparator); |
3734 | |
|
3735 | 0 | Assert(xcnt == dclist_count(&rb->catchange_txns)); |
3736 | 0 | return xids; |
3737 | 0 | } |
3738 | | |
3739 | | /* |
3740 | | * Query whether a transaction is already *known* to contain catalog |
3741 | | * changes. This can be wrong until directly before the commit! |
3742 | | */ |
3743 | | bool |
3744 | | ReorderBufferXidHasCatalogChanges(ReorderBuffer *rb, TransactionId xid) |
3745 | 0 | { |
3746 | 0 | ReorderBufferTXN *txn; |
3747 | |
|
3748 | 0 | txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr, |
3749 | 0 | false); |
3750 | 0 | if (txn == NULL) |
3751 | 0 | return false; |
3752 | | |
3753 | 0 | return rbtxn_has_catalog_changes(txn); |
3754 | 0 | } |
3755 | | |
3756 | | /* |
3757 | | * ReorderBufferXidHasBaseSnapshot |
3758 | | * Have we already set the base snapshot for the given txn/subtxn? |
3759 | | */ |
3760 | | bool |
3761 | | ReorderBufferXidHasBaseSnapshot(ReorderBuffer *rb, TransactionId xid) |
3762 | 0 | { |
3763 | 0 | ReorderBufferTXN *txn; |
3764 | |
|
3765 | 0 | txn = ReorderBufferTXNByXid(rb, xid, false, |
3766 | 0 | NULL, InvalidXLogRecPtr, false); |
3767 | | |
3768 | | /* transaction isn't known yet, ergo no snapshot */ |
3769 | 0 | if (txn == NULL) |
3770 | 0 | return false; |
3771 | | |
3772 | | /* a known subtxn? operate on top-level txn instead */ |
3773 | 0 | if (rbtxn_is_known_subxact(txn)) |
3774 | 0 | txn = ReorderBufferTXNByXid(rb, txn->toplevel_xid, false, |
3775 | 0 | NULL, InvalidXLogRecPtr, false); |
3776 | |
|
3777 | 0 | return txn->base_snapshot != NULL; |
3778 | 0 | } |
3779 | | |
3780 | | |
3781 | | /* |
3782 | | * --------------------------------------- |
3783 | | * Disk serialization support |
3784 | | * --------------------------------------- |
3785 | | */ |
3786 | | |
3787 | | /* |
3788 | | * Ensure the IO buffer is >= sz. |
3789 | | */ |
3790 | | static void |
3791 | | ReorderBufferSerializeReserve(ReorderBuffer *rb, Size sz) |
3792 | 0 | { |
3793 | 0 | if (!rb->outbufsize) |
3794 | 0 | { |
3795 | 0 | rb->outbuf = MemoryContextAlloc(rb->context, sz); |
3796 | 0 | rb->outbufsize = sz; |
3797 | 0 | } |
3798 | 0 | else if (rb->outbufsize < sz) |
3799 | 0 | { |
3800 | 0 | rb->outbuf = repalloc(rb->outbuf, sz); |
3801 | 0 | rb->outbufsize = sz; |
3802 | 0 | } |
3803 | 0 | } |
3804 | | |
3805 | | |
3806 | | /* Compare two transactions by size */ |
3807 | | static int |
3808 | | ReorderBufferTXNSizeCompare(const pairingheap_node *a, const pairingheap_node *b, void *arg) |
3809 | 0 | { |
3810 | 0 | const ReorderBufferTXN *ta = pairingheap_const_container(ReorderBufferTXN, txn_node, a); |
3811 | 0 | const ReorderBufferTXN *tb = pairingheap_const_container(ReorderBufferTXN, txn_node, b); |
3812 | |
|
3813 | 0 | if (ta->size < tb->size) |
3814 | 0 | return -1; |
3815 | 0 | if (ta->size > tb->size) |
3816 | 0 | return 1; |
3817 | 0 | return 0; |
3818 | 0 | } |
3819 | | |
3820 | | /* |
3821 | | * Find the largest transaction (toplevel or subxact) to evict (spill to disk). |
3822 | | */ |
3823 | | static ReorderBufferTXN * |
3824 | | ReorderBufferLargestTXN(ReorderBuffer *rb) |
3825 | 0 | { |
3826 | 0 | ReorderBufferTXN *largest; |
3827 | | |
3828 | | /* Get the largest transaction from the max-heap */ |
3829 | 0 | largest = pairingheap_container(ReorderBufferTXN, txn_node, |
3830 | 0 | pairingheap_first(rb->txn_heap)); |
3831 | |
|
3832 | 0 | Assert(largest); |
3833 | 0 | Assert(largest->size > 0); |
3834 | 0 | Assert(largest->size <= rb->size); |
3835 | |
|
3836 | 0 | return largest; |
3837 | 0 | } |
3838 | | |
3839 | | /* |
3840 | | * Find the largest streamable (and non-aborted) toplevel transaction to evict |
3841 | | * (by streaming). |
3842 | | * |
3843 | | * This can be seen as an optimized version of ReorderBufferLargestTXN, which |
3844 | | * should give us the same transaction (because we don't update memory account |
3845 | | * for subtransaction with streaming, so it's always 0). But we can simply |
3846 | | * iterate over the limited number of toplevel transactions that have a base |
3847 | | * snapshot. There is no use of selecting a transaction that doesn't have base |
3848 | | * snapshot because we don't decode such transactions. Also, we do not select |
3849 | | * the transaction which doesn't have any streamable change. |
3850 | | * |
3851 | | * Note that, we skip transactions that contain incomplete changes. There |
3852 | | * is a scope of optimization here such that we can select the largest |
3853 | | * transaction which has incomplete changes. But that will make the code and |
3854 | | * design quite complex and that might not be worth the benefit. If we plan to |
3855 | | * stream the transactions that contain incomplete changes then we need to |
3856 | | * find a way to partially stream/truncate the transaction changes in-memory |
3857 | | * and build a mechanism to partially truncate the spilled files. |
3858 | | * Additionally, whenever we partially stream the transaction we need to |
3859 | | * maintain the last streamed lsn and next time we need to restore from that |
3860 | | * segment and the offset in WAL. As we stream the changes from the top |
3861 | | * transaction and restore them subtransaction wise, we need to even remember |
3862 | | * the subxact from where we streamed the last change. |
3863 | | */ |
3864 | | static ReorderBufferTXN * |
3865 | | ReorderBufferLargestStreamableTopTXN(ReorderBuffer *rb) |
3866 | 0 | { |
3867 | 0 | dlist_iter iter; |
3868 | 0 | Size largest_size = 0; |
3869 | 0 | ReorderBufferTXN *largest = NULL; |
3870 | | |
3871 | | /* Find the largest top-level transaction having a base snapshot. */ |
3872 | 0 | dlist_foreach(iter, &rb->txns_by_base_snapshot_lsn) |
3873 | 0 | { |
3874 | 0 | ReorderBufferTXN *txn; |
3875 | |
|
3876 | 0 | txn = dlist_container(ReorderBufferTXN, base_snapshot_node, iter.cur); |
3877 | | |
3878 | | /* must not be a subtxn */ |
3879 | 0 | Assert(!rbtxn_is_known_subxact(txn)); |
3880 | | /* base_snapshot must be set */ |
3881 | 0 | Assert(txn->base_snapshot != NULL); |
3882 | | |
3883 | | /* Don't consider these kinds of transactions for eviction. */ |
3884 | 0 | if (rbtxn_has_partial_change(txn) || |
3885 | 0 | !rbtxn_has_streamable_change(txn) || |
3886 | 0 | rbtxn_is_aborted(txn)) |
3887 | 0 | continue; |
3888 | | |
3889 | | /* Find the largest of the eviction candidates. */ |
3890 | 0 | if ((largest == NULL || txn->total_size > largest_size) && |
3891 | 0 | (txn->total_size > 0)) |
3892 | 0 | { |
3893 | 0 | largest = txn; |
3894 | 0 | largest_size = txn->total_size; |
3895 | 0 | } |
3896 | 0 | } |
3897 | |
|
3898 | 0 | return largest; |
3899 | 0 | } |
3900 | | |
3901 | | /* |
3902 | | * Check whether the logical_decoding_work_mem limit was reached, and if yes |
3903 | | * pick the largest (sub)transaction at-a-time to evict and spill its changes to |
3904 | | * disk or send to the output plugin until we reach under the memory limit. |
3905 | | * |
3906 | | * If debug_logical_replication_streaming is set to "immediate", stream or |
3907 | | * serialize the changes immediately. |
3908 | | * |
3909 | | * XXX At this point we select the transactions until we reach under the memory |
3910 | | * limit, but we might also adapt a more elaborate eviction strategy - for example |
3911 | | * evicting enough transactions to free certain fraction (e.g. 50%) of the memory |
3912 | | * limit. |
3913 | | */ |
3914 | | static void |
3915 | | ReorderBufferCheckMemoryLimit(ReorderBuffer *rb) |
3916 | 0 | { |
3917 | 0 | ReorderBufferTXN *txn; |
3918 | 0 | bool update_stats = true; |
3919 | |
|
3920 | 0 | if (rb->size >= logical_decoding_work_mem * (Size) 1024) |
3921 | 0 | { |
3922 | | /* |
3923 | | * Update the statistics as the memory usage has reached the limit. We |
3924 | | * report the statistics update later in this function since we can |
3925 | | * update the slot statistics altogether while streaming or |
3926 | | * serializing transactions in most cases. |
3927 | | */ |
3928 | 0 | rb->memExceededCount += 1; |
3929 | 0 | } |
3930 | 0 | else if (debug_logical_replication_streaming == DEBUG_LOGICAL_REP_STREAMING_BUFFERED) |
3931 | 0 | { |
3932 | | /* |
3933 | | * Bail out if debug_logical_replication_streaming is buffered and we |
3934 | | * haven't exceeded the memory limit. |
3935 | | */ |
3936 | 0 | return; |
3937 | 0 | } |
3938 | | |
3939 | | /* |
3940 | | * If debug_logical_replication_streaming is immediate, loop until there's |
3941 | | * no change. Otherwise, loop until we reach under the memory limit. One |
3942 | | * might think that just by evicting the largest (sub)transaction we will |
3943 | | * come under the memory limit based on assumption that the selected |
3944 | | * transaction is at least as large as the most recent change (which |
3945 | | * caused us to go over the memory limit). However, that is not true |
3946 | | * because a user can reduce the logical_decoding_work_mem to a smaller |
3947 | | * value before the most recent change. |
3948 | | */ |
3949 | 0 | while (rb->size >= logical_decoding_work_mem * (Size) 1024 || |
3950 | 0 | (debug_logical_replication_streaming == DEBUG_LOGICAL_REP_STREAMING_IMMEDIATE && |
3951 | 0 | rb->size > 0)) |
3952 | 0 | { |
3953 | | /* |
3954 | | * Pick the largest non-aborted transaction and evict it from memory |
3955 | | * by streaming, if possible. Otherwise, spill to disk. |
3956 | | */ |
3957 | 0 | if (ReorderBufferCanStartStreaming(rb) && |
3958 | 0 | (txn = ReorderBufferLargestStreamableTopTXN(rb)) != NULL) |
3959 | 0 | { |
3960 | | /* we know there has to be one, because the size is not zero */ |
3961 | 0 | Assert(txn && rbtxn_is_toptxn(txn)); |
3962 | 0 | Assert(txn->total_size > 0); |
3963 | 0 | Assert(rb->size >= txn->total_size); |
3964 | | |
3965 | | /* skip the transaction if aborted */ |
3966 | 0 | if (ReorderBufferCheckAndTruncateAbortedTXN(rb, txn)) |
3967 | 0 | continue; |
3968 | | |
3969 | 0 | ReorderBufferStreamTXN(rb, txn); |
3970 | 0 | } |
3971 | 0 | else |
3972 | 0 | { |
3973 | | /* |
3974 | | * Pick the largest transaction (or subtransaction) and evict it |
3975 | | * from memory by serializing it to disk. |
3976 | | */ |
3977 | 0 | txn = ReorderBufferLargestTXN(rb); |
3978 | | |
3979 | | /* we know there has to be one, because the size is not zero */ |
3980 | 0 | Assert(txn); |
3981 | 0 | Assert(txn->size > 0); |
3982 | 0 | Assert(rb->size >= txn->size); |
3983 | | |
3984 | | /* skip the transaction if aborted */ |
3985 | 0 | if (ReorderBufferCheckAndTruncateAbortedTXN(rb, txn)) |
3986 | 0 | continue; |
3987 | | |
3988 | 0 | ReorderBufferSerializeTXN(rb, txn); |
3989 | 0 | } |
3990 | | |
3991 | | /* |
3992 | | * After eviction, the transaction should have no entries in memory, |
3993 | | * and should use 0 bytes for changes. |
3994 | | */ |
3995 | 0 | Assert(txn->size == 0); |
3996 | 0 | Assert(txn->nentries_mem == 0); |
3997 | | |
3998 | | /* |
3999 | | * We've reported the memExceededCount update while streaming or |
4000 | | * serializing the transaction. |
4001 | | */ |
4002 | 0 | update_stats = false; |
4003 | 0 | } |
4004 | |
|
4005 | 0 | if (update_stats) |
4006 | 0 | UpdateDecodingStats((LogicalDecodingContext *) rb->private_data); |
4007 | | |
4008 | | /* We must be under the memory limit now. */ |
4009 | 0 | Assert(rb->size < logical_decoding_work_mem * (Size) 1024); |
4010 | 0 | } |
4011 | | |
4012 | | /* |
4013 | | * Spill data of a large transaction (and its subtransactions) to disk. |
4014 | | */ |
4015 | | static void |
4016 | | ReorderBufferSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn) |
4017 | 0 | { |
4018 | 0 | dlist_iter subtxn_i; |
4019 | 0 | dlist_mutable_iter change_i; |
4020 | 0 | int fd = -1; |
4021 | 0 | XLogSegNo curOpenSegNo = 0; |
4022 | 0 | Size spilled = 0; |
4023 | 0 | Size size = txn->size; |
4024 | |
|
4025 | 0 | elog(DEBUG2, "spill %u changes in XID %u to disk", |
4026 | 0 | (uint32) txn->nentries_mem, txn->xid); |
4027 | | |
4028 | | /* do the same to all child TXs */ |
4029 | 0 | dlist_foreach(subtxn_i, &txn->subtxns) |
4030 | 0 | { |
4031 | 0 | ReorderBufferTXN *subtxn; |
4032 | |
|
4033 | 0 | subtxn = dlist_container(ReorderBufferTXN, node, subtxn_i.cur); |
4034 | 0 | ReorderBufferSerializeTXN(rb, subtxn); |
4035 | 0 | } |
4036 | | |
4037 | | /* serialize changestream */ |
4038 | 0 | dlist_foreach_modify(change_i, &txn->changes) |
4039 | 0 | { |
4040 | 0 | ReorderBufferChange *change; |
4041 | |
|
4042 | 0 | change = dlist_container(ReorderBufferChange, node, change_i.cur); |
4043 | | |
4044 | | /* |
4045 | | * store in segment in which it belongs by start lsn, don't split over |
4046 | | * multiple segments tho |
4047 | | */ |
4048 | 0 | if (fd == -1 || |
4049 | 0 | !XLByteInSeg(change->lsn, curOpenSegNo, wal_segment_size)) |
4050 | 0 | { |
4051 | 0 | char path[MAXPGPATH]; |
4052 | |
|
4053 | 0 | if (fd != -1) |
4054 | 0 | CloseTransientFile(fd); |
4055 | |
|
4056 | 0 | XLByteToSeg(change->lsn, curOpenSegNo, wal_segment_size); |
4057 | | |
4058 | | /* |
4059 | | * No need to care about TLIs here, only used during a single run, |
4060 | | * so each LSN only maps to a specific WAL record. |
4061 | | */ |
4062 | 0 | ReorderBufferSerializedPath(path, MyReplicationSlot, txn->xid, |
4063 | 0 | curOpenSegNo); |
4064 | | |
4065 | | /* open segment, create it if necessary */ |
4066 | 0 | fd = OpenTransientFile(path, |
4067 | 0 | O_CREAT | O_WRONLY | O_APPEND | PG_BINARY); |
4068 | |
|
4069 | 0 | if (fd < 0) |
4070 | 0 | ereport(ERROR, |
4071 | 0 | (errcode_for_file_access(), |
4072 | 0 | errmsg("could not open file \"%s\": %m", path))); |
4073 | 0 | } |
4074 | | |
4075 | 0 | ReorderBufferSerializeChange(rb, txn, fd, change); |
4076 | 0 | dlist_delete(&change->node); |
4077 | 0 | ReorderBufferFreeChange(rb, change, false); |
4078 | |
|
4079 | 0 | spilled++; |
4080 | 0 | } |
4081 | | |
4082 | | /* Update the memory counter */ |
4083 | 0 | ReorderBufferChangeMemoryUpdate(rb, NULL, txn, false, size); |
4084 | | |
4085 | | /* update the statistics iff we have spilled anything */ |
4086 | 0 | if (spilled) |
4087 | 0 | { |
4088 | 0 | rb->spillCount += 1; |
4089 | 0 | rb->spillBytes += size; |
4090 | | |
4091 | | /* don't consider already serialized transactions */ |
4092 | 0 | rb->spillTxns += (rbtxn_is_serialized(txn) || rbtxn_is_serialized_clear(txn)) ? 0 : 1; |
4093 | | |
4094 | | /* update the decoding stats */ |
4095 | 0 | UpdateDecodingStats((LogicalDecodingContext *) rb->private_data); |
4096 | 0 | } |
4097 | |
|
4098 | 0 | Assert(spilled == txn->nentries_mem); |
4099 | 0 | Assert(dlist_is_empty(&txn->changes)); |
4100 | 0 | txn->nentries_mem = 0; |
4101 | 0 | txn->txn_flags |= RBTXN_IS_SERIALIZED; |
4102 | |
|
4103 | 0 | if (fd != -1) |
4104 | 0 | CloseTransientFile(fd); |
4105 | 0 | } |
4106 | | |
4107 | | /* |
4108 | | * Serialize individual change to disk. |
4109 | | */ |
4110 | | static void |
4111 | | ReorderBufferSerializeChange(ReorderBuffer *rb, ReorderBufferTXN *txn, |
4112 | | int fd, ReorderBufferChange *change) |
4113 | 0 | { |
4114 | 0 | ReorderBufferDiskChange *ondisk; |
4115 | 0 | Size sz = sizeof(ReorderBufferDiskChange); |
4116 | |
|
4117 | 0 | ReorderBufferSerializeReserve(rb, sz); |
4118 | |
|
4119 | 0 | ondisk = (ReorderBufferDiskChange *) rb->outbuf; |
4120 | 0 | memcpy(&ondisk->change, change, sizeof(ReorderBufferChange)); |
4121 | |
|
4122 | 0 | switch (change->action) |
4123 | 0 | { |
4124 | | /* fall through these, they're all similar enough */ |
4125 | 0 | case REORDER_BUFFER_CHANGE_INSERT: |
4126 | 0 | case REORDER_BUFFER_CHANGE_UPDATE: |
4127 | 0 | case REORDER_BUFFER_CHANGE_DELETE: |
4128 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT: |
4129 | 0 | { |
4130 | 0 | char *data; |
4131 | 0 | HeapTuple oldtup, |
4132 | 0 | newtup; |
4133 | 0 | Size oldlen = 0; |
4134 | 0 | Size newlen = 0; |
4135 | |
|
4136 | 0 | oldtup = change->data.tp.oldtuple; |
4137 | 0 | newtup = change->data.tp.newtuple; |
4138 | |
|
4139 | 0 | if (oldtup) |
4140 | 0 | { |
4141 | 0 | sz += sizeof(HeapTupleData); |
4142 | 0 | oldlen = oldtup->t_len; |
4143 | 0 | sz += oldlen; |
4144 | 0 | } |
4145 | |
|
4146 | 0 | if (newtup) |
4147 | 0 | { |
4148 | 0 | sz += sizeof(HeapTupleData); |
4149 | 0 | newlen = newtup->t_len; |
4150 | 0 | sz += newlen; |
4151 | 0 | } |
4152 | | |
4153 | | /* make sure we have enough space */ |
4154 | 0 | ReorderBufferSerializeReserve(rb, sz); |
4155 | |
|
4156 | 0 | data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange); |
4157 | | /* might have been reallocated above */ |
4158 | 0 | ondisk = (ReorderBufferDiskChange *) rb->outbuf; |
4159 | |
|
4160 | 0 | if (oldlen) |
4161 | 0 | { |
4162 | 0 | memcpy(data, oldtup, sizeof(HeapTupleData)); |
4163 | 0 | data += sizeof(HeapTupleData); |
4164 | |
|
4165 | 0 | memcpy(data, oldtup->t_data, oldlen); |
4166 | 0 | data += oldlen; |
4167 | 0 | } |
4168 | |
|
4169 | 0 | if (newlen) |
4170 | 0 | { |
4171 | 0 | memcpy(data, newtup, sizeof(HeapTupleData)); |
4172 | 0 | data += sizeof(HeapTupleData); |
4173 | |
|
4174 | 0 | memcpy(data, newtup->t_data, newlen); |
4175 | 0 | data += newlen; |
4176 | 0 | } |
4177 | 0 | break; |
4178 | 0 | } |
4179 | 0 | case REORDER_BUFFER_CHANGE_MESSAGE: |
4180 | 0 | { |
4181 | 0 | char *data; |
4182 | 0 | Size prefix_size = strlen(change->data.msg.prefix) + 1; |
4183 | |
|
4184 | 0 | sz += prefix_size + change->data.msg.message_size + |
4185 | 0 | sizeof(Size) + sizeof(Size); |
4186 | 0 | ReorderBufferSerializeReserve(rb, sz); |
4187 | |
|
4188 | 0 | data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange); |
4189 | | |
4190 | | /* might have been reallocated above */ |
4191 | 0 | ondisk = (ReorderBufferDiskChange *) rb->outbuf; |
4192 | | |
4193 | | /* write the prefix including the size */ |
4194 | 0 | memcpy(data, &prefix_size, sizeof(Size)); |
4195 | 0 | data += sizeof(Size); |
4196 | 0 | memcpy(data, change->data.msg.prefix, |
4197 | 0 | prefix_size); |
4198 | 0 | data += prefix_size; |
4199 | | |
4200 | | /* write the message including the size */ |
4201 | 0 | memcpy(data, &change->data.msg.message_size, sizeof(Size)); |
4202 | 0 | data += sizeof(Size); |
4203 | 0 | memcpy(data, change->data.msg.message, |
4204 | 0 | change->data.msg.message_size); |
4205 | 0 | data += change->data.msg.message_size; |
4206 | |
|
4207 | 0 | break; |
4208 | 0 | } |
4209 | 0 | case REORDER_BUFFER_CHANGE_INVALIDATION: |
4210 | 0 | { |
4211 | 0 | char *data; |
4212 | 0 | Size inval_size = sizeof(SharedInvalidationMessage) * |
4213 | 0 | change->data.inval.ninvalidations; |
4214 | |
|
4215 | 0 | sz += inval_size; |
4216 | |
|
4217 | 0 | ReorderBufferSerializeReserve(rb, sz); |
4218 | 0 | data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange); |
4219 | | |
4220 | | /* might have been reallocated above */ |
4221 | 0 | ondisk = (ReorderBufferDiskChange *) rb->outbuf; |
4222 | 0 | memcpy(data, change->data.inval.invalidations, inval_size); |
4223 | 0 | data += inval_size; |
4224 | |
|
4225 | 0 | break; |
4226 | 0 | } |
4227 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT: |
4228 | 0 | { |
4229 | 0 | Snapshot snap; |
4230 | 0 | char *data; |
4231 | |
|
4232 | 0 | snap = change->data.snapshot; |
4233 | |
|
4234 | 0 | sz += sizeof(SnapshotData) + |
4235 | 0 | sizeof(TransactionId) * snap->xcnt + |
4236 | 0 | sizeof(TransactionId) * snap->subxcnt; |
4237 | | |
4238 | | /* make sure we have enough space */ |
4239 | 0 | ReorderBufferSerializeReserve(rb, sz); |
4240 | 0 | data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange); |
4241 | | /* might have been reallocated above */ |
4242 | 0 | ondisk = (ReorderBufferDiskChange *) rb->outbuf; |
4243 | |
|
4244 | 0 | memcpy(data, snap, sizeof(SnapshotData)); |
4245 | 0 | data += sizeof(SnapshotData); |
4246 | |
|
4247 | 0 | if (snap->xcnt) |
4248 | 0 | { |
4249 | 0 | memcpy(data, snap->xip, |
4250 | 0 | sizeof(TransactionId) * snap->xcnt); |
4251 | 0 | data += sizeof(TransactionId) * snap->xcnt; |
4252 | 0 | } |
4253 | |
|
4254 | 0 | if (snap->subxcnt) |
4255 | 0 | { |
4256 | 0 | memcpy(data, snap->subxip, |
4257 | 0 | sizeof(TransactionId) * snap->subxcnt); |
4258 | 0 | data += sizeof(TransactionId) * snap->subxcnt; |
4259 | 0 | } |
4260 | 0 | break; |
4261 | 0 | } |
4262 | 0 | case REORDER_BUFFER_CHANGE_TRUNCATE: |
4263 | 0 | { |
4264 | 0 | Size size; |
4265 | 0 | char *data; |
4266 | | |
4267 | | /* account for the OIDs of truncated relations */ |
4268 | 0 | size = sizeof(Oid) * change->data.truncate.nrelids; |
4269 | 0 | sz += size; |
4270 | | |
4271 | | /* make sure we have enough space */ |
4272 | 0 | ReorderBufferSerializeReserve(rb, sz); |
4273 | |
|
4274 | 0 | data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange); |
4275 | | /* might have been reallocated above */ |
4276 | 0 | ondisk = (ReorderBufferDiskChange *) rb->outbuf; |
4277 | |
|
4278 | 0 | memcpy(data, change->data.truncate.relids, size); |
4279 | 0 | data += size; |
4280 | |
|
4281 | 0 | break; |
4282 | 0 | } |
4283 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM: |
4284 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_ABORT: |
4285 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID: |
4286 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID: |
4287 | | /* ReorderBufferChange contains everything important */ |
4288 | 0 | break; |
4289 | 0 | } |
4290 | | |
4291 | 0 | ondisk->size = sz; |
4292 | |
|
4293 | 0 | errno = 0; |
4294 | 0 | pgstat_report_wait_start(WAIT_EVENT_REORDER_BUFFER_WRITE); |
4295 | 0 | if (write(fd, rb->outbuf, ondisk->size) != ondisk->size) |
4296 | 0 | { |
4297 | 0 | int save_errno = errno; |
4298 | |
|
4299 | 0 | CloseTransientFile(fd); |
4300 | | |
4301 | | /* if write didn't set errno, assume problem is no disk space */ |
4302 | 0 | errno = save_errno ? save_errno : ENOSPC; |
4303 | 0 | ereport(ERROR, |
4304 | 0 | (errcode_for_file_access(), |
4305 | 0 | errmsg("could not write to data file for XID %u: %m", |
4306 | 0 | txn->xid))); |
4307 | 0 | } |
4308 | 0 | pgstat_report_wait_end(); |
4309 | | |
4310 | | /* |
4311 | | * Keep the transaction's final_lsn up to date with each change we send to |
4312 | | * disk, so that ReorderBufferRestoreCleanup works correctly. (We used to |
4313 | | * only do this on commit and abort records, but that doesn't work if a |
4314 | | * system crash leaves a transaction without its abort record). |
4315 | | * |
4316 | | * Make sure not to move it backwards. |
4317 | | */ |
4318 | 0 | if (txn->final_lsn < change->lsn) |
4319 | 0 | txn->final_lsn = change->lsn; |
4320 | |
|
4321 | 0 | Assert(ondisk->change.action == change->action); |
4322 | 0 | } |
4323 | | |
4324 | | /* Returns true, if the output plugin supports streaming, false, otherwise. */ |
4325 | | static inline bool |
4326 | | ReorderBufferCanStream(ReorderBuffer *rb) |
4327 | 0 | { |
4328 | 0 | LogicalDecodingContext *ctx = rb->private_data; |
4329 | |
|
4330 | 0 | return ctx->streaming; |
4331 | 0 | } |
4332 | | |
4333 | | /* Returns true, if the streaming can be started now, false, otherwise. */ |
4334 | | static inline bool |
4335 | | ReorderBufferCanStartStreaming(ReorderBuffer *rb) |
4336 | 0 | { |
4337 | 0 | LogicalDecodingContext *ctx = rb->private_data; |
4338 | 0 | SnapBuild *builder = ctx->snapshot_builder; |
4339 | | |
4340 | | /* We can't start streaming unless a consistent state is reached. */ |
4341 | 0 | if (SnapBuildCurrentState(builder) < SNAPBUILD_CONSISTENT) |
4342 | 0 | return false; |
4343 | | |
4344 | | /* |
4345 | | * We can't start streaming immediately even if the streaming is enabled |
4346 | | * because we previously decoded this transaction and now just are |
4347 | | * restarting. |
4348 | | */ |
4349 | 0 | if (ReorderBufferCanStream(rb) && |
4350 | 0 | !SnapBuildXactNeedsSkip(builder, ctx->reader->ReadRecPtr)) |
4351 | 0 | return true; |
4352 | | |
4353 | 0 | return false; |
4354 | 0 | } |
4355 | | |
4356 | | /* |
4357 | | * Send data of a large transaction (and its subtransactions) to the |
4358 | | * output plugin, but using the stream API. |
4359 | | */ |
4360 | | static void |
4361 | | ReorderBufferStreamTXN(ReorderBuffer *rb, ReorderBufferTXN *txn) |
4362 | 0 | { |
4363 | 0 | Snapshot snapshot_now; |
4364 | 0 | CommandId command_id; |
4365 | 0 | Size stream_bytes; |
4366 | 0 | bool txn_is_streamed; |
4367 | | |
4368 | | /* We can never reach here for a subtransaction. */ |
4369 | 0 | Assert(rbtxn_is_toptxn(txn)); |
4370 | | |
4371 | | /* |
4372 | | * We can't make any assumptions about base snapshot here, similar to what |
4373 | | * ReorderBufferCommit() does. That relies on base_snapshot getting |
4374 | | * transferred from subxact in ReorderBufferCommitChild(), but that was |
4375 | | * not yet called as the transaction is in-progress. |
4376 | | * |
4377 | | * So just walk the subxacts and use the same logic here. But we only need |
4378 | | * to do that once, when the transaction is streamed for the first time. |
4379 | | * After that we need to reuse the snapshot from the previous run. |
4380 | | * |
4381 | | * Unlike DecodeCommit which adds xids of all the subtransactions in |
4382 | | * snapshot's xip array via SnapBuildCommitTxn, we can't do that here but |
4383 | | * we do add them to subxip array instead via ReorderBufferCopySnap. This |
4384 | | * allows the catalog changes made in subtransactions decoded till now to |
4385 | | * be visible. |
4386 | | */ |
4387 | 0 | if (txn->snapshot_now == NULL) |
4388 | 0 | { |
4389 | 0 | dlist_iter subxact_i; |
4390 | | |
4391 | | /* make sure this transaction is streamed for the first time */ |
4392 | 0 | Assert(!rbtxn_is_streamed(txn)); |
4393 | | |
4394 | | /* at the beginning we should have invalid command ID */ |
4395 | 0 | Assert(txn->command_id == InvalidCommandId); |
4396 | |
|
4397 | 0 | dlist_foreach(subxact_i, &txn->subtxns) |
4398 | 0 | { |
4399 | 0 | ReorderBufferTXN *subtxn; |
4400 | |
|
4401 | 0 | subtxn = dlist_container(ReorderBufferTXN, node, subxact_i.cur); |
4402 | 0 | ReorderBufferTransferSnapToParent(txn, subtxn); |
4403 | 0 | } |
4404 | | |
4405 | | /* |
4406 | | * If this transaction has no snapshot, it didn't make any changes to |
4407 | | * the database till now, so there's nothing to decode. |
4408 | | */ |
4409 | 0 | if (txn->base_snapshot == NULL) |
4410 | 0 | { |
4411 | 0 | Assert(txn->ninvalidations == 0); |
4412 | 0 | return; |
4413 | 0 | } |
4414 | | |
4415 | 0 | command_id = FirstCommandId; |
4416 | 0 | snapshot_now = ReorderBufferCopySnap(rb, txn->base_snapshot, |
4417 | 0 | txn, command_id); |
4418 | 0 | } |
4419 | 0 | else |
4420 | 0 | { |
4421 | | /* the transaction must have been already streamed */ |
4422 | 0 | Assert(rbtxn_is_streamed(txn)); |
4423 | | |
4424 | | /* |
4425 | | * Nah, we already have snapshot from the previous streaming run. We |
4426 | | * assume new subxacts can't move the LSN backwards, and so can't beat |
4427 | | * the LSN condition in the previous branch (so no need to walk |
4428 | | * through subxacts again). In fact, we must not do that as we may be |
4429 | | * using snapshot half-way through the subxact. |
4430 | | */ |
4431 | 0 | command_id = txn->command_id; |
4432 | | |
4433 | | /* |
4434 | | * We can't use txn->snapshot_now directly because after the last |
4435 | | * streaming run, we might have got some new sub-transactions. So we |
4436 | | * need to add them to the snapshot. |
4437 | | */ |
4438 | 0 | snapshot_now = ReorderBufferCopySnap(rb, txn->snapshot_now, |
4439 | 0 | txn, command_id); |
4440 | | |
4441 | | /* Free the previously copied snapshot. */ |
4442 | 0 | Assert(txn->snapshot_now->copied); |
4443 | 0 | ReorderBufferFreeSnap(rb, txn->snapshot_now); |
4444 | 0 | txn->snapshot_now = NULL; |
4445 | 0 | } |
4446 | | |
4447 | | /* |
4448 | | * Remember this information to be used later to update stats. We can't |
4449 | | * update the stats here as an error while processing the changes would |
4450 | | * lead to the accumulation of stats even though we haven't streamed all |
4451 | | * the changes. |
4452 | | */ |
4453 | 0 | txn_is_streamed = rbtxn_is_streamed(txn); |
4454 | 0 | stream_bytes = txn->total_size; |
4455 | | |
4456 | | /* Process and send the changes to output plugin. */ |
4457 | 0 | ReorderBufferProcessTXN(rb, txn, InvalidXLogRecPtr, snapshot_now, |
4458 | 0 | command_id, true); |
4459 | |
|
4460 | 0 | rb->streamCount += 1; |
4461 | 0 | rb->streamBytes += stream_bytes; |
4462 | | |
4463 | | /* Don't consider already streamed transaction. */ |
4464 | 0 | rb->streamTxns += (txn_is_streamed) ? 0 : 1; |
4465 | | |
4466 | | /* update the decoding stats */ |
4467 | 0 | UpdateDecodingStats((LogicalDecodingContext *) rb->private_data); |
4468 | |
|
4469 | 0 | Assert(dlist_is_empty(&txn->changes)); |
4470 | 0 | Assert(txn->nentries == 0); |
4471 | 0 | Assert(txn->nentries_mem == 0); |
4472 | 0 | } |
4473 | | |
4474 | | /* |
4475 | | * Size of a change in memory. |
4476 | | */ |
4477 | | static Size |
4478 | | ReorderBufferChangeSize(ReorderBufferChange *change) |
4479 | 0 | { |
4480 | 0 | Size sz = sizeof(ReorderBufferChange); |
4481 | |
|
4482 | 0 | switch (change->action) |
4483 | 0 | { |
4484 | | /* fall through these, they're all similar enough */ |
4485 | 0 | case REORDER_BUFFER_CHANGE_INSERT: |
4486 | 0 | case REORDER_BUFFER_CHANGE_UPDATE: |
4487 | 0 | case REORDER_BUFFER_CHANGE_DELETE: |
4488 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT: |
4489 | 0 | { |
4490 | 0 | HeapTuple oldtup, |
4491 | 0 | newtup; |
4492 | 0 | Size oldlen = 0; |
4493 | 0 | Size newlen = 0; |
4494 | |
|
4495 | 0 | oldtup = change->data.tp.oldtuple; |
4496 | 0 | newtup = change->data.tp.newtuple; |
4497 | |
|
4498 | 0 | if (oldtup) |
4499 | 0 | { |
4500 | 0 | sz += sizeof(HeapTupleData); |
4501 | 0 | oldlen = oldtup->t_len; |
4502 | 0 | sz += oldlen; |
4503 | 0 | } |
4504 | |
|
4505 | 0 | if (newtup) |
4506 | 0 | { |
4507 | 0 | sz += sizeof(HeapTupleData); |
4508 | 0 | newlen = newtup->t_len; |
4509 | 0 | sz += newlen; |
4510 | 0 | } |
4511 | |
|
4512 | 0 | break; |
4513 | 0 | } |
4514 | 0 | case REORDER_BUFFER_CHANGE_MESSAGE: |
4515 | 0 | { |
4516 | 0 | Size prefix_size = strlen(change->data.msg.prefix) + 1; |
4517 | |
|
4518 | 0 | sz += prefix_size + change->data.msg.message_size + |
4519 | 0 | sizeof(Size) + sizeof(Size); |
4520 | |
|
4521 | 0 | break; |
4522 | 0 | } |
4523 | 0 | case REORDER_BUFFER_CHANGE_INVALIDATION: |
4524 | 0 | { |
4525 | 0 | sz += sizeof(SharedInvalidationMessage) * |
4526 | 0 | change->data.inval.ninvalidations; |
4527 | 0 | break; |
4528 | 0 | } |
4529 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT: |
4530 | 0 | { |
4531 | 0 | Snapshot snap; |
4532 | |
|
4533 | 0 | snap = change->data.snapshot; |
4534 | |
|
4535 | 0 | sz += sizeof(SnapshotData) + |
4536 | 0 | sizeof(TransactionId) * snap->xcnt + |
4537 | 0 | sizeof(TransactionId) * snap->subxcnt; |
4538 | |
|
4539 | 0 | break; |
4540 | 0 | } |
4541 | 0 | case REORDER_BUFFER_CHANGE_TRUNCATE: |
4542 | 0 | { |
4543 | 0 | sz += sizeof(Oid) * change->data.truncate.nrelids; |
4544 | |
|
4545 | 0 | break; |
4546 | 0 | } |
4547 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM: |
4548 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_ABORT: |
4549 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID: |
4550 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID: |
4551 | | /* ReorderBufferChange contains everything important */ |
4552 | 0 | break; |
4553 | 0 | } |
4554 | | |
4555 | 0 | return sz; |
4556 | 0 | } |
4557 | | |
4558 | | |
4559 | | /* |
4560 | | * Restore a number of changes spilled to disk back into memory. |
4561 | | */ |
4562 | | static Size |
4563 | | ReorderBufferRestoreChanges(ReorderBuffer *rb, ReorderBufferTXN *txn, |
4564 | | TXNEntryFile *file, XLogSegNo *segno) |
4565 | 0 | { |
4566 | 0 | Size restored = 0; |
4567 | 0 | XLogSegNo last_segno; |
4568 | 0 | dlist_mutable_iter cleanup_iter; |
4569 | 0 | File *fd = &file->vfd; |
4570 | |
|
4571 | 0 | Assert(XLogRecPtrIsValid(txn->first_lsn)); |
4572 | 0 | Assert(XLogRecPtrIsValid(txn->final_lsn)); |
4573 | | |
4574 | | /* free current entries, so we have memory for more */ |
4575 | 0 | dlist_foreach_modify(cleanup_iter, &txn->changes) |
4576 | 0 | { |
4577 | 0 | ReorderBufferChange *cleanup = |
4578 | 0 | dlist_container(ReorderBufferChange, node, cleanup_iter.cur); |
4579 | |
|
4580 | 0 | dlist_delete(&cleanup->node); |
4581 | 0 | ReorderBufferFreeChange(rb, cleanup, true); |
4582 | 0 | } |
4583 | 0 | txn->nentries_mem = 0; |
4584 | 0 | Assert(dlist_is_empty(&txn->changes)); |
4585 | |
|
4586 | 0 | XLByteToSeg(txn->final_lsn, last_segno, wal_segment_size); |
4587 | |
|
4588 | 0 | while (restored < max_changes_in_memory && *segno <= last_segno) |
4589 | 0 | { |
4590 | 0 | ssize_t readBytes; |
4591 | 0 | ReorderBufferDiskChange *ondisk; |
4592 | |
|
4593 | 0 | CHECK_FOR_INTERRUPTS(); |
4594 | |
|
4595 | 0 | if (*fd == -1) |
4596 | 0 | { |
4597 | 0 | char path[MAXPGPATH]; |
4598 | | |
4599 | | /* first time in */ |
4600 | 0 | if (*segno == 0) |
4601 | 0 | XLByteToSeg(txn->first_lsn, *segno, wal_segment_size); |
4602 | |
|
4603 | 0 | Assert(*segno != 0 || dlist_is_empty(&txn->changes)); |
4604 | | |
4605 | | /* |
4606 | | * No need to care about TLIs here, only used during a single run, |
4607 | | * so each LSN only maps to a specific WAL record. |
4608 | | */ |
4609 | 0 | ReorderBufferSerializedPath(path, MyReplicationSlot, txn->xid, |
4610 | 0 | *segno); |
4611 | |
|
4612 | 0 | *fd = PathNameOpenFile(path, O_RDONLY | PG_BINARY); |
4613 | | |
4614 | | /* No harm in resetting the offset even in case of failure */ |
4615 | 0 | file->curOffset = 0; |
4616 | |
|
4617 | 0 | if (*fd < 0 && errno == ENOENT) |
4618 | 0 | { |
4619 | 0 | *fd = -1; |
4620 | 0 | (*segno)++; |
4621 | 0 | continue; |
4622 | 0 | } |
4623 | 0 | else if (*fd < 0) |
4624 | 0 | ereport(ERROR, |
4625 | 0 | (errcode_for_file_access(), |
4626 | 0 | errmsg("could not open file \"%s\": %m", |
4627 | 0 | path))); |
4628 | 0 | } |
4629 | | |
4630 | | /* |
4631 | | * Read the statically sized part of a change which has information |
4632 | | * about the total size. If we couldn't read a record, we're at the |
4633 | | * end of this file. |
4634 | | */ |
4635 | 0 | ReorderBufferSerializeReserve(rb, sizeof(ReorderBufferDiskChange)); |
4636 | 0 | readBytes = FileRead(file->vfd, rb->outbuf, |
4637 | 0 | sizeof(ReorderBufferDiskChange), |
4638 | 0 | file->curOffset, WAIT_EVENT_REORDER_BUFFER_READ); |
4639 | | |
4640 | | /* eof */ |
4641 | 0 | if (readBytes == 0) |
4642 | 0 | { |
4643 | 0 | FileClose(*fd); |
4644 | 0 | *fd = -1; |
4645 | 0 | (*segno)++; |
4646 | 0 | continue; |
4647 | 0 | } |
4648 | 0 | else if (readBytes < 0) |
4649 | 0 | ereport(ERROR, |
4650 | 0 | (errcode_for_file_access(), |
4651 | 0 | errmsg("could not read from reorderbuffer spill file: %m"))); |
4652 | 0 | else if (readBytes != sizeof(ReorderBufferDiskChange)) |
4653 | 0 | ereport(ERROR, |
4654 | 0 | (errcode_for_file_access(), |
4655 | 0 | errmsg("could not read from reorderbuffer spill file: read %zd of %zu", |
4656 | 0 | readBytes, |
4657 | 0 | sizeof(ReorderBufferDiskChange)))); |
4658 | | |
4659 | 0 | file->curOffset += readBytes; |
4660 | |
|
4661 | 0 | ondisk = (ReorderBufferDiskChange *) rb->outbuf; |
4662 | |
|
4663 | 0 | ReorderBufferSerializeReserve(rb, |
4664 | 0 | sizeof(ReorderBufferDiskChange) + ondisk->size); |
4665 | 0 | ondisk = (ReorderBufferDiskChange *) rb->outbuf; |
4666 | |
|
4667 | 0 | readBytes = FileRead(file->vfd, |
4668 | 0 | rb->outbuf + sizeof(ReorderBufferDiskChange), |
4669 | 0 | ondisk->size - sizeof(ReorderBufferDiskChange), |
4670 | 0 | file->curOffset, |
4671 | 0 | WAIT_EVENT_REORDER_BUFFER_READ); |
4672 | |
|
4673 | 0 | if (readBytes < 0) |
4674 | 0 | ereport(ERROR, |
4675 | 0 | (errcode_for_file_access(), |
4676 | 0 | errmsg("could not read from reorderbuffer spill file: %m"))); |
4677 | 0 | else if (readBytes != ondisk->size - sizeof(ReorderBufferDiskChange)) |
4678 | 0 | ereport(ERROR, |
4679 | 0 | (errcode_for_file_access(), |
4680 | 0 | errmsg("could not read from reorderbuffer spill file: read %zd of %zu", |
4681 | 0 | readBytes, |
4682 | 0 | (ondisk->size - sizeof(ReorderBufferDiskChange))))); |
4683 | | |
4684 | 0 | file->curOffset += readBytes; |
4685 | | |
4686 | | /* |
4687 | | * ok, read a full change from disk, now restore it into proper |
4688 | | * in-memory format |
4689 | | */ |
4690 | 0 | ReorderBufferRestoreChange(rb, txn, rb->outbuf); |
4691 | 0 | restored++; |
4692 | 0 | } |
4693 | | |
4694 | 0 | return restored; |
4695 | 0 | } |
4696 | | |
4697 | | /* |
4698 | | * Convert change from its on-disk format to in-memory format and queue it onto |
4699 | | * the TXN's ->changes list. |
4700 | | * |
4701 | | * Note: although "data" is declared char*, at entry it points to a |
4702 | | * maxalign'd buffer, making it safe in most of this function to assume |
4703 | | * that the pointed-to data is suitably aligned for direct access. |
4704 | | */ |
4705 | | static void |
4706 | | ReorderBufferRestoreChange(ReorderBuffer *rb, ReorderBufferTXN *txn, |
4707 | | char *data) |
4708 | 0 | { |
4709 | 0 | ReorderBufferDiskChange *ondisk; |
4710 | 0 | ReorderBufferChange *change; |
4711 | |
|
4712 | 0 | ondisk = (ReorderBufferDiskChange *) data; |
4713 | |
|
4714 | 0 | change = ReorderBufferAllocChange(rb); |
4715 | | |
4716 | | /* copy static part */ |
4717 | 0 | memcpy(change, &ondisk->change, sizeof(ReorderBufferChange)); |
4718 | |
|
4719 | 0 | data += sizeof(ReorderBufferDiskChange); |
4720 | | |
4721 | | /* restore individual stuff */ |
4722 | 0 | switch (change->action) |
4723 | 0 | { |
4724 | | /* fall through these, they're all similar enough */ |
4725 | 0 | case REORDER_BUFFER_CHANGE_INSERT: |
4726 | 0 | case REORDER_BUFFER_CHANGE_UPDATE: |
4727 | 0 | case REORDER_BUFFER_CHANGE_DELETE: |
4728 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT: |
4729 | 0 | if (change->data.tp.oldtuple) |
4730 | 0 | { |
4731 | 0 | uint32 tuplelen = ((HeapTuple) data)->t_len; |
4732 | |
|
4733 | 0 | change->data.tp.oldtuple = |
4734 | 0 | ReorderBufferAllocTupleBuf(rb, tuplelen - SizeofHeapTupleHeader); |
4735 | | |
4736 | | /* restore ->tuple */ |
4737 | 0 | memcpy(change->data.tp.oldtuple, data, |
4738 | 0 | sizeof(HeapTupleData)); |
4739 | 0 | data += sizeof(HeapTupleData); |
4740 | | |
4741 | | /* reset t_data pointer into the new tuplebuf */ |
4742 | 0 | change->data.tp.oldtuple->t_data = |
4743 | 0 | (HeapTupleHeader) ((char *) change->data.tp.oldtuple + HEAPTUPLESIZE); |
4744 | | |
4745 | | /* restore tuple data itself */ |
4746 | 0 | memcpy(change->data.tp.oldtuple->t_data, data, tuplelen); |
4747 | 0 | data += tuplelen; |
4748 | 0 | } |
4749 | |
|
4750 | 0 | if (change->data.tp.newtuple) |
4751 | 0 | { |
4752 | | /* here, data might not be suitably aligned! */ |
4753 | 0 | uint32 tuplelen; |
4754 | |
|
4755 | 0 | memcpy(&tuplelen, data + offsetof(HeapTupleData, t_len), |
4756 | 0 | sizeof(uint32)); |
4757 | |
|
4758 | 0 | change->data.tp.newtuple = |
4759 | 0 | ReorderBufferAllocTupleBuf(rb, tuplelen - SizeofHeapTupleHeader); |
4760 | | |
4761 | | /* restore ->tuple */ |
4762 | 0 | memcpy(change->data.tp.newtuple, data, |
4763 | 0 | sizeof(HeapTupleData)); |
4764 | 0 | data += sizeof(HeapTupleData); |
4765 | | |
4766 | | /* reset t_data pointer into the new tuplebuf */ |
4767 | 0 | change->data.tp.newtuple->t_data = |
4768 | 0 | (HeapTupleHeader) ((char *) change->data.tp.newtuple + HEAPTUPLESIZE); |
4769 | | |
4770 | | /* restore tuple data itself */ |
4771 | 0 | memcpy(change->data.tp.newtuple->t_data, data, tuplelen); |
4772 | 0 | data += tuplelen; |
4773 | 0 | } |
4774 | |
|
4775 | 0 | break; |
4776 | 0 | case REORDER_BUFFER_CHANGE_MESSAGE: |
4777 | 0 | { |
4778 | 0 | Size prefix_size; |
4779 | | |
4780 | | /* read prefix */ |
4781 | 0 | memcpy(&prefix_size, data, sizeof(Size)); |
4782 | 0 | data += sizeof(Size); |
4783 | 0 | change->data.msg.prefix = MemoryContextAlloc(rb->context, |
4784 | 0 | prefix_size); |
4785 | 0 | memcpy(change->data.msg.prefix, data, prefix_size); |
4786 | 0 | Assert(change->data.msg.prefix[prefix_size - 1] == '\0'); |
4787 | 0 | data += prefix_size; |
4788 | | |
4789 | | /* read the message */ |
4790 | 0 | memcpy(&change->data.msg.message_size, data, sizeof(Size)); |
4791 | 0 | data += sizeof(Size); |
4792 | 0 | change->data.msg.message = MemoryContextAlloc(rb->context, |
4793 | 0 | change->data.msg.message_size); |
4794 | 0 | memcpy(change->data.msg.message, data, |
4795 | 0 | change->data.msg.message_size); |
4796 | 0 | data += change->data.msg.message_size; |
4797 | |
|
4798 | 0 | break; |
4799 | 0 | } |
4800 | 0 | case REORDER_BUFFER_CHANGE_INVALIDATION: |
4801 | 0 | { |
4802 | 0 | Size inval_size = sizeof(SharedInvalidationMessage) * |
4803 | 0 | change->data.inval.ninvalidations; |
4804 | |
|
4805 | 0 | change->data.inval.invalidations = |
4806 | 0 | MemoryContextAlloc(rb->context, inval_size); |
4807 | | |
4808 | | /* read the message */ |
4809 | 0 | memcpy(change->data.inval.invalidations, data, inval_size); |
4810 | |
|
4811 | 0 | break; |
4812 | 0 | } |
4813 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT: |
4814 | 0 | { |
4815 | 0 | Snapshot oldsnap; |
4816 | 0 | Snapshot newsnap; |
4817 | 0 | Size size; |
4818 | |
|
4819 | 0 | oldsnap = (Snapshot) data; |
4820 | |
|
4821 | 0 | size = sizeof(SnapshotData) + |
4822 | 0 | sizeof(TransactionId) * oldsnap->xcnt + |
4823 | 0 | sizeof(TransactionId) * (oldsnap->subxcnt + 0); |
4824 | |
|
4825 | 0 | change->data.snapshot = MemoryContextAllocZero(rb->context, size); |
4826 | |
|
4827 | 0 | newsnap = change->data.snapshot; |
4828 | |
|
4829 | 0 | memcpy(newsnap, data, size); |
4830 | 0 | newsnap->xip = (TransactionId *) |
4831 | 0 | (((char *) newsnap) + sizeof(SnapshotData)); |
4832 | 0 | newsnap->subxip = newsnap->xip + newsnap->xcnt; |
4833 | 0 | newsnap->copied = true; |
4834 | 0 | break; |
4835 | 0 | } |
4836 | | /* the base struct contains all the data, easy peasy */ |
4837 | 0 | case REORDER_BUFFER_CHANGE_TRUNCATE: |
4838 | 0 | { |
4839 | 0 | Oid *relids; |
4840 | |
|
4841 | 0 | relids = ReorderBufferAllocRelids(rb, change->data.truncate.nrelids); |
4842 | 0 | memcpy(relids, data, change->data.truncate.nrelids * sizeof(Oid)); |
4843 | 0 | change->data.truncate.relids = relids; |
4844 | |
|
4845 | 0 | break; |
4846 | 0 | } |
4847 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM: |
4848 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_ABORT: |
4849 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID: |
4850 | 0 | case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID: |
4851 | 0 | break; |
4852 | 0 | } |
4853 | | |
4854 | 0 | dlist_push_tail(&txn->changes, &change->node); |
4855 | 0 | txn->nentries_mem++; |
4856 | | |
4857 | | /* |
4858 | | * Update memory accounting for the restored change. We need to do this |
4859 | | * although we don't check the memory limit when restoring the changes in |
4860 | | * this branch (we only do that when initially queueing the changes after |
4861 | | * decoding), because we will release the changes later, and that will |
4862 | | * update the accounting too (subtracting the size from the counters). And |
4863 | | * we don't want to underflow there. |
4864 | | */ |
4865 | 0 | ReorderBufferChangeMemoryUpdate(rb, change, NULL, true, |
4866 | 0 | ReorderBufferChangeSize(change)); |
4867 | 0 | } |
4868 | | |
4869 | | /* |
4870 | | * Remove all on-disk stored for the passed in transaction. |
4871 | | */ |
4872 | | static void |
4873 | | ReorderBufferRestoreCleanup(ReorderBuffer *rb, ReorderBufferTXN *txn) |
4874 | 0 | { |
4875 | 0 | XLogSegNo first; |
4876 | 0 | XLogSegNo cur; |
4877 | 0 | XLogSegNo last; |
4878 | |
|
4879 | 0 | Assert(XLogRecPtrIsValid(txn->first_lsn)); |
4880 | 0 | Assert(XLogRecPtrIsValid(txn->final_lsn)); |
4881 | |
|
4882 | 0 | XLByteToSeg(txn->first_lsn, first, wal_segment_size); |
4883 | 0 | XLByteToSeg(txn->final_lsn, last, wal_segment_size); |
4884 | | |
4885 | | /* iterate over all possible filenames, and delete them */ |
4886 | 0 | for (cur = first; cur <= last; cur++) |
4887 | 0 | { |
4888 | 0 | char path[MAXPGPATH]; |
4889 | |
|
4890 | 0 | ReorderBufferSerializedPath(path, MyReplicationSlot, txn->xid, cur); |
4891 | 0 | if (unlink(path) != 0 && errno != ENOENT) |
4892 | 0 | ereport(ERROR, |
4893 | 0 | (errcode_for_file_access(), |
4894 | 0 | errmsg("could not remove file \"%s\": %m", path))); |
4895 | 0 | } |
4896 | 0 | } |
4897 | | |
4898 | | /* |
4899 | | * Remove any leftover serialized reorder buffers from a slot directory after a |
4900 | | * prior crash or decoding session exit. |
4901 | | */ |
4902 | | static void |
4903 | | ReorderBufferCleanupSerializedTXNs(const char *slotname) |
4904 | 0 | { |
4905 | 0 | DIR *spill_dir; |
4906 | 0 | struct dirent *spill_de; |
4907 | 0 | struct stat statbuf; |
4908 | 0 | char path[MAXPGPATH * 2 + sizeof(PG_REPLSLOT_DIR)]; |
4909 | |
|
4910 | 0 | sprintf(path, "%s/%s", PG_REPLSLOT_DIR, slotname); |
4911 | | |
4912 | | /* we're only handling directories here, skip if it's not ours */ |
4913 | 0 | if (lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode)) |
4914 | 0 | return; |
4915 | | |
4916 | 0 | spill_dir = AllocateDir(path); |
4917 | 0 | while ((spill_de = ReadDirExtended(spill_dir, path, INFO)) != NULL) |
4918 | 0 | { |
4919 | | /* only look at names that can be ours */ |
4920 | 0 | if (strncmp(spill_de->d_name, "xid", 3) == 0) |
4921 | 0 | { |
4922 | 0 | snprintf(path, sizeof(path), |
4923 | 0 | "%s/%s/%s", PG_REPLSLOT_DIR, slotname, |
4924 | 0 | spill_de->d_name); |
4925 | |
|
4926 | 0 | if (unlink(path) != 0) |
4927 | 0 | ereport(ERROR, |
4928 | 0 | (errcode_for_file_access(), |
4929 | 0 | errmsg("could not remove file \"%s\" during removal of %s/%s/xid*: %m", |
4930 | 0 | path, PG_REPLSLOT_DIR, slotname))); |
4931 | 0 | } |
4932 | 0 | } |
4933 | 0 | FreeDir(spill_dir); |
4934 | 0 | } |
4935 | | |
4936 | | /* |
4937 | | * Given a replication slot, transaction ID and segment number, fill in the |
4938 | | * corresponding spill file into 'path', which is a caller-owned buffer of size |
4939 | | * at least MAXPGPATH. |
4940 | | */ |
4941 | | static void |
4942 | | ReorderBufferSerializedPath(char *path, ReplicationSlot *slot, TransactionId xid, |
4943 | | XLogSegNo segno) |
4944 | 0 | { |
4945 | 0 | XLogRecPtr recptr; |
4946 | |
|
4947 | 0 | XLogSegNoOffsetToRecPtr(segno, 0, wal_segment_size, recptr); |
4948 | |
|
4949 | 0 | snprintf(path, MAXPGPATH, "%s/%s/xid-%u-lsn-%X-%X.spill", |
4950 | 0 | PG_REPLSLOT_DIR, |
4951 | 0 | NameStr(MyReplicationSlot->data.name), |
4952 | 0 | xid, LSN_FORMAT_ARGS(recptr)); |
4953 | 0 | } |
4954 | | |
4955 | | /* |
4956 | | * Delete all data spilled to disk after we've restarted/crashed. It will be |
4957 | | * recreated when the respective slots are reused. |
4958 | | */ |
4959 | | void |
4960 | | StartupReorderBuffer(void) |
4961 | 0 | { |
4962 | 0 | DIR *logical_dir; |
4963 | 0 | struct dirent *logical_de; |
4964 | |
|
4965 | 0 | logical_dir = AllocateDir(PG_REPLSLOT_DIR); |
4966 | 0 | while ((logical_de = ReadDir(logical_dir, PG_REPLSLOT_DIR)) != NULL) |
4967 | 0 | { |
4968 | 0 | if (strcmp(logical_de->d_name, ".") == 0 || |
4969 | 0 | strcmp(logical_de->d_name, "..") == 0) |
4970 | 0 | continue; |
4971 | | |
4972 | | /* if it cannot be a slot, skip the directory */ |
4973 | 0 | if (!ReplicationSlotValidateName(logical_de->d_name, true, DEBUG2)) |
4974 | 0 | continue; |
4975 | | |
4976 | | /* |
4977 | | * ok, has to be a surviving logical slot, iterate and delete |
4978 | | * everything starting with xid-* |
4979 | | */ |
4980 | 0 | ReorderBufferCleanupSerializedTXNs(logical_de->d_name); |
4981 | 0 | } |
4982 | 0 | FreeDir(logical_dir); |
4983 | 0 | } |
4984 | | |
4985 | | /* --------------------------------------- |
4986 | | * toast reassembly support |
4987 | | * --------------------------------------- |
4988 | | */ |
4989 | | |
4990 | | /* |
4991 | | * Initialize per tuple toast reconstruction support. |
4992 | | */ |
4993 | | static void |
4994 | | ReorderBufferToastInitHash(ReorderBuffer *rb, ReorderBufferTXN *txn) |
4995 | 0 | { |
4996 | 0 | HASHCTL hash_ctl; |
4997 | |
|
4998 | 0 | Assert(txn->toast_hash == NULL); |
4999 | |
|
5000 | 0 | hash_ctl.keysize = sizeof(Oid); |
5001 | 0 | hash_ctl.entrysize = sizeof(ReorderBufferToastEnt); |
5002 | 0 | hash_ctl.hcxt = rb->context; |
5003 | 0 | txn->toast_hash = hash_create("ReorderBufferToastHash", 5, &hash_ctl, |
5004 | 0 | HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); |
5005 | 0 | } |
5006 | | |
5007 | | /* |
5008 | | * Per toast-chunk handling for toast reconstruction |
5009 | | * |
5010 | | * Appends a toast chunk so we can reconstruct it when the tuple "owning" the |
5011 | | * toasted Datum comes along. |
5012 | | */ |
5013 | | static void |
5014 | | ReorderBufferToastAppendChunk(ReorderBuffer *rb, ReorderBufferTXN *txn, |
5015 | | Relation relation, ReorderBufferChange *change) |
5016 | 0 | { |
5017 | 0 | ReorderBufferToastEnt *ent; |
5018 | 0 | HeapTuple newtup; |
5019 | 0 | bool found; |
5020 | 0 | int32 chunksize; |
5021 | 0 | bool isnull; |
5022 | 0 | Pointer chunk; |
5023 | 0 | TupleDesc desc = RelationGetDescr(relation); |
5024 | 0 | Oid chunk_id; |
5025 | 0 | int32 chunk_seq; |
5026 | |
|
5027 | 0 | if (txn->toast_hash == NULL) |
5028 | 0 | ReorderBufferToastInitHash(rb, txn); |
5029 | |
|
5030 | 0 | Assert(IsToastRelation(relation)); |
5031 | |
|
5032 | 0 | newtup = change->data.tp.newtuple; |
5033 | 0 | chunk_id = DatumGetObjectId(fastgetattr(newtup, 1, desc, &isnull)); |
5034 | 0 | Assert(!isnull); |
5035 | 0 | chunk_seq = DatumGetInt32(fastgetattr(newtup, 2, desc, &isnull)); |
5036 | 0 | Assert(!isnull); |
5037 | |
|
5038 | 0 | ent = (ReorderBufferToastEnt *) |
5039 | 0 | hash_search(txn->toast_hash, &chunk_id, HASH_ENTER, &found); |
5040 | |
|
5041 | 0 | if (!found) |
5042 | 0 | { |
5043 | 0 | Assert(ent->chunk_id == chunk_id); |
5044 | 0 | ent->num_chunks = 0; |
5045 | 0 | ent->last_chunk_seq = 0; |
5046 | 0 | ent->size = 0; |
5047 | 0 | ent->reconstructed = NULL; |
5048 | 0 | dlist_init(&ent->chunks); |
5049 | |
|
5050 | 0 | if (chunk_seq != 0) |
5051 | 0 | elog(ERROR, "got sequence entry %d for toast chunk %u instead of seq 0", |
5052 | 0 | chunk_seq, chunk_id); |
5053 | 0 | } |
5054 | 0 | else if (found && chunk_seq != ent->last_chunk_seq + 1) |
5055 | 0 | elog(ERROR, "got sequence entry %d for toast chunk %u instead of seq %d", |
5056 | 0 | chunk_seq, chunk_id, ent->last_chunk_seq + 1); |
5057 | | |
5058 | 0 | chunk = DatumGetPointer(fastgetattr(newtup, 3, desc, &isnull)); |
5059 | 0 | Assert(!isnull); |
5060 | | |
5061 | | /* calculate size so we can allocate the right size at once later */ |
5062 | 0 | if (!VARATT_IS_EXTENDED(chunk)) |
5063 | 0 | chunksize = VARSIZE(chunk) - VARHDRSZ; |
5064 | 0 | else if (VARATT_IS_SHORT(chunk)) |
5065 | | /* could happen due to heap_form_tuple doing its thing */ |
5066 | 0 | chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT; |
5067 | 0 | else |
5068 | 0 | elog(ERROR, "unexpected type of toast chunk"); |
5069 | | |
5070 | 0 | ent->size += chunksize; |
5071 | 0 | ent->last_chunk_seq = chunk_seq; |
5072 | 0 | ent->num_chunks++; |
5073 | 0 | dlist_push_tail(&ent->chunks, &change->node); |
5074 | 0 | } |
5075 | | |
5076 | | /* |
5077 | | * Rejigger change->newtuple to point to in-memory toast tuples instead of |
5078 | | * on-disk toast tuples that may no longer exist (think DROP TABLE or VACUUM). |
5079 | | * |
5080 | | * We cannot replace unchanged toast tuples though, so those will still point |
5081 | | * to on-disk toast data. |
5082 | | * |
5083 | | * While updating the existing change with detoasted tuple data, we need to |
5084 | | * update the memory accounting info, because the change size will differ. |
5085 | | * Otherwise the accounting may get out of sync, triggering serialization |
5086 | | * at unexpected times. |
5087 | | * |
5088 | | * We simply subtract size of the change before rejiggering the tuple, and |
5089 | | * then add the new size. This makes it look like the change was removed |
5090 | | * and then added back, except it only tweaks the accounting info. |
5091 | | * |
5092 | | * In particular it can't trigger serialization, which would be pointless |
5093 | | * anyway as it happens during commit processing right before handing |
5094 | | * the change to the output plugin. |
5095 | | */ |
5096 | | static void |
5097 | | ReorderBufferToastReplace(ReorderBuffer *rb, ReorderBufferTXN *txn, |
5098 | | Relation relation, ReorderBufferChange *change) |
5099 | 0 | { |
5100 | 0 | TupleDesc desc; |
5101 | 0 | int natt; |
5102 | 0 | Datum *attrs; |
5103 | 0 | bool *isnull; |
5104 | 0 | bool *free; |
5105 | 0 | HeapTuple tmphtup; |
5106 | 0 | Relation toast_rel; |
5107 | 0 | TupleDesc toast_desc; |
5108 | 0 | MemoryContext oldcontext; |
5109 | 0 | HeapTuple newtup; |
5110 | 0 | Size old_size; |
5111 | | |
5112 | | /* no toast tuples changed */ |
5113 | 0 | if (txn->toast_hash == NULL) |
5114 | 0 | return; |
5115 | | |
5116 | | /* |
5117 | | * We're going to modify the size of the change. So, to make sure the |
5118 | | * accounting is correct we record the current change size and then after |
5119 | | * re-computing the change we'll subtract the recorded size and then |
5120 | | * re-add the new change size at the end. We don't immediately subtract |
5121 | | * the old size because if there is any error before we add the new size, |
5122 | | * we will release the changes and that will update the accounting info |
5123 | | * (subtracting the size from the counters). And we don't want to |
5124 | | * underflow there. |
5125 | | */ |
5126 | 0 | old_size = ReorderBufferChangeSize(change); |
5127 | |
|
5128 | 0 | oldcontext = MemoryContextSwitchTo(rb->context); |
5129 | | |
5130 | | /* we should only have toast tuples in an INSERT or UPDATE */ |
5131 | 0 | Assert(change->data.tp.newtuple); |
5132 | |
|
5133 | 0 | desc = RelationGetDescr(relation); |
5134 | |
|
5135 | 0 | toast_rel = RelationIdGetRelation(relation->rd_rel->reltoastrelid); |
5136 | 0 | if (!RelationIsValid(toast_rel)) |
5137 | 0 | elog(ERROR, "could not open toast relation with OID %u (base relation \"%s\")", |
5138 | 0 | relation->rd_rel->reltoastrelid, RelationGetRelationName(relation)); |
5139 | | |
5140 | 0 | toast_desc = RelationGetDescr(toast_rel); |
5141 | | |
5142 | | /* should we allocate from stack instead? */ |
5143 | 0 | attrs = palloc0_array(Datum, desc->natts); |
5144 | 0 | isnull = palloc0_array(bool, desc->natts); |
5145 | 0 | free = palloc0_array(bool, desc->natts); |
5146 | |
|
5147 | 0 | newtup = change->data.tp.newtuple; |
5148 | |
|
5149 | 0 | heap_deform_tuple(newtup, desc, attrs, isnull); |
5150 | |
|
5151 | 0 | for (natt = 0; natt < desc->natts; natt++) |
5152 | 0 | { |
5153 | 0 | CompactAttribute *attr = TupleDescCompactAttr(desc, natt); |
5154 | 0 | ReorderBufferToastEnt *ent; |
5155 | 0 | varlena *varlena_pointer; |
5156 | | |
5157 | | /* va_rawsize is the size of the original datum -- including header */ |
5158 | 0 | varatt_external toast_pointer; |
5159 | 0 | varatt_indirect redirect_pointer; |
5160 | 0 | varlena *new_datum = NULL; |
5161 | 0 | varlena *reconstructed; |
5162 | 0 | dlist_iter it; |
5163 | 0 | Size data_done = 0; |
5164 | |
|
5165 | 0 | if (attr->attisdropped) |
5166 | 0 | continue; |
5167 | | |
5168 | | /* not a varlena datatype */ |
5169 | 0 | if (attr->attlen != -1) |
5170 | 0 | continue; |
5171 | | |
5172 | | /* no data */ |
5173 | 0 | if (isnull[natt]) |
5174 | 0 | continue; |
5175 | | |
5176 | | /* ok, we know we have a toast datum */ |
5177 | 0 | varlena_pointer = (varlena *) DatumGetPointer(attrs[natt]); |
5178 | | |
5179 | | /* no need to do anything if the tuple isn't external */ |
5180 | 0 | if (!VARATT_IS_EXTERNAL(varlena_pointer)) |
5181 | 0 | continue; |
5182 | | |
5183 | 0 | VARATT_EXTERNAL_GET_POINTER(toast_pointer, varlena_pointer); |
5184 | | |
5185 | | /* |
5186 | | * Check whether the toast tuple changed, replace if so. |
5187 | | */ |
5188 | 0 | ent = (ReorderBufferToastEnt *) |
5189 | 0 | hash_search(txn->toast_hash, |
5190 | 0 | &toast_pointer.va_valueid, |
5191 | 0 | HASH_FIND, |
5192 | 0 | NULL); |
5193 | 0 | if (ent == NULL) |
5194 | 0 | continue; |
5195 | | |
5196 | 0 | new_datum = |
5197 | 0 | (varlena *) palloc0(INDIRECT_POINTER_SIZE); |
5198 | |
|
5199 | 0 | free[natt] = true; |
5200 | |
|
5201 | 0 | reconstructed = palloc0(toast_pointer.va_rawsize); |
5202 | |
|
5203 | 0 | ent->reconstructed = reconstructed; |
5204 | | |
5205 | | /* stitch toast tuple back together from its parts */ |
5206 | 0 | dlist_foreach(it, &ent->chunks) |
5207 | 0 | { |
5208 | 0 | bool cisnull; |
5209 | 0 | ReorderBufferChange *cchange; |
5210 | 0 | HeapTuple ctup; |
5211 | 0 | Pointer chunk; |
5212 | |
|
5213 | 0 | cchange = dlist_container(ReorderBufferChange, node, it.cur); |
5214 | 0 | ctup = cchange->data.tp.newtuple; |
5215 | 0 | chunk = DatumGetPointer(fastgetattr(ctup, 3, toast_desc, &cisnull)); |
5216 | |
|
5217 | 0 | Assert(!cisnull); |
5218 | 0 | Assert(!VARATT_IS_EXTERNAL(chunk)); |
5219 | 0 | Assert(!VARATT_IS_SHORT(chunk)); |
5220 | |
|
5221 | 0 | memcpy(VARDATA(reconstructed) + data_done, |
5222 | 0 | VARDATA(chunk), |
5223 | 0 | VARSIZE(chunk) - VARHDRSZ); |
5224 | 0 | data_done += VARSIZE(chunk) - VARHDRSZ; |
5225 | 0 | } |
5226 | 0 | Assert(data_done == VARATT_EXTERNAL_GET_EXTSIZE(toast_pointer)); |
5227 | | |
5228 | | /* make sure its marked as compressed or not */ |
5229 | 0 | if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer)) |
5230 | 0 | SET_VARSIZE_COMPRESSED(reconstructed, data_done + VARHDRSZ); |
5231 | 0 | else |
5232 | 0 | SET_VARSIZE(reconstructed, data_done + VARHDRSZ); |
5233 | |
|
5234 | 0 | memset(&redirect_pointer, 0, sizeof(redirect_pointer)); |
5235 | 0 | redirect_pointer.pointer = reconstructed; |
5236 | |
|
5237 | 0 | SET_VARTAG_EXTERNAL(new_datum, VARTAG_INDIRECT); |
5238 | 0 | memcpy(VARDATA_EXTERNAL(new_datum), &redirect_pointer, |
5239 | 0 | sizeof(redirect_pointer)); |
5240 | |
|
5241 | 0 | attrs[natt] = PointerGetDatum(new_datum); |
5242 | 0 | } |
5243 | | |
5244 | | /* |
5245 | | * Build tuple in separate memory & copy tuple back into the tuplebuf |
5246 | | * passed to the output plugin. We can't directly heap_fill_tuple() into |
5247 | | * the tuplebuf because attrs[] will point back into the current content. |
5248 | | */ |
5249 | 0 | tmphtup = heap_form_tuple(desc, attrs, isnull); |
5250 | 0 | Assert(newtup->t_len <= MaxHeapTupleSize); |
5251 | 0 | Assert(newtup->t_data == (HeapTupleHeader) ((char *) newtup + HEAPTUPLESIZE)); |
5252 | |
|
5253 | 0 | memcpy(newtup->t_data, tmphtup->t_data, tmphtup->t_len); |
5254 | 0 | newtup->t_len = tmphtup->t_len; |
5255 | | |
5256 | | /* |
5257 | | * free resources we won't further need, more persistent stuff will be |
5258 | | * free'd in ReorderBufferToastReset(). |
5259 | | */ |
5260 | 0 | RelationClose(toast_rel); |
5261 | 0 | pfree(tmphtup); |
5262 | 0 | for (natt = 0; natt < desc->natts; natt++) |
5263 | 0 | { |
5264 | 0 | if (free[natt]) |
5265 | 0 | pfree(DatumGetPointer(attrs[natt])); |
5266 | 0 | } |
5267 | 0 | pfree(attrs); |
5268 | 0 | pfree(free); |
5269 | 0 | pfree(isnull); |
5270 | |
|
5271 | 0 | MemoryContextSwitchTo(oldcontext); |
5272 | | |
5273 | | /* subtract the old change size */ |
5274 | 0 | ReorderBufferChangeMemoryUpdate(rb, change, NULL, false, old_size); |
5275 | | /* now add the change back, with the correct size */ |
5276 | 0 | ReorderBufferChangeMemoryUpdate(rb, change, NULL, true, |
5277 | 0 | ReorderBufferChangeSize(change)); |
5278 | 0 | } |
5279 | | |
5280 | | /* |
5281 | | * Free all resources allocated for toast reconstruction. |
5282 | | */ |
5283 | | static void |
5284 | | ReorderBufferToastReset(ReorderBuffer *rb, ReorderBufferTXN *txn) |
5285 | 0 | { |
5286 | 0 | HASH_SEQ_STATUS hstat; |
5287 | 0 | ReorderBufferToastEnt *ent; |
5288 | |
|
5289 | 0 | if (txn->toast_hash == NULL) |
5290 | 0 | return; |
5291 | | |
5292 | | /* sequentially walk over the hash and free everything */ |
5293 | 0 | hash_seq_init(&hstat, txn->toast_hash); |
5294 | 0 | while ((ent = (ReorderBufferToastEnt *) hash_seq_search(&hstat)) != NULL) |
5295 | 0 | { |
5296 | 0 | dlist_mutable_iter it; |
5297 | |
|
5298 | 0 | if (ent->reconstructed != NULL) |
5299 | 0 | pfree(ent->reconstructed); |
5300 | |
|
5301 | 0 | dlist_foreach_modify(it, &ent->chunks) |
5302 | 0 | { |
5303 | 0 | ReorderBufferChange *change = |
5304 | 0 | dlist_container(ReorderBufferChange, node, it.cur); |
5305 | |
|
5306 | 0 | dlist_delete(&change->node); |
5307 | 0 | ReorderBufferFreeChange(rb, change, true); |
5308 | 0 | } |
5309 | 0 | } |
5310 | |
|
5311 | 0 | hash_destroy(txn->toast_hash); |
5312 | 0 | txn->toast_hash = NULL; |
5313 | 0 | } |
5314 | | |
5315 | | |
5316 | | /* --------------------------------------- |
5317 | | * Visibility support for logical decoding |
5318 | | * |
5319 | | * |
5320 | | * Lookup actual cmin/cmax values when using decoding snapshot. We can't |
5321 | | * always rely on stored cmin/cmax values because of two scenarios: |
5322 | | * |
5323 | | * * A tuple got changed multiple times during a single transaction and thus |
5324 | | * has got a combo CID. Combo CIDs are only valid for the duration of a |
5325 | | * single transaction. |
5326 | | * * A tuple with a cmin but no cmax (and thus no combo CID) got |
5327 | | * deleted/updated in another transaction than the one which created it |
5328 | | * which we are looking at right now. As only one of cmin, cmax or combo CID |
5329 | | * is actually stored in the heap we don't have access to the value we |
5330 | | * need anymore. |
5331 | | * |
5332 | | * To resolve those problems we have a per-transaction hash of (cmin, |
5333 | | * cmax) tuples keyed by (relfilelocator, ctid) which contains the actual |
5334 | | * (cmin, cmax) values. That also takes care of combo CIDs by simply |
5335 | | * not caring about them at all. As we have the real cmin/cmax values |
5336 | | * combo CIDs aren't interesting. |
5337 | | * |
5338 | | * As we only care about catalog tuples here the overhead of this |
5339 | | * hashtable should be acceptable. |
5340 | | * |
5341 | | * Heap rewrites complicate this a bit, check rewriteheap.c for |
5342 | | * details. |
5343 | | * ------------------------------------------------------------------------- |
5344 | | */ |
5345 | | |
5346 | | /* struct for sorting mapping files by LSN efficiently */ |
5347 | | typedef struct RewriteMappingFile |
5348 | | { |
5349 | | XLogRecPtr lsn; |
5350 | | char fname[MAXPGPATH]; |
5351 | | } RewriteMappingFile; |
5352 | | |
5353 | | #ifdef NOT_USED |
5354 | | static void |
5355 | | DisplayMapping(HTAB *tuplecid_data) |
5356 | | { |
5357 | | HASH_SEQ_STATUS hstat; |
5358 | | ReorderBufferTupleCidEnt *ent; |
5359 | | |
5360 | | hash_seq_init(&hstat, tuplecid_data); |
5361 | | while ((ent = (ReorderBufferTupleCidEnt *) hash_seq_search(&hstat)) != NULL) |
5362 | | { |
5363 | | elog(DEBUG3, "mapping: node: %u/%u/%u tid: %u/%u cmin: %u, cmax: %u", |
5364 | | ent->key.rlocator.dbOid, |
5365 | | ent->key.rlocator.spcOid, |
5366 | | ent->key.rlocator.relNumber, |
5367 | | ItemPointerGetBlockNumber(&ent->key.tid), |
5368 | | ItemPointerGetOffsetNumber(&ent->key.tid), |
5369 | | ent->cmin, |
5370 | | ent->cmax |
5371 | | ); |
5372 | | } |
5373 | | } |
5374 | | #endif |
5375 | | |
5376 | | /* |
5377 | | * Apply a single mapping file to tuplecid_data. |
5378 | | * |
5379 | | * The mapping file has to have been verified to be a) committed b) for our |
5380 | | * transaction c) applied in LSN order. |
5381 | | */ |
5382 | | static void |
5383 | | ApplyLogicalMappingFile(HTAB *tuplecid_data, const char *fname) |
5384 | 0 | { |
5385 | 0 | char path[MAXPGPATH]; |
5386 | 0 | int fd; |
5387 | 0 | ssize_t readBytes; |
5388 | 0 | LogicalRewriteMappingData map; |
5389 | |
|
5390 | 0 | sprintf(path, "%s/%s", PG_LOGICAL_MAPPINGS_DIR, fname); |
5391 | 0 | fd = OpenTransientFile(path, O_RDONLY | PG_BINARY); |
5392 | 0 | if (fd < 0) |
5393 | 0 | ereport(ERROR, |
5394 | 0 | (errcode_for_file_access(), |
5395 | 0 | errmsg("could not open file \"%s\": %m", path))); |
5396 | | |
5397 | 0 | while (true) |
5398 | 0 | { |
5399 | 0 | ReorderBufferTupleCidKey key; |
5400 | 0 | ReorderBufferTupleCidEnt *ent; |
5401 | 0 | ReorderBufferTupleCidEnt *new_ent; |
5402 | 0 | bool found; |
5403 | | |
5404 | | /* be careful about padding */ |
5405 | 0 | memset(&key, 0, sizeof(ReorderBufferTupleCidKey)); |
5406 | | |
5407 | | /* read all mappings till the end of the file */ |
5408 | 0 | pgstat_report_wait_start(WAIT_EVENT_REORDER_LOGICAL_MAPPING_READ); |
5409 | 0 | readBytes = read(fd, &map, sizeof(LogicalRewriteMappingData)); |
5410 | 0 | pgstat_report_wait_end(); |
5411 | |
|
5412 | 0 | if (readBytes < 0) |
5413 | 0 | ereport(ERROR, |
5414 | 0 | (errcode_for_file_access(), |
5415 | 0 | errmsg("could not read file \"%s\": %m", |
5416 | 0 | path))); |
5417 | 0 | else if (readBytes == 0) /* EOF */ |
5418 | 0 | break; |
5419 | 0 | else if (readBytes != sizeof(LogicalRewriteMappingData)) |
5420 | 0 | ereport(ERROR, |
5421 | 0 | (errcode_for_file_access(), |
5422 | 0 | errmsg("could not read from file \"%s\": read %zd of %zu", |
5423 | 0 | path, readBytes, |
5424 | 0 | sizeof(LogicalRewriteMappingData)))); |
5425 | | |
5426 | 0 | key.rlocator = map.old_locator; |
5427 | 0 | ItemPointerCopy(&map.old_tid, |
5428 | 0 | &key.tid); |
5429 | | |
5430 | |
|
5431 | 0 | ent = (ReorderBufferTupleCidEnt *) |
5432 | 0 | hash_search(tuplecid_data, &key, HASH_FIND, NULL); |
5433 | | |
5434 | | /* no existing mapping, no need to update */ |
5435 | 0 | if (!ent) |
5436 | 0 | continue; |
5437 | | |
5438 | 0 | key.rlocator = map.new_locator; |
5439 | 0 | ItemPointerCopy(&map.new_tid, |
5440 | 0 | &key.tid); |
5441 | |
|
5442 | 0 | new_ent = (ReorderBufferTupleCidEnt *) |
5443 | 0 | hash_search(tuplecid_data, &key, HASH_ENTER, &found); |
5444 | |
|
5445 | 0 | if (found) |
5446 | 0 | { |
5447 | | /* |
5448 | | * Make sure the existing mapping makes sense. We sometime update |
5449 | | * old records that did not yet have a cmax (e.g. pg_class' own |
5450 | | * entry while rewriting it) during rewrites, so allow that. |
5451 | | */ |
5452 | 0 | Assert(ent->cmin == InvalidCommandId || ent->cmin == new_ent->cmin); |
5453 | 0 | Assert(ent->cmax == InvalidCommandId || ent->cmax == new_ent->cmax); |
5454 | 0 | } |
5455 | 0 | else |
5456 | 0 | { |
5457 | | /* update mapping */ |
5458 | 0 | new_ent->cmin = ent->cmin; |
5459 | 0 | new_ent->cmax = ent->cmax; |
5460 | 0 | new_ent->combocid = ent->combocid; |
5461 | 0 | } |
5462 | 0 | } |
5463 | | |
5464 | 0 | if (CloseTransientFile(fd) != 0) |
5465 | 0 | ereport(ERROR, |
5466 | 0 | (errcode_for_file_access(), |
5467 | 0 | errmsg("could not close file \"%s\": %m", path))); |
5468 | 0 | } |
5469 | | |
5470 | | |
5471 | | /* |
5472 | | * Check whether the TransactionId 'xid' is in the pre-sorted array 'xip'. |
5473 | | */ |
5474 | | static bool |
5475 | | TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num) |
5476 | 0 | { |
5477 | 0 | return bsearch(&xid, xip, num, |
5478 | 0 | sizeof(TransactionId), xidComparator) != NULL; |
5479 | 0 | } |
5480 | | |
5481 | | /* |
5482 | | * list_sort() comparator for sorting RewriteMappingFiles in LSN order. |
5483 | | */ |
5484 | | static int |
5485 | | file_sort_by_lsn(const ListCell *a_p, const ListCell *b_p) |
5486 | 0 | { |
5487 | 0 | RewriteMappingFile *a = (RewriteMappingFile *) lfirst(a_p); |
5488 | 0 | RewriteMappingFile *b = (RewriteMappingFile *) lfirst(b_p); |
5489 | |
|
5490 | 0 | return pg_cmp_u64(a->lsn, b->lsn); |
5491 | 0 | } |
5492 | | |
5493 | | /* |
5494 | | * Apply any existing logical remapping files if there are any targeted at our |
5495 | | * transaction for relid. |
5496 | | */ |
5497 | | static void |
5498 | | UpdateLogicalMappings(HTAB *tuplecid_data, Oid relid, Snapshot snapshot) |
5499 | | { |
5500 | | DIR *mapping_dir; |
5501 | | struct dirent *mapping_de; |
5502 | | List *files = NIL; |
5503 | | ListCell *file; |
5504 | | Oid dboid = IsSharedRelation(relid) ? InvalidOid : MyDatabaseId; |
5505 | | |
5506 | | mapping_dir = AllocateDir(PG_LOGICAL_MAPPINGS_DIR); |
5507 | | while ((mapping_de = ReadDir(mapping_dir, PG_LOGICAL_MAPPINGS_DIR)) != NULL) |
5508 | | { |
5509 | | Oid f_dboid; |
5510 | | Oid f_relid; |
5511 | | TransactionId f_mapped_xid; |
5512 | | TransactionId f_create_xid; |
5513 | | XLogRecPtr f_lsn; |
5514 | | uint32 f_hi, |
5515 | | f_lo; |
5516 | | RewriteMappingFile *f; |
5517 | | |
5518 | | if (strcmp(mapping_de->d_name, ".") == 0 || |
5519 | | strcmp(mapping_de->d_name, "..") == 0) |
5520 | | continue; |
5521 | | |
5522 | | /* Ignore files that aren't ours */ |
5523 | | if (strncmp(mapping_de->d_name, "map-", 4) != 0) |
5524 | | continue; |
5525 | | |
5526 | | if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT, |
5527 | | &f_dboid, &f_relid, &f_hi, &f_lo, |
5528 | | &f_mapped_xid, &f_create_xid) != 6) |
5529 | | elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name); |
5530 | | |
5531 | | f_lsn = ((uint64) f_hi) << 32 | f_lo; |
5532 | | |
5533 | | /* mapping for another database */ |
5534 | | if (f_dboid != dboid) |
5535 | | continue; |
5536 | | |
5537 | | /* mapping for another relation */ |
5538 | | if (f_relid != relid) |
5539 | | continue; |
5540 | | |
5541 | | /* did the creating transaction abort? */ |
5542 | | if (!TransactionIdDidCommit(f_create_xid)) |
5543 | | continue; |
5544 | | |
5545 | | /* not for our transaction */ |
5546 | | if (!TransactionIdInArray(f_mapped_xid, snapshot->subxip, snapshot->subxcnt)) |
5547 | | continue; |
5548 | | |
5549 | | /* ok, relevant, queue for apply */ |
5550 | | f = palloc_object(RewriteMappingFile); |
5551 | | f->lsn = f_lsn; |
5552 | | strcpy(f->fname, mapping_de->d_name); |
5553 | | files = lappend(files, f); |
5554 | | } |
5555 | | FreeDir(mapping_dir); |
5556 | | |
5557 | | /* sort files so we apply them in LSN order */ |
5558 | | list_sort(files, file_sort_by_lsn); |
5559 | | |
5560 | | foreach(file, files) |
5561 | | { |
5562 | | RewriteMappingFile *f = (RewriteMappingFile *) lfirst(file); |
5563 | | |
5564 | | elog(DEBUG1, "applying mapping: \"%s\" in %u", f->fname, |
5565 | | snapshot->subxip[0]); |
5566 | | ApplyLogicalMappingFile(tuplecid_data, f->fname); |
5567 | | pfree(f); |
5568 | | } |
5569 | | } |
5570 | | |
5571 | | /* |
5572 | | * Lookup cmin/cmax of a tuple, during logical decoding where we can't rely on |
5573 | | * combo CIDs. |
5574 | | */ |
5575 | | bool |
5576 | | ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data, |
5577 | | Snapshot snapshot, |
5578 | | HeapTuple htup, Buffer buffer, |
5579 | | CommandId *cmin, CommandId *cmax) |
5580 | 0 | { |
5581 | 0 | ReorderBufferTupleCidKey key; |
5582 | 0 | ReorderBufferTupleCidEnt *ent; |
5583 | 0 | ForkNumber forkno; |
5584 | 0 | BlockNumber blockno; |
5585 | 0 | bool updated_mapping = false; |
5586 | | |
5587 | | /* |
5588 | | * Return unresolved if tuplecid_data is not valid. That's because when |
5589 | | * streaming in-progress transactions we may run into tuples with the CID |
5590 | | * before actually decoding them. Think e.g. about INSERT followed by |
5591 | | * TRUNCATE, where the TRUNCATE may not be decoded yet when applying the |
5592 | | * INSERT. So in such cases, we assume the CID is from the future |
5593 | | * command. |
5594 | | */ |
5595 | 0 | if (tuplecid_data == NULL) |
5596 | 0 | return false; |
5597 | | |
5598 | | /* be careful about padding */ |
5599 | 0 | memset(&key, 0, sizeof(key)); |
5600 | |
|
5601 | 0 | Assert(!BufferIsLocal(buffer)); |
5602 | | |
5603 | | /* |
5604 | | * get relfilelocator from the buffer, no convenient way to access it |
5605 | | * other than that. |
5606 | | */ |
5607 | 0 | BufferGetTag(buffer, &key.rlocator, &forkno, &blockno); |
5608 | | |
5609 | | /* tuples can only be in the main fork */ |
5610 | 0 | Assert(forkno == MAIN_FORKNUM); |
5611 | 0 | Assert(blockno == ItemPointerGetBlockNumber(&htup->t_self)); |
5612 | |
|
5613 | 0 | ItemPointerCopy(&htup->t_self, |
5614 | 0 | &key.tid); |
5615 | |
|
5616 | 0 | restart: |
5617 | 0 | ent = (ReorderBufferTupleCidEnt *) |
5618 | 0 | hash_search(tuplecid_data, &key, HASH_FIND, NULL); |
5619 | | |
5620 | | /* |
5621 | | * failed to find a mapping, check whether the table was rewritten and |
5622 | | * apply mapping if so, but only do that once - there can be no new |
5623 | | * mappings while we are in here since we have to hold a lock on the |
5624 | | * relation. |
5625 | | */ |
5626 | 0 | if (ent == NULL && !updated_mapping) |
5627 | 0 | { |
5628 | 0 | UpdateLogicalMappings(tuplecid_data, htup->t_tableOid, snapshot); |
5629 | | /* now check but don't update for a mapping again */ |
5630 | 0 | updated_mapping = true; |
5631 | 0 | goto restart; |
5632 | 0 | } |
5633 | 0 | else if (ent == NULL) |
5634 | 0 | return false; |
5635 | | |
5636 | 0 | if (cmin) |
5637 | 0 | *cmin = ent->cmin; |
5638 | 0 | if (cmax) |
5639 | 0 | *cmax = ent->cmax; |
5640 | 0 | return true; |
5641 | 0 | } |
5642 | | |
5643 | | /* |
5644 | | * Count invalidation messages of specified transaction. |
5645 | | * |
5646 | | * Returns number of messages, and msgs is set to the pointer of the linked |
5647 | | * list for the messages. |
5648 | | */ |
5649 | | uint32 |
5650 | | ReorderBufferGetInvalidations(ReorderBuffer *rb, TransactionId xid, |
5651 | | SharedInvalidationMessage **msgs) |
5652 | 0 | { |
5653 | 0 | ReorderBufferTXN *txn; |
5654 | |
|
5655 | 0 | txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr, |
5656 | 0 | false); |
5657 | |
|
5658 | 0 | if (txn == NULL) |
5659 | 0 | return 0; |
5660 | | |
5661 | 0 | *msgs = txn->invalidations; |
5662 | |
|
5663 | 0 | return txn->ninvalidations; |
5664 | 0 | } |