Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * subtrans.c
4 : : * PostgreSQL subtransaction-log manager
5 : : *
6 : : * The pg_subtrans manager is a pg_xact-like manager that stores the parent
7 : : * transaction Id for each transaction. It is a fundamental part of the
8 : : * nested transactions implementation. A main transaction has a parent
9 : : * of InvalidTransactionId, and each subtransaction has its immediate parent.
10 : : * The tree can easily be walked from child to parent, but not in the
11 : : * opposite direction.
12 : : *
13 : : * This code is based on xact.c, but the robustness requirements
14 : : * are completely different from pg_xact, because we only need to remember
15 : : * pg_subtrans information for currently-open transactions. Thus, there is
16 : : * no need to preserve data over a crash and restart.
17 : : *
18 : : * There are no XLOG interactions since we do not care about preserving
19 : : * data across crashes. During database startup, we simply force the
20 : : * currently-active page of SUBTRANS to zeroes.
21 : : *
22 : : * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
23 : : * Portions Copyright (c) 1994, Regents of the University of California
24 : : *
25 : : * src/backend/access/transam/subtrans.c
26 : : *
27 : : *-------------------------------------------------------------------------
28 : : */
29 : : #include "postgres.h"
30 : :
31 : : #include "access/slru.h"
32 : : #include "access/subtrans.h"
33 : : #include "access/transam.h"
34 : : #include "miscadmin.h"
35 : : #include "pg_trace.h"
36 : : #include "utils/guc_hooks.h"
37 : : #include "utils/snapmgr.h"
38 : :
39 : :
40 : : /*
41 : : * Defines for SubTrans page sizes. A page is the same BLCKSZ as is used
42 : : * everywhere else in Postgres.
43 : : *
44 : : * Note: because TransactionIds are 32 bits and wrap around at 0xFFFFFFFF,
45 : : * SubTrans page numbering also wraps around at
46 : : * 0xFFFFFFFF/SUBTRANS_XACTS_PER_PAGE, and segment numbering at
47 : : * 0xFFFFFFFF/SUBTRANS_XACTS_PER_PAGE/SLRU_PAGES_PER_SEGMENT. We need take no
48 : : * explicit notice of that fact in this module, except when comparing segment
49 : : * and page numbers in TruncateSUBTRANS (see SubTransPagePrecedes) and zeroing
50 : : * them in StartupSUBTRANS.
51 : : */
52 : :
53 : : /* We need four bytes per xact */
54 : : #define SUBTRANS_XACTS_PER_PAGE (BLCKSZ / sizeof(TransactionId))
55 : :
56 : : /*
57 : : * Although we return an int64 the actual value can't currently exceed
58 : : * 0xFFFFFFFF/SUBTRANS_XACTS_PER_PAGE.
59 : : */
60 : : static inline int64
137 akorotkov@postgresql 61 :GNC 6919350 : TransactionIdToPage(TransactionId xid)
62 : : {
63 : 6919350 : return xid / (int64) SUBTRANS_XACTS_PER_PAGE;
64 : : }
65 : :
66 : : #define TransactionIdToEntry(xid) ((xid) % (TransactionId) SUBTRANS_XACTS_PER_PAGE)
67 : :
68 : :
69 : : /*
70 : : * Link to shared-memory data structures for SUBTRANS control
71 : : */
72 : : static SlruCtlData SubTransCtlData;
73 : :
74 : : #define SubTransCtl (&SubTransCtlData)
75 : :
76 : :
77 : : static int ZeroSUBTRANSPage(int64 pageno);
78 : : static bool SubTransPagePrecedes(int64 page1, int64 page2);
79 : :
80 : :
81 : : /*
82 : : * Record the parent of a subtransaction in the subtrans log.
83 : : */
84 : : void
2544 simon@2ndQuadrant.co 85 :CBC 7013 : SubTransSetParent(TransactionId xid, TransactionId parent)
86 : : {
137 akorotkov@postgresql 87 :GNC 7013 : int64 pageno = TransactionIdToPage(xid);
7227 tgl@sss.pgh.pa.us 88 :CBC 7013 : int entryno = TransactionIdToEntry(xid);
89 : : int slotno;
90 : : LWLock *lock;
91 : : TransactionId *ptr;
92 : :
5230 simon@2ndQuadrant.co 93 [ - + ]: 7013 : Assert(TransactionIdIsValid(parent));
2544 94 [ - + ]: 7013 : Assert(TransactionIdFollows(xid, parent));
95 : :
46 alvherre@alvh.no-ip. 96 :GNC 7013 : lock = SimpleLruGetBankLock(SubTransCtl, pageno);
97 : 7013 : LWLockAcquire(lock, LW_EXCLUSIVE);
98 : :
6101 tgl@sss.pgh.pa.us 99 :CBC 7013 : slotno = SimpleLruReadPage(SubTransCtl, pageno, true, xid);
7174 100 : 7013 : ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
7227 101 : 7013 : ptr += entryno;
102 : :
103 : : /*
104 : : * It's possible we'll try to set the parent xid multiple times but we
105 : : * shouldn't ever be changing the xid from one valid xid to another valid
106 : : * xid, which would corrupt the data structure.
107 : : */
2544 simon@2ndQuadrant.co 108 [ + + ]: 7013 : if (*ptr != parent)
109 : : {
110 [ - + ]: 6757 : Assert(*ptr == InvalidTransactionId);
111 : 6757 : *ptr = parent;
112 : 6757 : SubTransCtl->shared->page_dirty[slotno] = true;
113 : : }
114 : :
46 alvherre@alvh.no-ip. 115 :GNC 7013 : LWLockRelease(lock);
7227 tgl@sss.pgh.pa.us 116 :CBC 7013 : }
117 : :
118 : : /*
119 : : * Interrogate the parent of a transaction in the subtrans log.
120 : : */
121 : : TransactionId
122 : 3089 : SubTransGetParent(TransactionId xid)
123 : : {
137 akorotkov@postgresql 124 :GNC 3089 : int64 pageno = TransactionIdToPage(xid);
7227 tgl@sss.pgh.pa.us 125 :CBC 3089 : int entryno = TransactionIdToEntry(xid);
126 : : int slotno;
127 : : TransactionId *ptr;
128 : : TransactionId parent;
129 : :
130 : : /* Can't ask about stuff that might not be around anymore */
7150 131 [ - + ]: 3089 : Assert(TransactionIdFollowsOrEquals(xid, TransactionXmin));
132 : :
133 : : /* Bootstrap and frozen XIDs have no parent */
7227 134 [ - + ]: 3089 : if (!TransactionIdIsNormal(xid))
7227 tgl@sss.pgh.pa.us 135 :UBC 0 : return InvalidTransactionId;
136 : :
137 : : /* lock is acquired by SimpleLruReadPage_ReadOnly */
138 : :
6704 tgl@sss.pgh.pa.us 139 :CBC 3089 : slotno = SimpleLruReadPage_ReadOnly(SubTransCtl, pageno, xid);
7174 140 : 3089 : ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
7227 141 : 3089 : ptr += entryno;
142 : :
143 : 3089 : parent = *ptr;
144 : :
46 alvherre@alvh.no-ip. 145 :GNC 3089 : LWLockRelease(SimpleLruGetBankLock(SubTransCtl, pageno));
146 : :
7227 tgl@sss.pgh.pa.us 147 :CBC 3089 : return parent;
148 : : }
149 : :
150 : : /*
151 : : * SubTransGetTopmostTransaction
152 : : *
153 : : * Returns the topmost transaction of the given transaction id.
154 : : *
155 : : * Because we cannot look back further than TransactionXmin, it is possible
156 : : * that this function will lie and return an intermediate subtransaction ID
157 : : * instead of the true topmost parent ID. This is OK, because in practice
158 : : * we only care about detecting whether the topmost parent is still running
159 : : * or is part of a current snapshot's list of still-running transactions.
160 : : * Therefore, any XID before TransactionXmin is as good as any other.
161 : : */
162 : : TransactionId
163 : 1081 : SubTransGetTopmostTransaction(TransactionId xid)
164 : : {
165 : 1081 : TransactionId parentXid = xid,
7168 bruce@momjian.us 166 : 1081 : previousXid = xid;
167 : :
168 : : /* Can't ask about stuff that might not be around anymore */
7150 tgl@sss.pgh.pa.us 169 [ - + ]: 1081 : Assert(TransactionIdFollowsOrEquals(xid, TransactionXmin));
170 : :
7227 171 [ + + ]: 4170 : while (TransactionIdIsValid(parentXid))
172 : : {
173 : 3089 : previousXid = parentXid;
7150 174 [ - + ]: 3089 : if (TransactionIdPrecedes(parentXid, TransactionXmin))
7175 tgl@sss.pgh.pa.us 175 :UBC 0 : break;
7227 tgl@sss.pgh.pa.us 176 :CBC 3089 : parentXid = SubTransGetParent(parentXid);
177 : :
178 : : /*
179 : : * By convention the parent xid gets allocated first, so should always
180 : : * precede the child xid. Anything else points to a corrupted data
181 : : * structure that could lead to an infinite loop, so exit.
182 : : */
2544 simon@2ndQuadrant.co 183 [ - + ]: 3089 : if (!TransactionIdPrecedes(parentXid, previousXid))
2544 simon@2ndQuadrant.co 184 [ # # ]:UBC 0 : elog(ERROR, "pg_subtrans contains invalid entry: xid %u points to parent xid %u",
185 : : previousXid, parentXid);
186 : : }
187 : :
7227 tgl@sss.pgh.pa.us 188 [ - + ]:CBC 1081 : Assert(TransactionIdIsValid(previousXid));
189 : :
190 : 1081 : return previousXid;
191 : : }
192 : :
193 : : /*
194 : : * Number of shared SUBTRANS buffers.
195 : : *
196 : : * If asked to autotune, use 2MB for every 1GB of shared buffers, up to 8MB.
197 : : * Otherwise just cap the configured amount to be between 16 and the maximum
198 : : * allowed.
199 : : */
200 : : static int
46 alvherre@alvh.no-ip. 201 :GNC 3464 : SUBTRANSShmemBuffers(void)
202 : : {
203 : : /* auto-tune based on shared buffers */
204 [ + + ]: 3464 : if (subtransaction_buffers == 0)
205 : 2549 : return SimpleLruAutotuneBuffers(512, 1024);
206 : :
207 [ + - ]: 915 : return Min(Max(16, subtransaction_buffers), SLRU_MAX_ALLOWED_BUFFERS);
208 : : }
209 : :
210 : : /*
211 : : * Initialization of shared memory for SUBTRANS
212 : : */
213 : : Size
7227 tgl@sss.pgh.pa.us 214 :CBC 1679 : SUBTRANSShmemSize(void)
215 : : {
46 alvherre@alvh.no-ip. 216 :GNC 1679 : return SimpleLruShmemSize(SUBTRANSShmemBuffers(), 0);
217 : : }
218 : :
219 : : void
7227 tgl@sss.pgh.pa.us 220 :CBC 898 : SUBTRANSShmemInit(void)
221 : : {
222 : : /* If auto-tuning is requested, now is the time to do it */
46 alvherre@alvh.no-ip. 223 [ + + ]:GNC 898 : if (subtransaction_buffers == 0)
224 : : {
225 : : char buf[32];
226 : :
227 : 887 : snprintf(buf, sizeof(buf), "%d", SUBTRANSShmemBuffers());
228 : 887 : SetConfigOption("subtransaction_buffers", buf, PGC_POSTMASTER,
229 : : PGC_S_DYNAMIC_DEFAULT);
230 : :
231 : : /*
232 : : * We prefer to report this value's source as PGC_S_DYNAMIC_DEFAULT.
233 : : * However, if the DBA explicitly set subtransaction_buffers = 0 in
234 : : * the config file, then PGC_S_DYNAMIC_DEFAULT will fail to override
235 : : * that and we must force the matter with PGC_S_OVERRIDE.
236 : : */
237 [ - + ]: 887 : if (subtransaction_buffers == 0) /* failed to apply it? */
46 alvherre@alvh.no-ip. 238 :UNC 0 : SetConfigOption("subtransaction_buffers", buf, PGC_POSTMASTER,
239 : : PGC_S_OVERRIDE);
240 : : }
46 alvherre@alvh.no-ip. 241 [ - + ]:GNC 898 : Assert(subtransaction_buffers != 0);
242 : :
7227 tgl@sss.pgh.pa.us 243 :CBC 898 : SubTransCtl->PagePrecedes = SubTransPagePrecedes;
46 alvherre@alvh.no-ip. 244 :GNC 898 : SimpleLruInit(SubTransCtl, "subtransaction", SUBTRANSShmemBuffers(), 0,
245 : : "pg_subtrans", LWTRANCHE_SUBTRANS_BUFFER,
246 : : LWTRANCHE_SUBTRANS_SLRU, SYNC_HANDLER_NONE, false);
1184 noah@leadboat.com 247 :CBC 898 : SlruPagePrecedesUnitTests(SubTransCtl, SUBTRANS_XACTS_PER_PAGE);
7227 tgl@sss.pgh.pa.us 248 : 898 : }
249 : :
250 : : /*
251 : : * GUC check_hook for subtransaction_buffers
252 : : */
253 : : bool
46 alvherre@alvh.no-ip. 254 :GNC 1832 : check_subtrans_buffers(int *newval, void **extra, GucSource source)
255 : : {
256 : 1832 : return check_slru_buffers("subtransaction_buffers", newval);
257 : : }
258 : :
259 : : /*
260 : : * This func must be called ONCE on system install. It creates
261 : : * the initial SUBTRANS segment. (The SUBTRANS directory is assumed to
262 : : * have been created by the initdb shell script, and SUBTRANSShmemInit
263 : : * must have been called already.)
264 : : *
265 : : * Note: it's not really necessary to create the initial segment now,
266 : : * since slru.c would create it on first write anyway. But we may as well
267 : : * do it to be sure the directory is set up correctly.
268 : : */
269 : : void
7227 tgl@sss.pgh.pa.us 270 :CBC 39 : BootStrapSUBTRANS(void)
271 : : {
272 : : int slotno;
46 alvherre@alvh.no-ip. 273 :GNC 39 : LWLock *lock = SimpleLruGetBankLock(SubTransCtl, 0);
274 : :
275 : 39 : LWLockAcquire(lock, LW_EXCLUSIVE);
276 : :
277 : : /* Create and zero the first page of the subtrans log */
7174 tgl@sss.pgh.pa.us 278 :CBC 39 : slotno = ZeroSUBTRANSPage(0);
279 : :
280 : : /* Make sure it's written out */
4854 alvherre@alvh.no-ip. 281 : 39 : SimpleLruWritePage(SubTransCtl, slotno);
6735 tgl@sss.pgh.pa.us 282 [ - + ]: 39 : Assert(!SubTransCtl->shared->page_dirty[slotno]);
283 : :
46 alvherre@alvh.no-ip. 284 :GNC 39 : LWLockRelease(lock);
7227 tgl@sss.pgh.pa.us 285 :CBC 39 : }
286 : :
287 : : /*
288 : : * Initialize (or reinitialize) a page of SUBTRANS to zeroes.
289 : : *
290 : : * The page is not actually written, just set up in shared memory.
291 : : * The slot number of the new page is returned.
292 : : *
293 : : * Control lock must be held at entry, and will be held at exit.
294 : : */
295 : : static int
137 akorotkov@postgresql 296 :GNC 6907427 : ZeroSUBTRANSPage(int64 pageno)
297 : : {
7174 tgl@sss.pgh.pa.us 298 :CBC 6907427 : return SimpleLruZeroPage(SubTransCtl, pageno);
299 : : }
300 : :
301 : : /*
302 : : * This must be called ONCE during postmaster or standalone-backend startup,
303 : : * after StartupXLOG has initialized TransamVariables->nextXid.
304 : : *
305 : : * oldestActiveXID is the oldest XID of any prepared transaction, or nextXid
306 : : * if there are none.
307 : : */
308 : : void
6876 309 : 821 : StartupSUBTRANS(TransactionId oldestActiveXID)
310 : : {
311 : : FullTransactionId nextXid;
312 : : int64 startPage;
313 : : int64 endPage;
40 alvherre@alvh.no-ip. 314 :GNC 821 : LWLock *prevlock = NULL;
315 : : LWLock *lock;
316 : :
317 : : /*
318 : : * Since we don't expect pg_subtrans to be valid across crashes, we
319 : : * initialize the currently-active page(s) to zeroes during startup.
320 : : * Whenever we advance into a new page, ExtendSUBTRANS will likewise zero
321 : : * the new page without regard to whatever was previously on disk.
322 : : */
6876 tgl@sss.pgh.pa.us 323 :CBC 821 : startPage = TransactionIdToPage(oldestActiveXID);
128 heikki.linnakangas@i 324 :GNC 821 : nextXid = TransamVariables->nextXid;
1342 andres@anarazel.de 325 :CBC 821 : endPage = TransactionIdToPage(XidFromFullTransactionId(nextXid));
326 : :
327 : : for (;;)
328 : : {
46 alvherre@alvh.no-ip. 329 :GNC 823 : lock = SimpleLruGetBankLock(SubTransCtl, startPage);
330 [ + - ]: 823 : if (prevlock != lock)
331 : : {
40 332 [ + + ]: 823 : if (prevlock)
333 : 2 : LWLockRelease(prevlock);
46 334 : 823 : LWLockAcquire(lock, LW_EXCLUSIVE);
335 : 823 : prevlock = lock;
336 : : }
337 : :
6876 tgl@sss.pgh.pa.us 338 :GBC 823 : (void) ZeroSUBTRANSPage(startPage);
40 alvherre@alvh.no-ip. 339 [ + + ]:GNC 823 : if (startPage == endPage)
340 : 821 : break;
341 : :
6876 tgl@sss.pgh.pa.us 342 :GBC 2 : startPage++;
343 : : /* must account for wraparound */
2977 simon@2ndQuadrant.co 344 [ - + ]: 2 : if (startPage > TransactionIdToPage(MaxTransactionId))
2866 rhaas@postgresql.org 345 :UBC 0 : startPage = 0;
346 : : }
347 : :
46 alvherre@alvh.no-ip. 348 :GNC 821 : LWLockRelease(lock);
7227 tgl@sss.pgh.pa.us 349 :CBC 821 : }
350 : :
351 : : /*
352 : : * Perform a checkpoint --- either during shutdown, or on-the-fly
353 : : */
354 : : void
355 : 1153 : CheckPointSUBTRANS(void)
356 : : {
357 : : /*
358 : : * Write dirty SUBTRANS pages to disk
359 : : *
360 : : * This is not actually necessary from a correctness point of view. We do
361 : : * it merely to improve the odds that writing of dirty pages is done by
362 : : * the checkpoint process and not by backends.
363 : : */
364 : : TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_START(true);
1297 tmunro@postgresql.or 365 : 1153 : SimpleLruWriteAll(SubTransCtl, true);
366 : : TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_DONE(true);
7227 tgl@sss.pgh.pa.us 367 : 1153 : }
368 : :
369 : :
370 : : /*
371 : : * Make sure that SUBTRANS has room for a newly-allocated XID.
372 : : *
373 : : * NB: this is called while holding XidGenLock. We want it to be very fast
374 : : * most of the time; even when it's not so fast, no actual I/O need happen
375 : : * unless we're forced to write out a dirty subtrans page to make room
376 : : * in shared memory.
377 : : */
378 : : void
379 : 24504285 : ExtendSUBTRANS(TransactionId newestXact)
380 : : {
381 : : int64 pageno;
382 : : LWLock *lock;
383 : :
384 : : /*
385 : : * No work except at first XID of a page. But beware: just after
386 : : * wraparound, the first XID of page zero is FirstNormalTransactionId.
387 : : */
388 [ + + + + ]: 24504285 : if (TransactionIdToEntry(newestXact) != 0 &&
389 : : !TransactionIdEquals(newestXact, FirstNormalTransactionId))
390 : 17597720 : return;
391 : :
392 : 6906565 : pageno = TransactionIdToPage(newestXact);
393 : :
46 alvherre@alvh.no-ip. 394 :GNC 6906565 : lock = SimpleLruGetBankLock(SubTransCtl, pageno);
395 : 6906565 : LWLockAcquire(lock, LW_EXCLUSIVE);
396 : :
397 : : /* Zero the page */
7174 tgl@sss.pgh.pa.us 398 :CBC 6906565 : ZeroSUBTRANSPage(pageno);
399 : :
46 alvherre@alvh.no-ip. 400 :GNC 6906565 : LWLockRelease(lock);
401 : : }
402 : :
403 : :
404 : : /*
405 : : * Remove all SUBTRANS segments before the one holding the passed transaction ID
406 : : *
407 : : * oldestXact is the oldest TransactionXmin of any running transaction. This
408 : : * is called only during checkpoint.
409 : : */
410 : : void
7227 tgl@sss.pgh.pa.us 411 :CBC 1039 : TruncateSUBTRANS(TransactionId oldestXact)
412 : : {
413 : : int64 cutoffPage;
414 : :
415 : : /*
416 : : * The cutoff point is the start of the segment containing oldestXact. We
417 : : * pass the *page* containing oldestXact to SimpleLruTruncate. We step
418 : : * back one transaction to avoid passing a cutoff page that hasn't been
419 : : * created yet in the rare case that oldestXact would be the first item on
420 : : * a page and oldestXact == next XID. In that case, if we didn't subtract
421 : : * one, we'd trigger SimpleLruTruncate's wraparound detection.
422 : : */
3188 heikki.linnakangas@i 423 [ + + ]: 1162 : TransactionIdRetreat(oldestXact);
7227 tgl@sss.pgh.pa.us 424 : 1039 : cutoffPage = TransactionIdToPage(oldestXact);
425 : :
426 : 1039 : SimpleLruTruncate(SubTransCtl, cutoffPage);
427 : 1039 : }
428 : :
429 : :
430 : : /*
431 : : * Decide whether a SUBTRANS page number is "older" for truncation purposes.
432 : : * Analogous to CLOGPagePrecedes().
433 : : */
434 : : static bool
137 akorotkov@postgresql 435 :GNC 263126 : SubTransPagePrecedes(int64 page1, int64 page2)
436 : : {
437 : : TransactionId xid1;
438 : : TransactionId xid2;
439 : :
7227 tgl@sss.pgh.pa.us 440 :CBC 263126 : xid1 = ((TransactionId) page1) * SUBTRANS_XACTS_PER_PAGE;
1184 noah@leadboat.com 441 : 263126 : xid1 += FirstNormalTransactionId + 1;
7227 tgl@sss.pgh.pa.us 442 : 263126 : xid2 = ((TransactionId) page2) * SUBTRANS_XACTS_PER_PAGE;
1184 noah@leadboat.com 443 : 263126 : xid2 += FirstNormalTransactionId + 1;
444 : :
445 [ + + + + ]: 478682 : return (TransactionIdPrecedes(xid1, xid2) &&
446 : 215556 : TransactionIdPrecedes(xid1, xid2 + SUBTRANS_XACTS_PER_PAGE - 1));
447 : : }
|