Coverage Report

Created: 2026-08-13 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/access/transam/commit_ts.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * commit_ts.c
4
 *    PostgreSQL commit timestamp manager
5
 *
6
 * This module is a pg_xact-like system that stores the commit timestamp
7
 * for each transaction.
8
 *
9
 * XLOG interactions: this module generates an XLOG record whenever a new
10
 * CommitTs page is initialized to zeroes.  Other writes of CommitTS come
11
 * from recording of transaction commit in xact.c, which generates its own
12
 * XLOG records for these events and will re-perform the status update on
13
 * redo; so we need make no additional XLOG entry here.
14
 *
15
 * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
16
 * Portions Copyright (c) 1994, Regents of the University of California
17
 *
18
 * src/backend/access/transam/commit_ts.c
19
 *
20
 *-------------------------------------------------------------------------
21
 */
22
#include "postgres.h"
23
24
#include "access/commit_ts.h"
25
#include "access/htup_details.h"
26
#include "access/slru.h"
27
#include "access/transam.h"
28
#include "access/xloginsert.h"
29
#include "access/xlogutils.h"
30
#include "funcapi.h"
31
#include "miscadmin.h"
32
#include "storage/shmem.h"
33
#include "storage/subsystems.h"
34
#include "utils/fmgrprotos.h"
35
#include "utils/guc_hooks.h"
36
#include "utils/timestamp.h"
37
38
/*
39
 * Defines for CommitTs page sizes.  A page is the same BLCKSZ as is used
40
 * everywhere else in Postgres.
41
 *
42
 * Note: because TransactionIds are 32 bits and wrap around at 0xFFFFFFFF,
43
 * CommitTs page numbering also wraps around at
44
 * 0xFFFFFFFF/COMMIT_TS_XACTS_PER_PAGE, and CommitTs segment numbering at
45
 * 0xFFFFFFFF/COMMIT_TS_XACTS_PER_PAGE/SLRU_PAGES_PER_SEGMENT.  We need take no
46
 * explicit notice of that fact in this module, except when comparing segment
47
 * and page numbers in TruncateCommitTs (see CommitTsPagePrecedes).
48
 */
49
50
/*
51
 * We need 8+2 bytes per xact.  Note that enlarging this struct might mean
52
 * the largest possible file name is more than 5 chars long; see
53
 * SlruScanDirectory.
54
 */
55
typedef struct CommitTimestampEntry
56
{
57
  TimestampTz time;
58
  ReplOriginId nodeid;
59
} CommitTimestampEntry;
60
61
0
#define SizeOfCommitTimestampEntry (offsetof(CommitTimestampEntry, nodeid) + \
62
0
                  sizeof(ReplOriginId))
63
64
#define COMMIT_TS_XACTS_PER_PAGE \
65
0
  (BLCKSZ / SizeOfCommitTimestampEntry)
66
67
68
/*
69
 * Although we return an int64 the actual value can't currently exceed
70
 * 0xFFFFFFFF/COMMIT_TS_XACTS_PER_PAGE.
71
 */
72
static inline int64
73
TransactionIdToCTsPage(TransactionId xid)
74
0
{
75
0
  return xid / (int64) COMMIT_TS_XACTS_PER_PAGE;
76
0
}
77
78
#define TransactionIdToCTsEntry(xid)  \
79
0
  ((xid) % (TransactionId) COMMIT_TS_XACTS_PER_PAGE)
80
81
/*
82
 * Link to shared-memory data structures for CommitTs control
83
 */
84
static void CommitTsShmemRequest(void *arg);
85
static void CommitTsShmemInit(void *arg);
86
static bool CommitTsPagePrecedes(int64 page1, int64 page2);
87
static int  commit_ts_errdetail_for_io_error(const void *opaque_data);
88
89
const ShmemCallbacks CommitTsShmemCallbacks = {
90
  .request_fn = CommitTsShmemRequest,
91
  .init_fn = CommitTsShmemInit,
92
};
93
94
static SlruDesc CommitTsSlruDesc;
95
96
0
#define CommitTsCtl (&CommitTsSlruDesc)
97
98
/*
99
 * We keep a cache of the last value set in shared memory.
100
 *
101
 * This is also good place to keep the activation status.  We keep this
102
 * separate from the GUC so that the standby can activate the module if the
103
 * primary has it active independently of the value of the GUC.
104
 *
105
 * This is protected by CommitTsLock.  In some places, we use commitTsActive
106
 * without acquiring the lock; where this happens, a comment explains the
107
 * rationale for it.
108
 */
109
typedef struct CommitTimestampShared
110
{
111
  TransactionId xidLastCommit;
112
  CommitTimestampEntry dataLastCommit;
113
  bool    commitTsActive;
114
} CommitTimestampShared;
115
116
static CommitTimestampShared *commitTsShared;
117
118
static void CommitTsShmemInit(void *arg);
119
120
/* GUC variable */
121
bool    track_commit_timestamp;
122
123
static void SetXidCommitTsInPage(TransactionId xid, int nsubxids,
124
                 TransactionId *subxids, TimestampTz ts,
125
                 ReplOriginId nodeid, int64 pageno);
126
static void TransactionIdSetCommitTs(TransactionId xid, TimestampTz ts,
127
                   ReplOriginId nodeid, int slotno);
128
static void error_commit_ts_disabled(void);
129
static void ActivateCommitTs(void);
130
static void DeactivateCommitTs(void);
131
static void WriteTruncateXlogRec(int64 pageno, TransactionId oldestXid);
132
133
/*
134
 * TransactionTreeSetCommitTsData
135
 *
136
 * Record the final commit timestamp of transaction entries in the commit log
137
 * for a transaction and its subtransaction tree, as efficiently as possible.
138
 *
139
 * xid is the top level transaction id.
140
 *
141
 * subxids is an array of xids of length nsubxids, representing subtransactions
142
 * in the tree of xid. In various cases nsubxids may be zero.
143
 * The reason why tracking just the parent xid commit timestamp is not enough
144
 * is that the subtrans SLRU does not stay valid across crashes (it's not
145
 * permanent) so we need to keep the information about them here. If the
146
 * subtrans implementation changes in the future, we might want to revisit the
147
 * decision of storing timestamp info for each subxid.
148
 */
149
void
150
TransactionTreeSetCommitTsData(TransactionId xid, int nsubxids,
151
                 TransactionId *subxids, TimestampTz timestamp,
152
                 ReplOriginId nodeid)
153
0
{
154
0
  int     i;
155
0
  TransactionId headxid;
156
0
  TransactionId newestXact;
157
158
  /*
159
   * No-op if the module is not active.
160
   *
161
   * An unlocked read here is fine, because in a standby (the only place
162
   * where the flag can change in flight) this routine is only called by the
163
   * recovery process, which is also the only process which can change the
164
   * flag.
165
   */
166
0
  if (!commitTsShared->commitTsActive)
167
0
    return;
168
169
  /*
170
   * Figure out the latest Xid in this batch: either the last subxid if
171
   * there's any, otherwise the parent xid.
172
   */
173
0
  if (nsubxids > 0)
174
0
    newestXact = subxids[nsubxids - 1];
175
0
  else
176
0
    newestXact = xid;
177
178
  /*
179
   * We split the xids to set the timestamp to in groups belonging to the
180
   * same SLRU page; the first element in each such set is its head.  The
181
   * first group has the main XID as the head; subsequent sets use the first
182
   * subxid not on the previous page as head.  This way, we only have to
183
   * lock/modify each SLRU page once.
184
   */
185
0
  headxid = xid;
186
0
  i = 0;
187
0
  for (;;)
188
0
  {
189
0
    int64   pageno = TransactionIdToCTsPage(headxid);
190
0
    int     j;
191
192
0
    for (j = i; j < nsubxids; j++)
193
0
    {
194
0
      if (TransactionIdToCTsPage(subxids[j]) != pageno)
195
0
        break;
196
0
    }
197
    /* subxids[i..j] are on the same page as the head */
198
199
0
    SetXidCommitTsInPage(headxid, j - i, subxids + i, timestamp, nodeid,
200
0
               pageno);
201
202
    /* if we wrote out all subxids, we're done. */
203
0
    if (j >= nsubxids)
204
0
      break;
205
206
    /*
207
     * Set the new head and skip over it, as well as over the subxids we
208
     * just wrote.
209
     */
210
0
    headxid = subxids[j];
211
0
    i = j + 1;
212
0
  }
213
214
  /* update the cached value in shared memory */
215
0
  LWLockAcquire(CommitTsLock, LW_EXCLUSIVE);
216
0
  commitTsShared->xidLastCommit = xid;
217
0
  commitTsShared->dataLastCommit.time = timestamp;
218
0
  commitTsShared->dataLastCommit.nodeid = nodeid;
219
220
  /* and move forwards our endpoint, if needed */
221
0
  if (TransactionIdPrecedes(TransamVariables->newestCommitTsXid, newestXact))
222
0
    TransamVariables->newestCommitTsXid = newestXact;
223
0
  LWLockRelease(CommitTsLock);
224
0
}
225
226
/*
227
 * Record the commit timestamp of transaction entries in the commit log for all
228
 * entries on a single page.  Atomic only on this page.
229
 */
230
static void
231
SetXidCommitTsInPage(TransactionId xid, int nsubxids,
232
           TransactionId *subxids, TimestampTz ts,
233
           ReplOriginId nodeid, int64 pageno)
234
0
{
235
0
  LWLock     *lock = SimpleLruGetBankLock(CommitTsCtl, pageno);
236
0
  int     slotno;
237
0
  int     i;
238
239
0
  LWLockAcquire(lock, LW_EXCLUSIVE);
240
241
0
  slotno = SimpleLruReadPage(CommitTsCtl, pageno, true, &xid);
242
243
0
  TransactionIdSetCommitTs(xid, ts, nodeid, slotno);
244
0
  for (i = 0; i < nsubxids; i++)
245
0
    TransactionIdSetCommitTs(subxids[i], ts, nodeid, slotno);
246
247
0
  CommitTsCtl->shared->page_dirty[slotno] = true;
248
249
0
  LWLockRelease(lock);
250
0
}
251
252
/*
253
 * Sets the commit timestamp of a single transaction.
254
 *
255
 * Caller must hold the correct SLRU bank lock, will be held at exit
256
 */
257
static void
258
TransactionIdSetCommitTs(TransactionId xid, TimestampTz ts,
259
             ReplOriginId nodeid, int slotno)
260
0
{
261
0
  int     entryno = TransactionIdToCTsEntry(xid);
262
0
  CommitTimestampEntry entry;
263
264
0
  Assert(TransactionIdIsNormal(xid));
265
266
0
  entry.time = ts;
267
0
  entry.nodeid = nodeid;
268
269
0
  memcpy(CommitTsCtl->shared->page_buffer[slotno] +
270
0
       SizeOfCommitTimestampEntry * entryno,
271
0
       &entry, SizeOfCommitTimestampEntry);
272
0
}
273
274
/*
275
 * Interrogate the commit timestamp of a transaction.
276
 *
277
 * The return value indicates whether a commit timestamp record was found for
278
 * the given xid.  The timestamp value is returned in *ts (which may not be
279
 * null), and the origin node for the Xid is returned in *nodeid, if it's not
280
 * null.
281
 */
282
bool
283
TransactionIdGetCommitTsData(TransactionId xid, TimestampTz *ts,
284
               ReplOriginId *nodeid)
285
0
{
286
0
  int64   pageno = TransactionIdToCTsPage(xid);
287
0
  int     entryno = TransactionIdToCTsEntry(xid);
288
0
  int     slotno;
289
0
  CommitTimestampEntry entry;
290
0
  TransactionId oldestCommitTsXid;
291
0
  TransactionId newestCommitTsXid;
292
293
0
  if (!TransactionIdIsValid(xid))
294
0
    ereport(ERROR,
295
0
        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
296
0
         errmsg("cannot retrieve commit timestamp for transaction %u", xid)));
297
0
  else if (!TransactionIdIsNormal(xid))
298
0
  {
299
    /* frozen and bootstrap xids are always committed far in the past */
300
0
    *ts = 0;
301
0
    if (nodeid)
302
0
      *nodeid = InvalidReplOriginId;
303
0
    return false;
304
0
  }
305
306
0
  LWLockAcquire(CommitTsLock, LW_SHARED);
307
308
  /* Error if module not enabled */
309
0
  if (!commitTsShared->commitTsActive)
310
0
    error_commit_ts_disabled();
311
312
  /*
313
   * If we're asked for the cached value, return that.  Otherwise, fall
314
   * through to read from SLRU.
315
   */
316
0
  if (commitTsShared->xidLastCommit == xid)
317
0
  {
318
0
    *ts = commitTsShared->dataLastCommit.time;
319
0
    if (nodeid)
320
0
      *nodeid = commitTsShared->dataLastCommit.nodeid;
321
322
0
    LWLockRelease(CommitTsLock);
323
0
    return *ts != 0;
324
0
  }
325
326
0
  oldestCommitTsXid = TransamVariables->oldestCommitTsXid;
327
0
  newestCommitTsXid = TransamVariables->newestCommitTsXid;
328
  /* neither is invalid, or both are */
329
0
  Assert(TransactionIdIsValid(oldestCommitTsXid) == TransactionIdIsValid(newestCommitTsXid));
330
0
  LWLockRelease(CommitTsLock);
331
332
  /*
333
   * Return empty if the requested value is outside our valid range.
334
   */
335
0
  if (!TransactionIdIsValid(oldestCommitTsXid) ||
336
0
    TransactionIdPrecedes(xid, oldestCommitTsXid) ||
337
0
    TransactionIdPrecedes(newestCommitTsXid, xid))
338
0
  {
339
0
    *ts = 0;
340
0
    if (nodeid)
341
0
      *nodeid = InvalidReplOriginId;
342
0
    return false;
343
0
  }
344
345
  /* lock is acquired by SimpleLruReadPage_ReadOnly */
346
0
  slotno = SimpleLruReadPage_ReadOnly(CommitTsCtl, pageno, &xid);
347
0
  memcpy(&entry,
348
0
       CommitTsCtl->shared->page_buffer[slotno] +
349
0
       SizeOfCommitTimestampEntry * entryno,
350
0
       SizeOfCommitTimestampEntry);
351
352
0
  *ts = entry.time;
353
0
  if (nodeid)
354
0
    *nodeid = entry.nodeid;
355
356
0
  LWLockRelease(SimpleLruGetBankLock(CommitTsCtl, pageno));
357
0
  return *ts != 0;
358
0
}
359
360
/*
361
 * Return the Xid of the latest committed transaction.  (As far as this module
362
 * is concerned, anyway; it's up to the caller to ensure the value is useful
363
 * for its purposes.)
364
 *
365
 * ts and nodeid are filled with the corresponding data; they can be passed
366
 * as NULL if not wanted.
367
 */
368
TransactionId
369
GetLatestCommitTsData(TimestampTz *ts, ReplOriginId *nodeid)
370
0
{
371
0
  TransactionId xid;
372
373
0
  LWLockAcquire(CommitTsLock, LW_SHARED);
374
375
  /* Error if module not enabled */
376
0
  if (!commitTsShared->commitTsActive)
377
0
    error_commit_ts_disabled();
378
379
0
  xid = commitTsShared->xidLastCommit;
380
0
  if (ts)
381
0
    *ts = commitTsShared->dataLastCommit.time;
382
0
  if (nodeid)
383
0
    *nodeid = commitTsShared->dataLastCommit.nodeid;
384
0
  LWLockRelease(CommitTsLock);
385
386
0
  return xid;
387
0
}
388
389
static void
390
error_commit_ts_disabled(void)
391
0
{
392
0
  ereport(ERROR,
393
0
      (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
394
0
       errmsg("could not get commit timestamp data"),
395
0
       RecoveryInProgress() ?
396
0
       errhint("Make sure the configuration parameter \"%s\" is set on the primary server.",
397
0
           "track_commit_timestamp") :
398
0
       errhint("Make sure the configuration parameter \"%s\" is set.",
399
0
           "track_commit_timestamp")));
400
0
}
401
402
/*
403
 * SQL-callable wrapper to obtain commit time of a transaction
404
 */
405
Datum
406
pg_xact_commit_timestamp(PG_FUNCTION_ARGS)
407
0
{
408
0
  TransactionId xid = PG_GETARG_TRANSACTIONID(0);
409
0
  TimestampTz ts;
410
0
  bool    found;
411
412
0
  found = TransactionIdGetCommitTsData(xid, &ts, NULL);
413
414
0
  if (!found)
415
0
    PG_RETURN_NULL();
416
417
0
  PG_RETURN_TIMESTAMPTZ(ts);
418
0
}
419
420
421
/*
422
 * pg_last_committed_xact
423
 *
424
 * SQL-callable wrapper to obtain some information about the latest
425
 * committed transaction: transaction ID, timestamp and replication
426
 * origin.
427
 */
428
Datum
429
pg_last_committed_xact(PG_FUNCTION_ARGS)
430
0
{
431
0
  TransactionId xid;
432
0
  ReplOriginId nodeid;
433
0
  TimestampTz ts;
434
0
  Datum   values[3];
435
0
  bool    nulls[3];
436
0
  TupleDesc tupdesc;
437
0
  HeapTuple htup;
438
439
  /* and construct a tuple with our data */
440
0
  xid = GetLatestCommitTsData(&ts, &nodeid);
441
442
0
  if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
443
0
    elog(ERROR, "return type must be a row type");
444
445
0
  if (!TransactionIdIsNormal(xid))
446
0
  {
447
0
    memset(nulls, true, sizeof(nulls));
448
0
  }
449
0
  else
450
0
  {
451
0
    values[0] = TransactionIdGetDatum(xid);
452
0
    nulls[0] = false;
453
454
0
    values[1] = TimestampTzGetDatum(ts);
455
0
    nulls[1] = false;
456
457
0
    values[2] = ObjectIdGetDatum((Oid) nodeid);
458
0
    nulls[2] = false;
459
0
  }
460
461
0
  htup = heap_form_tuple(tupdesc, values, nulls);
462
463
0
  PG_RETURN_DATUM(HeapTupleGetDatum(htup));
464
0
}
465
466
/*
467
 * pg_xact_commit_timestamp_origin
468
 *
469
 * SQL-callable wrapper to obtain commit timestamp and replication origin
470
 * of a given transaction.
471
 */
472
Datum
473
pg_xact_commit_timestamp_origin(PG_FUNCTION_ARGS)
474
0
{
475
0
  TransactionId xid = PG_GETARG_TRANSACTIONID(0);
476
0
  ReplOriginId nodeid;
477
0
  TimestampTz ts;
478
0
  Datum   values[2];
479
0
  bool    nulls[2];
480
0
  TupleDesc tupdesc;
481
0
  HeapTuple htup;
482
0
  bool    found;
483
484
0
  found = TransactionIdGetCommitTsData(xid, &ts, &nodeid);
485
486
0
  if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
487
0
    elog(ERROR, "return type must be a row type");
488
489
0
  if (!found)
490
0
  {
491
0
    memset(nulls, true, sizeof(nulls));
492
0
  }
493
0
  else
494
0
  {
495
0
    values[0] = TimestampTzGetDatum(ts);
496
0
    nulls[0] = false;
497
498
0
    values[1] = ObjectIdGetDatum((Oid) nodeid);
499
0
    nulls[1] = false;
500
0
  }
501
502
0
  htup = heap_form_tuple(tupdesc, values, nulls);
503
504
0
  PG_RETURN_DATUM(HeapTupleGetDatum(htup));
505
0
}
506
507
/*
508
 * Number of shared CommitTS buffers.
509
 *
510
 * If asked to autotune, use 2MB for every 1GB of shared buffers, up to 8MB.
511
 * Otherwise just cap the configured amount to be between 16 and the maximum
512
 * allowed.
513
 */
514
static int
515
CommitTsShmemBuffers(void)
516
0
{
517
  /* auto-tune based on shared buffers */
518
0
  if (commit_timestamp_buffers == 0)
519
0
    return SimpleLruAutotuneBuffers(512, 1024);
520
521
0
  return Min(Max(16, commit_timestamp_buffers), SLRU_MAX_ALLOWED_BUFFERS);
522
0
}
523
524
/*
525
 * Register CommitTs shared memory needs at system startup (postmaster start
526
 * or standalone backend)
527
 */
528
static void
529
CommitTsShmemRequest(void *arg)
530
0
{
531
  /* If auto-tuning is requested, now is the time to do it */
532
0
  if (commit_timestamp_buffers == 0)
533
0
  {
534
0
    char    buf[32];
535
536
0
    snprintf(buf, sizeof(buf), "%d", CommitTsShmemBuffers());
537
0
    SetConfigOption("commit_timestamp_buffers", buf, PGC_POSTMASTER,
538
0
            PGC_S_DYNAMIC_DEFAULT);
539
540
    /*
541
     * We prefer to report this value's source as PGC_S_DYNAMIC_DEFAULT.
542
     * However, if the DBA explicitly set commit_timestamp_buffers = 0 in
543
     * the config file, then PGC_S_DYNAMIC_DEFAULT will fail to override
544
     * that and we must force the matter with PGC_S_OVERRIDE.
545
     */
546
0
    if (commit_timestamp_buffers == 0) /* failed to apply it? */
547
0
      SetConfigOption("commit_timestamp_buffers", buf, PGC_POSTMASTER,
548
0
              PGC_S_OVERRIDE);
549
0
  }
550
0
  Assert(commit_timestamp_buffers != 0);
551
0
  SimpleLruRequest(.desc = &CommitTsSlruDesc,
552
0
           .name = "commit_timestamp",
553
0
           .Dir = "pg_commit_ts",
554
0
           .long_segment_names = false,
555
556
0
           .nslots = CommitTsShmemBuffers(),
557
558
0
           .PagePrecedes = CommitTsPagePrecedes,
559
0
           .errdetail_for_io_error = commit_ts_errdetail_for_io_error,
560
561
0
           .sync_handler = SYNC_HANDLER_COMMIT_TS,
562
0
           .buffer_tranche_id = LWTRANCHE_COMMITTS_BUFFER,
563
0
           .bank_tranche_id = LWTRANCHE_COMMITTS_SLRU,
564
0
    );
565
566
0
  ShmemRequestStruct(.name = "CommitTs shared",
567
0
             .size = sizeof(CommitTimestampShared),
568
0
             .ptr = (void **) &commitTsShared,
569
0
    );
570
0
}
571
572
static void
573
CommitTsShmemInit(void *arg)
574
0
{
575
0
  commitTsShared->xidLastCommit = InvalidTransactionId;
576
0
  TIMESTAMP_NOBEGIN(commitTsShared->dataLastCommit.time);
577
0
  commitTsShared->dataLastCommit.nodeid = InvalidReplOriginId;
578
0
  commitTsShared->commitTsActive = false;
579
580
0
  SlruPagePrecedesUnitTests(CommitTsCtl, COMMIT_TS_XACTS_PER_PAGE);
581
0
}
582
583
/*
584
 * GUC check_hook for commit_timestamp_buffers
585
 */
586
bool
587
check_commit_ts_buffers(int *newval, void **extra, GucSource source)
588
2
{
589
2
  return check_slru_buffers("commit_timestamp_buffers", newval);
590
2
}
591
592
/*
593
 * This function must be called ONCE on system install.
594
 *
595
 * (The CommitTs directory is assumed to have been created by initdb, and
596
 * CommitTsShmemInit must have been called already.)
597
 */
598
void
599
BootStrapCommitTs(void)
600
0
{
601
  /*
602
   * Nothing to do here at present, unlike most other SLRU modules; segments
603
   * are created when the server is started with this module enabled. See
604
   * ActivateCommitTs.
605
   */
606
0
}
607
608
/*
609
 * This must be called ONCE during postmaster or standalone-backend startup,
610
 * after StartupXLOG has initialized TransamVariables->nextXid.
611
 */
612
void
613
StartupCommitTs(void)
614
0
{
615
0
  ActivateCommitTs();
616
0
}
617
618
/*
619
 * This must be called ONCE during postmaster or standalone-backend startup,
620
 * after recovery has finished.
621
 */
622
void
623
CompleteCommitTsInitialization(void)
624
0
{
625
  /*
626
   * If the feature is not enabled, turn it off for good.  This also removes
627
   * any leftover data.
628
   *
629
   * Conversely, we activate the module if the feature is enabled.  This is
630
   * necessary for primary and standby as the activation depends on the
631
   * control file contents at the beginning of recovery or when a
632
   * XLOG_PARAMETER_CHANGE is replayed.
633
   */
634
0
  if (!track_commit_timestamp)
635
0
    DeactivateCommitTs();
636
0
  else
637
0
    ActivateCommitTs();
638
0
}
639
640
/*
641
 * Activate or deactivate CommitTs' upon reception of a XLOG_PARAMETER_CHANGE
642
 * XLog record during recovery.
643
 */
644
void
645
CommitTsParameterChange(bool newvalue, bool oldvalue)
646
0
{
647
  /*
648
   * If the commit_ts module is disabled in this server and we get word from
649
   * the primary server that it is enabled there, activate it so that we can
650
   * replay future WAL records involving it; also mark it as active on
651
   * pg_control.  If the old value was already set, we already did this, so
652
   * don't do anything.
653
   *
654
   * If the module is disabled in the primary, disable it here too, unless
655
   * the module is enabled locally.
656
   *
657
   * Note this only runs in the recovery process, so an unlocked read is
658
   * fine.
659
   */
660
0
  if (newvalue)
661
0
  {
662
0
    if (!commitTsShared->commitTsActive)
663
0
      ActivateCommitTs();
664
0
  }
665
0
  else if (commitTsShared->commitTsActive)
666
0
    DeactivateCommitTs();
667
0
}
668
669
/*
670
 * Activate this module whenever necessary.
671
 *    This must happen during postmaster or standalone-backend startup,
672
 *    or during WAL replay anytime the track_commit_timestamp setting is
673
 *    changed in the primary.
674
 *
675
 * The reason why this SLRU needs separate activation/deactivation functions is
676
 * that it can be enabled/disabled during start and the activation/deactivation
677
 * on the primary is propagated to the standby via replay. Other SLRUs don't
678
 * have this property and they can be just initialized during normal startup.
679
 *
680
 * This is in charge of creating the currently active segment, if it's not
681
 * already there.  The reason for this is that the server might have been
682
 * running with this module disabled for a while and thus might have skipped
683
 * the normal creation point.
684
 */
685
static void
686
ActivateCommitTs(void)
687
0
{
688
0
  TransactionId xid;
689
0
  int64   pageno;
690
691
  /*
692
   * During bootstrap, we should not register commit timestamps so skip the
693
   * activation in this case.
694
   */
695
0
  if (IsBootstrapProcessingMode())
696
0
    return;
697
698
  /* If we've done this already, there's nothing to do */
699
0
  LWLockAcquire(CommitTsLock, LW_EXCLUSIVE);
700
0
  if (commitTsShared->commitTsActive)
701
0
  {
702
0
    LWLockRelease(CommitTsLock);
703
0
    return;
704
0
  }
705
0
  LWLockRelease(CommitTsLock);
706
707
0
  xid = XidFromFullTransactionId(TransamVariables->nextXid);
708
0
  pageno = TransactionIdToCTsPage(xid);
709
710
  /*
711
   * Re-Initialize our idea of the latest page number.
712
   */
713
0
  pg_atomic_write_u64(&CommitTsCtl->shared->latest_page_number, pageno);
714
715
  /*
716
   * If CommitTs is enabled, but it wasn't in the previous server run, we
717
   * need to set the oldest and newest values to the next Xid; that way, we
718
   * will not try to read data that might not have been set.
719
   *
720
   * XXX does this have a problem if a server is started with commitTs
721
   * enabled, then started with commitTs disabled, then restarted with it
722
   * enabled again?  It doesn't look like it does, because there should be a
723
   * checkpoint that sets the value to InvalidTransactionId at end of
724
   * recovery; and so any chance of injecting new transactions without
725
   * CommitTs values would occur after the oldestCommitTsXid has been set to
726
   * Invalid temporarily.
727
   */
728
0
  LWLockAcquire(CommitTsLock, LW_EXCLUSIVE);
729
0
  if (TransamVariables->oldestCommitTsXid == InvalidTransactionId)
730
0
  {
731
0
    TransamVariables->oldestCommitTsXid =
732
0
      TransamVariables->newestCommitTsXid = ReadNextTransactionId();
733
0
  }
734
0
  LWLockRelease(CommitTsLock);
735
736
  /* Create the current segment file, if necessary */
737
0
  if (!SimpleLruDoesPhysicalPageExist(CommitTsCtl, pageno))
738
0
    SimpleLruZeroAndWritePage(CommitTsCtl, pageno);
739
740
  /* Change the activation status in shared memory. */
741
0
  LWLockAcquire(CommitTsLock, LW_EXCLUSIVE);
742
0
  commitTsShared->commitTsActive = true;
743
0
  LWLockRelease(CommitTsLock);
744
0
}
745
746
/*
747
 * Deactivate this module.
748
 *
749
 * This must be called when the track_commit_timestamp parameter is turned off.
750
 * This happens during postmaster or standalone-backend startup, or during WAL
751
 * replay.
752
 *
753
 * Resets CommitTs into invalid state to make sure we don't hand back
754
 * possibly-invalid data; also removes segments of old data.
755
 */
756
static void
757
DeactivateCommitTs(void)
758
0
{
759
  /*
760
   * Cleanup the status in the shared memory.
761
   *
762
   * We reset everything in the commitTsShared record to prevent user from
763
   * getting confusing data about last committed transaction on the standby
764
   * when the module was activated repeatedly on the primary.
765
   */
766
0
  LWLockAcquire(CommitTsLock, LW_EXCLUSIVE);
767
768
0
  commitTsShared->commitTsActive = false;
769
0
  commitTsShared->xidLastCommit = InvalidTransactionId;
770
0
  TIMESTAMP_NOBEGIN(commitTsShared->dataLastCommit.time);
771
0
  commitTsShared->dataLastCommit.nodeid = InvalidReplOriginId;
772
773
0
  TransamVariables->oldestCommitTsXid = InvalidTransactionId;
774
0
  TransamVariables->newestCommitTsXid = InvalidTransactionId;
775
776
  /*
777
   * Remove *all* files.  This is necessary so that there are no leftover
778
   * files; in the case where this feature is later enabled after running
779
   * with it disabled for some time there may be a gap in the file sequence.
780
   * (We can probably tolerate out-of-sequence files, as they are going to
781
   * be overwritten anyway when we wrap around, but it seems better to be
782
   * tidy.)
783
   *
784
   * Note that we do this with CommitTsLock acquired in exclusive mode. This
785
   * is very heavy-handed, but since this routine can only be called in the
786
   * replica and should happen very rarely, we don't worry too much about
787
   * it.  Note also that no process should be consulting this SLRU if we
788
   * have just deactivated it.
789
   */
790
0
  (void) SlruScanDirectory(CommitTsCtl, SlruScanDirCbDeleteAll, NULL);
791
792
0
  LWLockRelease(CommitTsLock);
793
0
}
794
795
/*
796
 * Perform a checkpoint --- either during shutdown, or on-the-fly
797
 */
798
void
799
CheckPointCommitTs(void)
800
0
{
801
  /*
802
   * Write dirty CommitTs pages to disk.  This may result in sync requests
803
   * queued for later handling by ProcessSyncRequests(), as part of the
804
   * checkpoint.
805
   */
806
0
  SimpleLruWriteAll(CommitTsCtl, true);
807
0
}
808
809
/*
810
 * Make sure that CommitTs has room for a newly-allocated XID.
811
 *
812
 * NB: this is called while holding XidGenLock.  We want it to be very fast
813
 * most of the time; even when it's not so fast, no actual I/O need happen
814
 * unless we're forced to write out a dirty CommitTs or xlog page to make room
815
 * in shared memory.
816
 *
817
 * NB: the current implementation relies on track_commit_timestamp being
818
 * PGC_POSTMASTER.
819
 */
820
void
821
ExtendCommitTs(TransactionId newestXact)
822
0
{
823
0
  int64   pageno;
824
0
  LWLock     *lock;
825
826
  /*
827
   * Nothing to do if module not enabled.  Note we do an unlocked read of
828
   * the flag here, which is okay because this routine is only called from
829
   * GetNewTransactionId, which is never called in a standby.
830
   */
831
0
  Assert(!InRecovery);
832
0
  if (!commitTsShared->commitTsActive)
833
0
    return;
834
835
  /*
836
   * No work except at first XID of a page.  But beware: just after
837
   * wraparound, the first XID of page zero is FirstNormalTransactionId.
838
   */
839
0
  if (TransactionIdToCTsEntry(newestXact) != 0 &&
840
0
    !TransactionIdEquals(newestXact, FirstNormalTransactionId))
841
0
    return;
842
843
0
  pageno = TransactionIdToCTsPage(newestXact);
844
845
0
  lock = SimpleLruGetBankLock(CommitTsCtl, pageno);
846
847
0
  LWLockAcquire(lock, LW_EXCLUSIVE);
848
849
  /* Zero the page ... */
850
0
  SimpleLruZeroPage(CommitTsCtl, pageno);
851
852
  /* and make a WAL entry about that, unless we're in REDO */
853
0
  if (!InRecovery)
854
0
    XLogSimpleInsertInt64(RM_COMMIT_TS_ID, COMMIT_TS_ZEROPAGE, pageno);
855
856
0
  LWLockRelease(lock);
857
0
}
858
859
/*
860
 * Remove all CommitTs segments before the one holding the passed
861
 * transaction ID.
862
 *
863
 * Note that we don't need to flush XLOG here.
864
 */
865
void
866
TruncateCommitTs(TransactionId oldestXact)
867
0
{
868
0
  int64   cutoffPage;
869
870
  /*
871
   * The cutoff point is the start of the segment containing oldestXact. We
872
   * pass the *page* containing oldestXact to SimpleLruTruncate.
873
   */
874
0
  cutoffPage = TransactionIdToCTsPage(oldestXact);
875
876
  /* Check to see if there's any files that could be removed */
877
0
  if (!SlruScanDirectory(CommitTsCtl, SlruScanDirCbReportPresence,
878
0
               &cutoffPage))
879
0
    return;         /* nothing to remove */
880
881
  /* Write XLOG record */
882
0
  WriteTruncateXlogRec(cutoffPage, oldestXact);
883
884
  /* Now we can remove the old CommitTs segment(s) */
885
0
  SimpleLruTruncate(CommitTsCtl, cutoffPage);
886
0
}
887
888
/*
889
 * Set the limit values between which commit TS can be consulted.
890
 */
891
void
892
SetCommitTsLimit(TransactionId oldestXact, TransactionId newestXact)
893
0
{
894
  /*
895
   * Be careful not to overwrite values that are either further into the
896
   * "future" or signal a disabled committs.
897
   */
898
0
  LWLockAcquire(CommitTsLock, LW_EXCLUSIVE);
899
0
  if (TransamVariables->oldestCommitTsXid != InvalidTransactionId)
900
0
  {
901
0
    if (TransactionIdPrecedes(TransamVariables->oldestCommitTsXid, oldestXact))
902
0
      TransamVariables->oldestCommitTsXid = oldestXact;
903
0
    if (TransactionIdPrecedes(newestXact, TransamVariables->newestCommitTsXid))
904
0
      TransamVariables->newestCommitTsXid = newestXact;
905
0
  }
906
0
  else
907
0
  {
908
0
    Assert(TransamVariables->newestCommitTsXid == InvalidTransactionId);
909
0
    TransamVariables->oldestCommitTsXid = oldestXact;
910
0
    TransamVariables->newestCommitTsXid = newestXact;
911
0
  }
912
0
  LWLockRelease(CommitTsLock);
913
0
}
914
915
/*
916
 * Move forwards the oldest commitTS value that can be consulted
917
 */
918
void
919
AdvanceOldestCommitTsXid(TransactionId oldestXact)
920
0
{
921
0
  LWLockAcquire(CommitTsLock, LW_EXCLUSIVE);
922
0
  if (TransamVariables->oldestCommitTsXid != InvalidTransactionId &&
923
0
    TransactionIdPrecedes(TransamVariables->oldestCommitTsXid, oldestXact))
924
0
    TransamVariables->oldestCommitTsXid = oldestXact;
925
0
  LWLockRelease(CommitTsLock);
926
0
}
927
928
929
/*
930
 * Decide whether a commitTS page number is "older" for truncation purposes.
931
 * Analogous to CLOGPagePrecedes().
932
 *
933
 * At default BLCKSZ, (1 << 31) % COMMIT_TS_XACTS_PER_PAGE == 128.  This
934
 * introduces differences compared to CLOG and the other SLRUs having (1 <<
935
 * 31) % per_page == 0.  This function never tests exactly
936
 * TransactionIdPrecedes(x-2^31, x).  When the system reaches xidStopLimit,
937
 * there are two possible counts of page boundaries between oldestXact and the
938
 * latest XID assigned, depending on whether oldestXact is within the first
939
 * 128 entries of its page.  Since this function doesn't know the location of
940
 * oldestXact within page2, it returns false for one page that actually is
941
 * expendable.  This is a wider (yet still negligible) version of the
942
 * truncation opportunity that CLOGPagePrecedes() cannot recognize.
943
 *
944
 * For the sake of a worked example, number entries with decimal values such
945
 * that page1==1 entries range from 1.0 to 1.999.  Let N+0.15 be the number of
946
 * pages that 2^31 entries will span (N is an integer).  If oldestXact=N+2.1,
947
 * then the final safe XID assignment leaves newestXact=1.95.  We keep page 2,
948
 * because entry=2.85 is the border that toggles whether entries precede the
949
 * last entry of the oldestXact page.  While page 2 is expendable at
950
 * oldestXact=N+2.1, it would be precious at oldestXact=N+2.9.
951
 */
952
static bool
953
CommitTsPagePrecedes(int64 page1, int64 page2)
954
0
{
955
0
  TransactionId xid1;
956
0
  TransactionId xid2;
957
958
0
  xid1 = ((TransactionId) page1) * COMMIT_TS_XACTS_PER_PAGE;
959
0
  xid1 += FirstNormalTransactionId + 1;
960
0
  xid2 = ((TransactionId) page2) * COMMIT_TS_XACTS_PER_PAGE;
961
0
  xid2 += FirstNormalTransactionId + 1;
962
963
0
  return (TransactionIdPrecedes(xid1, xid2) &&
964
0
      TransactionIdPrecedes(xid1, xid2 + COMMIT_TS_XACTS_PER_PAGE - 1));
965
0
}
966
967
static int
968
commit_ts_errdetail_for_io_error(const void *opaque_data)
969
0
{
970
0
  TransactionId xid = *(const TransactionId *) opaque_data;
971
972
0
  return errdetail("Could not access commit timestamp of transaction %u.", xid);
973
0
}
974
975
/*
976
 * Write a TRUNCATE xlog record
977
 */
978
static void
979
WriteTruncateXlogRec(int64 pageno, TransactionId oldestXid)
980
0
{
981
0
  xl_commit_ts_truncate xlrec;
982
983
0
  xlrec.pageno = pageno;
984
0
  xlrec.oldestXid = oldestXid;
985
986
0
  XLogBeginInsert();
987
0
  XLogRegisterData(&xlrec, SizeOfCommitTsTruncate);
988
0
  (void) XLogInsert(RM_COMMIT_TS_ID, COMMIT_TS_TRUNCATE);
989
0
}
990
991
/*
992
 * CommitTS resource manager's routines
993
 */
994
void
995
commit_ts_redo(XLogReaderState *record)
996
0
{
997
0
  uint8   info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
998
999
  /* Backup blocks are not used in commit_ts records */
1000
0
  Assert(!XLogRecHasAnyBlockRefs(record));
1001
1002
0
  if (info == COMMIT_TS_ZEROPAGE)
1003
0
  {
1004
0
    int64   pageno;
1005
1006
0
    memcpy(&pageno, XLogRecGetData(record), sizeof(pageno));
1007
0
    SimpleLruZeroAndWritePage(CommitTsCtl, pageno);
1008
0
  }
1009
0
  else if (info == COMMIT_TS_TRUNCATE)
1010
0
  {
1011
0
    xl_commit_ts_truncate *trunc = (xl_commit_ts_truncate *) XLogRecGetData(record);
1012
1013
0
    AdvanceOldestCommitTsXid(trunc->oldestXid);
1014
1015
    /*
1016
     * During XLOG replay, latest_page_number isn't set up yet; insert a
1017
     * suitable value to bypass the sanity test in SimpleLruTruncate.
1018
     */
1019
0
    pg_atomic_write_u64(&CommitTsCtl->shared->latest_page_number,
1020
0
              trunc->pageno);
1021
1022
0
    SimpleLruTruncate(CommitTsCtl, trunc->pageno);
1023
0
  }
1024
0
  else
1025
0
    elog(PANIC, "commit_ts_redo: unknown op code %u", info);
1026
0
}
1027
1028
/*
1029
 * Entrypoint for sync.c to sync commit_ts files.
1030
 */
1031
int
1032
committssyncfiletag(const FileTag *ftag, char *path)
1033
0
{
1034
0
  return SlruSyncFileTag(CommitTsCtl, ftag, path);
1035
0
}