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-2025, 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 13839570 : TransactionIdToPage(TransactionId xid)
62 : {
63 13839570 : 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 bool SubTransPagePrecedes(int64 page1, int64 page2);
78 :
79 :
80 : /*
81 : * Record the parent of a subtransaction in the subtrans log.
82 : */
83 : void
84 12994 : SubTransSetParent(TransactionId xid, TransactionId parent)
85 : {
86 12994 : int64 pageno = TransactionIdToPage(xid);
87 12994 : int entryno = TransactionIdToEntry(xid);
88 : int slotno;
89 : LWLock *lock;
90 : TransactionId *ptr;
91 :
92 : Assert(TransactionIdIsValid(parent));
93 : Assert(TransactionIdFollows(xid, parent));
94 :
95 12994 : lock = SimpleLruGetBankLock(SubTransCtl, pageno);
96 12994 : LWLockAcquire(lock, LW_EXCLUSIVE);
97 :
98 12994 : slotno = SimpleLruReadPage(SubTransCtl, pageno, true, xid);
99 12994 : ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
100 12994 : ptr += entryno;
101 :
102 : /*
103 : * It's possible we'll try to set the parent xid multiple times but we
104 : * shouldn't ever be changing the xid from one valid xid to another valid
105 : * xid, which would corrupt the data structure.
106 : */
107 12994 : if (*ptr != parent)
108 : {
109 : Assert(*ptr == InvalidTransactionId);
110 11822 : *ptr = parent;
111 11822 : SubTransCtl->shared->page_dirty[slotno] = true;
112 : }
113 :
114 12994 : LWLockRelease(lock);
115 12994 : }
116 :
117 : /*
118 : * Interrogate the parent of a transaction in the subtrans log.
119 : */
120 : TransactionId
121 6370 : SubTransGetParent(TransactionId xid)
122 : {
123 6370 : int64 pageno = TransactionIdToPage(xid);
124 6370 : int entryno = TransactionIdToEntry(xid);
125 : int slotno;
126 : TransactionId *ptr;
127 : TransactionId parent;
128 :
129 : /* Can't ask about stuff that might not be around anymore */
130 : Assert(TransactionIdFollowsOrEquals(xid, TransactionXmin));
131 :
132 : /* Bootstrap and frozen XIDs have no parent */
133 6370 : if (!TransactionIdIsNormal(xid))
134 0 : return InvalidTransactionId;
135 :
136 : /* lock is acquired by SimpleLruReadPage_ReadOnly */
137 :
138 6370 : slotno = SimpleLruReadPage_ReadOnly(SubTransCtl, pageno, xid);
139 6370 : ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
140 6370 : ptr += entryno;
141 :
142 6370 : parent = *ptr;
143 :
144 6370 : LWLockRelease(SimpleLruGetBankLock(SubTransCtl, pageno));
145 :
146 6370 : return parent;
147 : }
148 :
149 : /*
150 : * SubTransGetTopmostTransaction
151 : *
152 : * Returns the topmost transaction of the given transaction id.
153 : *
154 : * Because we cannot look back further than TransactionXmin, it is possible
155 : * that this function will lie and return an intermediate subtransaction ID
156 : * instead of the true topmost parent ID. This is OK, because in practice
157 : * we only care about detecting whether the topmost parent is still running
158 : * or is part of a current snapshot's list of still-running transactions.
159 : * Therefore, any XID before TransactionXmin is as good as any other.
160 : */
161 : TransactionId
162 2360 : SubTransGetTopmostTransaction(TransactionId xid)
163 : {
164 2360 : TransactionId parentXid = xid,
165 2360 : previousXid = xid;
166 :
167 : /* Can't ask about stuff that might not be around anymore */
168 : Assert(TransactionIdFollowsOrEquals(xid, TransactionXmin));
169 :
170 8730 : while (TransactionIdIsValid(parentXid))
171 : {
172 6370 : previousXid = parentXid;
173 6370 : if (TransactionIdPrecedes(parentXid, TransactionXmin))
174 0 : break;
175 6370 : parentXid = SubTransGetParent(parentXid);
176 :
177 : /*
178 : * By convention the parent xid gets allocated first, so should always
179 : * precede the child xid. Anything else points to a corrupted data
180 : * structure that could lead to an infinite loop, so exit.
181 : */
182 6370 : if (!TransactionIdPrecedes(parentXid, previousXid))
183 0 : elog(ERROR, "pg_subtrans contains invalid entry: xid %u points to parent xid %u",
184 : previousXid, parentXid);
185 : }
186 :
187 : Assert(TransactionIdIsValid(previousXid));
188 :
189 2360 : return previousXid;
190 : }
191 :
192 : /*
193 : * Number of shared SUBTRANS buffers.
194 : *
195 : * If asked to autotune, use 2MB for every 1GB of shared buffers, up to 8MB.
196 : * Otherwise just cap the configured amount to be between 16 and the maximum
197 : * allowed.
198 : */
199 : static int
200 8278 : SUBTRANSShmemBuffers(void)
201 : {
202 : /* auto-tune based on shared buffers */
203 8278 : if (subtransaction_buffers == 0)
204 6088 : return SimpleLruAutotuneBuffers(512, 1024);
205 :
206 2190 : return Min(Max(16, subtransaction_buffers), SLRU_MAX_ALLOWED_BUFFERS);
207 : }
208 :
209 : /*
210 : * Initialization of shared memory for SUBTRANS
211 : */
212 : Size
213 3998 : SUBTRANSShmemSize(void)
214 : {
215 3998 : return SimpleLruShmemSize(SUBTRANSShmemBuffers(), 0);
216 : }
217 :
218 : void
219 2152 : SUBTRANSShmemInit(void)
220 : {
221 : /* If auto-tuning is requested, now is the time to do it */
222 2152 : if (subtransaction_buffers == 0)
223 : {
224 : char buf[32];
225 :
226 2128 : snprintf(buf, sizeof(buf), "%d", SUBTRANSShmemBuffers());
227 2128 : SetConfigOption("subtransaction_buffers", buf, PGC_POSTMASTER,
228 : PGC_S_DYNAMIC_DEFAULT);
229 :
230 : /*
231 : * We prefer to report this value's source as PGC_S_DYNAMIC_DEFAULT.
232 : * However, if the DBA explicitly set subtransaction_buffers = 0 in
233 : * the config file, then PGC_S_DYNAMIC_DEFAULT will fail to override
234 : * that and we must force the matter with PGC_S_OVERRIDE.
235 : */
236 2128 : if (subtransaction_buffers == 0) /* failed to apply it? */
237 0 : SetConfigOption("subtransaction_buffers", buf, PGC_POSTMASTER,
238 : PGC_S_OVERRIDE);
239 : }
240 : Assert(subtransaction_buffers != 0);
241 :
242 2152 : SubTransCtl->PagePrecedes = SubTransPagePrecedes;
243 2152 : SimpleLruInit(SubTransCtl, "subtransaction", SUBTRANSShmemBuffers(), 0,
244 : "pg_subtrans", LWTRANCHE_SUBTRANS_BUFFER,
245 : LWTRANCHE_SUBTRANS_SLRU, SYNC_HANDLER_NONE, false);
246 : SlruPagePrecedesUnitTests(SubTransCtl, SUBTRANS_XACTS_PER_PAGE);
247 2152 : }
248 :
249 : /*
250 : * GUC check_hook for subtransaction_buffers
251 : */
252 : bool
253 4408 : check_subtrans_buffers(int *newval, void **extra, GucSource source)
254 : {
255 4408 : return check_slru_buffers("subtransaction_buffers", newval);
256 : }
257 :
258 : /*
259 : * This func must be called ONCE on system install. It creates
260 : * the initial SUBTRANS segment. (The SUBTRANS directory is assumed to
261 : * have been created by the initdb shell script, and SUBTRANSShmemInit
262 : * must have been called already.)
263 : *
264 : * Note: it's not really necessary to create the initial segment now,
265 : * since slru.c would create it on first write anyway. But we may as well
266 : * do it to be sure the directory is set up correctly.
267 : */
268 : void
269 102 : BootStrapSUBTRANS(void)
270 : {
271 : /* Zero the initial page and flush it to disk */
272 102 : SimpleLruZeroAndWritePage(SubTransCtl, 0);
273 102 : }
274 :
275 : /*
276 : * This must be called ONCE during postmaster or standalone-backend startup,
277 : * after StartupXLOG has initialized TransamVariables->nextXid.
278 : *
279 : * oldestActiveXID is the oldest XID of any prepared transaction, or nextXid
280 : * if there are none.
281 : */
282 : void
283 1858 : StartupSUBTRANS(TransactionId oldestActiveXID)
284 : {
285 : FullTransactionId nextXid;
286 : int64 startPage;
287 : int64 endPage;
288 1858 : LWLock *prevlock = NULL;
289 : LWLock *lock;
290 :
291 : /*
292 : * Since we don't expect pg_subtrans to be valid across crashes, we
293 : * initialize the currently-active page(s) to zeroes during startup.
294 : * Whenever we advance into a new page, ExtendSUBTRANS will likewise zero
295 : * the new page without regard to whatever was previously on disk.
296 : */
297 1858 : startPage = TransactionIdToPage(oldestActiveXID);
298 1858 : nextXid = TransamVariables->nextXid;
299 1858 : endPage = TransactionIdToPage(XidFromFullTransactionId(nextXid));
300 :
301 : for (;;)
302 : {
303 1862 : lock = SimpleLruGetBankLock(SubTransCtl, startPage);
304 1862 : if (prevlock != lock)
305 : {
306 1862 : if (prevlock)
307 4 : LWLockRelease(prevlock);
308 1862 : LWLockAcquire(lock, LW_EXCLUSIVE);
309 1862 : prevlock = lock;
310 : }
311 :
312 1862 : (void) SimpleLruZeroPage(SubTransCtl, startPage);
313 1862 : if (startPage == endPage)
314 1858 : break;
315 :
316 4 : startPage++;
317 : /* must account for wraparound */
318 4 : if (startPage > TransactionIdToPage(MaxTransactionId))
319 0 : startPage = 0;
320 : }
321 :
322 1858 : LWLockRelease(lock);
323 1858 : }
324 :
325 : /*
326 : * Perform a checkpoint --- either during shutdown, or on-the-fly
327 : */
328 : void
329 3380 : CheckPointSUBTRANS(void)
330 : {
331 : /*
332 : * Write dirty SUBTRANS pages to disk
333 : *
334 : * This is not actually necessary from a correctness point of view. We do
335 : * it merely to improve the odds that writing of dirty pages is done by
336 : * the checkpoint process and not by backends.
337 : */
338 : TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_START(true);
339 3380 : SimpleLruWriteAll(SubTransCtl, true);
340 : TRACE_POSTGRESQL_SUBTRANS_CHECKPOINT_DONE(true);
341 3380 : }
342 :
343 :
344 : /*
345 : * Make sure that SUBTRANS has room for a newly-allocated XID.
346 : *
347 : * NB: this is called while holding XidGenLock. We want it to be very fast
348 : * most of the time; even when it's not so fast, no actual I/O need happen
349 : * unless we're forced to write out a dirty subtrans page to make room
350 : * in shared memory.
351 : */
352 : void
353 49045786 : ExtendSUBTRANS(TransactionId newestXact)
354 : {
355 : int64 pageno;
356 : LWLock *lock;
357 :
358 : /*
359 : * No work except at first XID of a page. But beware: just after
360 : * wraparound, the first XID of page zero is FirstNormalTransactionId.
361 : */
362 49045786 : if (TransactionIdToEntry(newestXact) != 0 &&
363 : !TransactionIdEquals(newestXact, FirstNormalTransactionId))
364 35232622 : return;
365 :
366 13813164 : pageno = TransactionIdToPage(newestXact);
367 :
368 13813164 : lock = SimpleLruGetBankLock(SubTransCtl, pageno);
369 13813164 : LWLockAcquire(lock, LW_EXCLUSIVE);
370 :
371 : /* Zero the page */
372 13813164 : SimpleLruZeroPage(SubTransCtl, pageno);
373 :
374 13813164 : LWLockRelease(lock);
375 : }
376 :
377 :
378 : /*
379 : * Remove all SUBTRANS segments before the one holding the passed transaction ID
380 : *
381 : * oldestXact is the oldest TransactionXmin of any running transaction. This
382 : * is called only during checkpoint.
383 : */
384 : void
385 3322 : TruncateSUBTRANS(TransactionId oldestXact)
386 : {
387 : int64 cutoffPage;
388 :
389 : /*
390 : * The cutoff point is the start of the segment containing oldestXact. We
391 : * pass the *page* containing oldestXact to SimpleLruTruncate. We step
392 : * back one transaction to avoid passing a cutoff page that hasn't been
393 : * created yet in the rare case that oldestXact would be the first item on
394 : * a page and oldestXact == next XID. In that case, if we didn't subtract
395 : * one, we'd trigger SimpleLruTruncate's wraparound detection.
396 : */
397 3640 : TransactionIdRetreat(oldestXact);
398 3322 : cutoffPage = TransactionIdToPage(oldestXact);
399 :
400 3322 : SimpleLruTruncate(SubTransCtl, cutoffPage);
401 3322 : }
402 :
403 :
404 : /*
405 : * Decide whether a SUBTRANS page number is "older" for truncation purposes.
406 : * Analogous to CLOGPagePrecedes().
407 : */
408 : static bool
409 646520 : SubTransPagePrecedes(int64 page1, int64 page2)
410 : {
411 : TransactionId xid1;
412 : TransactionId xid2;
413 :
414 646520 : xid1 = ((TransactionId) page1) * SUBTRANS_XACTS_PER_PAGE;
415 646520 : xid1 += FirstNormalTransactionId + 1;
416 646520 : xid2 = ((TransactionId) page2) * SUBTRANS_XACTS_PER_PAGE;
417 646520 : xid2 += FirstNormalTransactionId + 1;
418 :
419 1162610 : return (TransactionIdPrecedes(xid1, xid2) &&
420 516090 : TransactionIdPrecedes(xid1, xid2 + SUBTRANS_XACTS_PER_PAGE - 1));
421 : }
|