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
61 13838072 : TransactionIdToPage(TransactionId xid)
62 : {
63 13838072 : 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
85 13034 : SubTransSetParent(TransactionId xid, TransactionId parent)
86 : {
87 13034 : int64 pageno = TransactionIdToPage(xid);
88 13034 : int entryno = TransactionIdToEntry(xid);
89 : int slotno;
90 : LWLock *lock;
91 : TransactionId *ptr;
92 :
93 : Assert(TransactionIdIsValid(parent));
94 : Assert(TransactionIdFollows(xid, parent));
95 :
96 13034 : lock = SimpleLruGetBankLock(SubTransCtl, pageno);
97 13034 : LWLockAcquire(lock, LW_EXCLUSIVE);
98 :
99 13034 : slotno = SimpleLruReadPage(SubTransCtl, pageno, true, xid);
100 13034 : ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
101 13034 : 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 : */
108 13034 : if (*ptr != parent)
109 : {
110 : Assert(*ptr == InvalidTransactionId);
111 11862 : *ptr = parent;
112 11862 : SubTransCtl->shared->page_dirty[slotno] = true;
113 : }
114 :
115 13034 : LWLockRelease(lock);
116 13034 : }
117 :
118 : /*
119 : * Interrogate the parent of a transaction in the subtrans log.
120 : */
121 : TransactionId
122 6208 : SubTransGetParent(TransactionId xid)
123 : {
124 6208 : int64 pageno = TransactionIdToPage(xid);
125 6208 : 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 */
131 : Assert(TransactionIdFollowsOrEquals(xid, TransactionXmin));
132 :
133 : /* Bootstrap and frozen XIDs have no parent */
134 6208 : if (!TransactionIdIsNormal(xid))
135 0 : return InvalidTransactionId;
136 :
137 : /* lock is acquired by SimpleLruReadPage_ReadOnly */
138 :
139 6208 : slotno = SimpleLruReadPage_ReadOnly(SubTransCtl, pageno, xid);
140 6208 : ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
141 6208 : ptr += entryno;
142 :
143 6208 : parent = *ptr;
144 :
145 6208 : LWLockRelease(SimpleLruGetBankLock(SubTransCtl, pageno));
146 :
147 6208 : 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 2200 : SubTransGetTopmostTransaction(TransactionId xid)
164 : {
165 2200 : TransactionId parentXid = xid,
166 2200 : previousXid = xid;
167 :
168 : /* Can't ask about stuff that might not be around anymore */
169 : Assert(TransactionIdFollowsOrEquals(xid, TransactionXmin));
170 :
171 8408 : while (TransactionIdIsValid(parentXid))
172 : {
173 6208 : previousXid = parentXid;
174 6208 : if (TransactionIdPrecedes(parentXid, TransactionXmin))
175 0 : break;
176 6208 : 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 : */
183 6208 : if (!TransactionIdPrecedes(parentXid, previousXid))
184 0 : elog(ERROR, "pg_subtrans contains invalid entry: xid %u points to parent xid %u",
185 : previousXid, parentXid);
186 : }
187 :
188 : Assert(TransactionIdIsValid(previousXid));
189 :
190 2200 : 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
201 7314 : SUBTRANSShmemBuffers(void)
202 : {
203 : /* auto-tune based on shared buffers */
204 7314 : if (subtransaction_buffers == 0)
205 5374 : return SimpleLruAutotuneBuffers(512, 1024);
206 :
207 1940 : return Min(Max(16, subtransaction_buffers), SLRU_MAX_ALLOWED_BUFFERS);
208 : }
209 :
210 : /*
211 : * Initialization of shared memory for SUBTRANS
212 : */
213 : Size
214 3534 : SUBTRANSShmemSize(void)
215 : {
216 3534 : return SimpleLruShmemSize(SUBTRANSShmemBuffers(), 0);
217 : }
218 :
219 : void
220 1902 : SUBTRANSShmemInit(void)
221 : {
222 : /* If auto-tuning is requested, now is the time to do it */
223 1902 : if (subtransaction_buffers == 0)
224 : {
225 : char buf[32];
226 :
227 1878 : snprintf(buf, sizeof(buf), "%d", SUBTRANSShmemBuffers());
228 1878 : 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 1878 : if (subtransaction_buffers == 0) /* failed to apply it? */
238 0 : SetConfigOption("subtransaction_buffers", buf, PGC_POSTMASTER,
239 : PGC_S_OVERRIDE);
240 : }
241 : Assert(subtransaction_buffers != 0);
242 :
243 1902 : SubTransCtl->PagePrecedes = SubTransPagePrecedes;
244 1902 : SimpleLruInit(SubTransCtl, "subtransaction", SUBTRANSShmemBuffers(), 0,
245 : "pg_subtrans", LWTRANCHE_SUBTRANS_BUFFER,
246 : LWTRANCHE_SUBTRANS_SLRU, SYNC_HANDLER_NONE, false);
247 : SlruPagePrecedesUnitTests(SubTransCtl, SUBTRANS_XACTS_PER_PAGE);
248 1902 : }
249 :
250 : /*
251 : * GUC check_hook for subtransaction_buffers
252 : */
253 : bool
254 3884 : check_subtrans_buffers(int *newval, void **extra, GucSource source)
255 : {
256 3884 : 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
270 90 : BootStrapSUBTRANS(void)
271 : {
272 : int slotno;
273 90 : LWLock *lock = SimpleLruGetBankLock(SubTransCtl, 0);
274 :
275 90 : LWLockAcquire(lock, LW_EXCLUSIVE);
276 :
277 : /* Create and zero the first page of the subtrans log */
278 90 : slotno = ZeroSUBTRANSPage(0);
279 :
280 : /* Make sure it's written out */
281 90 : SimpleLruWritePage(SubTransCtl, slotno);
282 : Assert(!SubTransCtl->shared->page_dirty[slotno]);
283 :
284 90 : LWLockRelease(lock);
285 90 : }
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
296 13814872 : ZeroSUBTRANSPage(int64 pageno)
297 : {
298 13814872 : 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
309 1630 : StartupSUBTRANS(TransactionId oldestActiveXID)
310 : {
311 : FullTransactionId nextXid;
312 : int64 startPage;
313 : int64 endPage;
314 1630 : 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 : */
323 1630 : startPage = TransactionIdToPage(oldestActiveXID);
324 1630 : nextXid = TransamVariables->nextXid;
325 1630 : endPage = TransactionIdToPage(XidFromFullTransactionId(nextXid));
326 :
327 : for (;;)
328 : {
329 1634 : lock = SimpleLruGetBankLock(SubTransCtl, startPage);
330 1634 : if (prevlock != lock)
331 : {
332 1634 : if (prevlock)
333 4 : LWLockRelease(prevlock);
334 1634 : LWLockAcquire(lock, LW_EXCLUSIVE);
335 1634 : prevlock = lock;
336 : }
337 :
338 1634 : (void) ZeroSUBTRANSPage(startPage);
339 1634 : if (startPage == endPage)
340 1630 : break;
341 :
342 4 : startPage++;
343 : /* must account for wraparound */
344 4 : if (startPage > TransactionIdToPage(MaxTransactionId))
345 0 : startPage = 0;
346 : }
347 :
348 1630 : LWLockRelease(lock);
349 1630 : }
350 :
351 : /*
352 : * Perform a checkpoint --- either during shutdown, or on-the-fly
353 : */
354 : void
355 2476 : 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);
365 2476 : SimpleLruWriteAll(SubTransCtl, true);
366 : TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_DONE(true);
367 2476 : }
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 49019196 : 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 49019196 : if (TransactionIdToEntry(newestXact) != 0 &&
389 : !TransactionIdEquals(newestXact, FirstNormalTransactionId))
390 35206048 : return;
391 :
392 13813148 : pageno = TransactionIdToPage(newestXact);
393 :
394 13813148 : lock = SimpleLruGetBankLock(SubTransCtl, pageno);
395 13813148 : LWLockAcquire(lock, LW_EXCLUSIVE);
396 :
397 : /* Zero the page */
398 13813148 : ZeroSUBTRANSPage(pageno);
399 :
400 13813148 : 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
411 2700 : 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 : */
423 2700 : TransactionIdRetreat(oldestXact);
424 2418 : cutoffPage = TransactionIdToPage(oldestXact);
425 :
426 2418 : SimpleLruTruncate(SubTransCtl, cutoffPage);
427 2418 : }
428 :
429 :
430 : /*
431 : * Decide whether a SUBTRANS page number is "older" for truncation purposes.
432 : * Analogous to CLOGPagePrecedes().
433 : */
434 : static bool
435 526588 : SubTransPagePrecedes(int64 page1, int64 page2)
436 : {
437 : TransactionId xid1;
438 : TransactionId xid2;
439 :
440 526588 : xid1 = ((TransactionId) page1) * SUBTRANS_XACTS_PER_PAGE;
441 526588 : xid1 += FirstNormalTransactionId + 1;
442 526588 : xid2 = ((TransactionId) page2) * SUBTRANS_XACTS_PER_PAGE;
443 526588 : xid2 += FirstNormalTransactionId + 1;
444 :
445 917686 : return (TransactionIdPrecedes(xid1, xid2) &&
446 391098 : TransactionIdPrecedes(xid1, xid2 + SUBTRANS_XACTS_PER_PAGE - 1));
447 : }
|