Skip to content

Commit da184d7

Browse files
committed
C2 on xlog
1 parent 6ae87b1 commit da184d7

File tree

7 files changed

+88
-2
lines changed

7 files changed

+88
-2
lines changed

src/backend/access/heap/heapam.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2229,6 +2229,12 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
22292229
XLogRegisterBuffer(0, buffer, REGBUF_STANDARD | bufflags);
22302230
XLogRegisterBufData(0, (char *) &xlhdr, SizeOfHeapHeader);
22312231
/* PG73FORMAT: write bitmap [+ padding] [+ oid] + data */
2232+
/**
2233+
3. 为什么跳过头部
2234+
堆元组的头部(HeapTupleHeader)包含元组的元信息,例如事务状态、行号等。
2235+
这些信息通常已经通过其他方式(如缓冲区的全页面镜像)记录到 WAL 中,
2236+
因此这里只需要记录实际的数据部分,减少 WAL 日志的大小。
2237+
*/
22322238
XLogRegisterBufData(0,
22332239
(char *) heaptup->t_data + SizeofHeapTupleHeader,
22342240
heaptup->t_len - SizeofHeapTupleHeader);
@@ -9693,6 +9699,8 @@ heap_xlog_delete(XLogReaderState *record)
96939699
UnlockReleaseBuffer(buffer);
96949700
}
96959701

9702+
// 用于处理堆表(heap table)插入操作的 WAL(Write-Ahead Logging)重放函数
9703+
// 非常好的示例
96969704
static void
96979705
heap_xlog_insert(XLogReaderState *record)
96989706
{

src/backend/access/transam/xloginsert.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,8 @@ XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
271271
regbuf = &registered_buffers[block_id];
272272

273273
BufferGetTag(buffer, &regbuf->rlocator, &regbuf->forkno, &regbuf->block);
274+
// 这里只是保存page指针,而不是记录整个页面到xlog中
275+
// 具体的记录逻辑在XLogRecordAssemble()中
274276
regbuf->page = BufferGetPage(buffer);
275277
regbuf->flags = flags;
276278
regbuf->rdata_tail = (XLogRecData *) &regbuf->rdata_head;

src/backend/access/transam/xlogutils.c

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,13 @@ XLogInitBufferForRedo(XLogReaderState *record, uint8 block_id)
347347
* If 'get_cleanup_lock' is true, a "cleanup lock" is acquired on the buffer
348348
* using LockBufferForCleanup(), instead of a regular exclusive lock.
349349
*/
350+
/**
351+
函数返回一个 XLogRedoAction 枚举值,指示页面的处理结果:
352+
BLK_RESTORED: 页面通过全页面镜像恢复。
353+
BLK_DONE: 页面已经包含最新的修改,无需进一步操作。
354+
BLK_NEEDS_REDO: 页面需要应用 WAL 记录中的修改。
355+
BLK_NOTFOUND: 页面不存在。
356+
*/
350357
XLogRedoAction
351358
XLogReadBufferForRedoExtended(XLogReaderState *record,
352359
uint8 block_id,
@@ -382,6 +389,7 @@ XLogReadBufferForRedoExtended(XLogReaderState *record,
382389
elog(PANIC, "block to be initialized in redo routine must be marked with WILL_INIT flag in the WAL record");
383390

384391
/* If it has a full-page image and it should be restored, do it. */
392+
/* FPI routine */
385393
if (XLogRecBlockImageApply(record, block_id))
386394
{
387395
Assert(XLogRecHasBlockImage(record, block_id));
@@ -414,20 +422,28 @@ XLogReadBufferForRedoExtended(XLogReaderState *record,
414422
if (forknum == INIT_FORKNUM)
415423
FlushOneBuffer(*buf);
416424

417-
return BLK_RESTORED;
425+
return BLK_RESTORED; // 页面通过全页面镜像FPI恢复。
418426
}
419427
else
420428
{
421429
*buf = XLogReadBufferExtended(rlocator, forknum, blkno, mode, prefetch_buffer);
422430
if (BufferIsValid(*buf))
423431
{
432+
/**
433+
- 普通独占锁(Exclusive Lock): 使用 LockBuffer 函数获取,
434+
允许对页面进行修改,但不允许其他进程同时访问该页面。
435+
- 清理锁(Cleanup Lock): 使用 LockBufferForCleanup 函数获取,
436+
通常用于清理页面中的死元组或进行更高级别的操作。
437+
清理锁是独占锁的一种扩展,具有更高的权限。
438+
*/
424439
if (mode != RBM_ZERO_AND_LOCK && mode != RBM_ZERO_AND_CLEANUP_LOCK)
425440
{
426441
if (get_cleanup_lock)
427442
LockBufferForCleanup(*buf);
428443
else
429444
LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);
430445
}
446+
// 如果页面的 LSN 大于或等于 WAL 记录的 LSN,说明页面已经包含了最新的修改,返回 BLK_DONE。
431447
if (lsn <= PageGetLSN(BufferGetPage(*buf)))
432448
return BLK_DONE;
433449
else
@@ -438,6 +454,16 @@ XLogReadBufferForRedoExtended(XLogReaderState *record,
438454
}
439455
}
440456

457+
/**
458+
XLogReadBufferExtended:
459+
专门用于 WAL 重放(XLOG replay)期间读取页面。
460+
主要在崩溃恢复或主备复制的 WAL 日志重放过程中使用。
461+
适配了 WAL 重放的特殊需求,例如处理页面不存在的情况或全零页面。
462+
463+
ReadBufferExtended:
464+
用于正常系统操作期间读取页面。
465+
适用于普通的表操作(如查询、插入、更新、删除)中访问页面。
466+
*/
441467
/*
442468
* XLogReadBufferExtended
443469
* Read a page during XLOG replay

src/backend/commands/sequence.c

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,12 @@ DeleteSequenceTuple(Oid relid)
621621
* entry, but we keep it around to ease porting of C code that may have
622622
* called the function directly.
623623
*/
624+
/**
625+
在 hot-standby 模式下不可以调用 nextval,
626+
因为它是写操作,会抛出类似以下的错误:
627+
transaction
628+
ERROR: cannot execute nextval() in a read-only transactio
629+
*/
624630
Datum
625631
nextval(PG_FUNCTION_ARGS)
626632
{
@@ -735,6 +741,13 @@ elm->last 表示上一次返回的序列值,而 elm->cached 表示当前缓存
735741
这里给buffer加了独占锁(同时也pin了buffer):
736742
read_seq_tuple 函数通过调用 ReadBuffer:它对缓冲区也进行了pin操作
737743
*/
744+
/**
745+
* 另外留意这里没通过mvcc机制来读tuple:
746+
* - xmin 通常被设置为 FrozenTransactionId,以确保该元组始终可见
747+
* - xmax 通常被设置为 InvalidTransactionId,表示该元组不会被删除或更新
748+
*
749+
* 因此导致了一些很trick的机制(hot-standy),见笔记
750+
*/
738751
seq = read_seq_tuple(seqrel, &buf, &seqdatatuple);
739752

740753
page = BufferGetPage(buf);
@@ -886,6 +899,7 @@ elm->last 表示上一次返回的序列值,而 elm->cached 表示当前缓存
886899
* sequence values if we crash.
887900
*/
888901
XLogBeginInsert();
902+
// REGBUF_WILL_INIT: 这是一个标志,表示缓冲区将被完全重新初始化,因此不需要记录其当前内容
889903
XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT);
890904

891905
/* set values that will be saved in xlog */
@@ -894,7 +908,8 @@ elm->last 表示上一次返回的序列值,而 elm->cached 表示当前缓存
894908
seq->log_cnt = 0;
895909

896910
xlrec.locator = seqrel->rd_locator;
897-
911+
912+
// 只通过maindata部分记录数据就足够了,不需要blockdata(其中永远只有一条tuple)
898913
XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec));
899914
XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len);
900915

@@ -1903,6 +1918,7 @@ seq_redo(XLogReaderState *record)
19031918
if (info != XLOG_SEQ_LOG)
19041919
elog(PANIC, "seq_redo: unknown op code %u", info);
19051920

1921+
// 每次都重新初始化一个新的buffer
19061922
buffer = XLogInitBufferForRedo(record, 0);
19071923
page = (Page) BufferGetPage(buffer);
19081924

@@ -1915,6 +1931,7 @@ seq_redo(XLogReaderState *record)
19151931
* are supposed to change will change, even transiently. We must palloc
19161932
* the local page for alignment reasons.
19171933
*/
1934+
// NB:热备模式下的一个corner case,需要构建localpage来解决,见gpt详解
19181935
localpage = (Page) palloc(BufferGetPageSize(buffer));
19191936

19201937
PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic));
@@ -1930,6 +1947,8 @@ seq_redo(XLogReaderState *record)
19301947

19311948
PageSetLSN(localpage, lsn);
19321949

1950+
// 虽然 memcpy 不是原子操作,但它的使用场景是安全的:
1951+
// 锁的保护: 在 memcpy 操作期间,缓冲区的独占锁确保了没有其他进程可以访问该缓冲区。
19331952
memcpy(page, localpage, BufferGetPageSize(buffer));
19341953
// 让bgwriter待会儿把这个buffer写到磁盘上
19351954
MarkBufferDirty(buffer);

src/backend/replication/logical/decode.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,14 @@ DecodeAbort(LogicalDecodingContext *ctx, XLogRecordBuffer *buf,
884884
UpdateDecodingStats(ctx);
885885
}
886886

887+
/**
888+
4. 示例场景
889+
假设有一个表 users,执行了SQL 插入操作,对应的 WAL 日志记录会被 DecodeInsert 函数解析为以下逻辑变更事件:
890+
表名: users
891+
操作类型: 插入
892+
插入的元组: {id: 1, name: 'Alice'}
893+
这些信息会被传递给逻辑复制的输出插件,用于生成逻辑复制流。
894+
*/
887895
/*
888896
* Parse XLOG_HEAP_INSERT (not MULTI_INSERT!) records into tuplebufs.
889897
*

src/include/access/heapam_xlog.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,13 @@
7070
/* PD_ALL_VISIBLE was cleared */
7171
#define XLH_INSERT_ALL_VISIBLE_CLEARED (1<<0)
7272
#define XLH_INSERT_LAST_IN_MULTI (1<<1)
73+
/**
74+
XLH_INSERT_IS_SPECULATIVE 表示插入操作是“试探性插入”(speculative insertion)。
75+
试探性插入是一种特殊的插入模式,通常用于支持唯一约束(unique constraint)或排他性约束(exclusion constraint)的并发检查。
76+
77+
在试探性插入中,元组(tuple)会被暂时插入到堆表中,但其状态是未决的(pending)。
78+
只有在约束检查通过后,插入才会被正式确认;否则,插入会被回滚。
79+
*/
7380
#define XLH_INSERT_IS_SPECULATIVE (1<<2)
7481
#define XLH_INSERT_CONTAINS_NEW_TUPLE (1<<3)
7582
#define XLH_INSERT_ON_TOAST_RELATION (1<<4)

src/include/access/xlog_internal.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,10 +354,26 @@ typedef struct RmgrData
354354
const char *(*rm_identify) (uint8 info);
355355
void (*rm_startup) (void);
356356
void (*rm_cleanup) (void);
357+
/**
358+
void (*rm_mask)(char *pagedata, BlockNumber blkno): 掩码函数,
359+
用于屏蔽页面中不需要进行一致性检查的部分。这在启用了 wal_consistency_checking 时非常重要。
360+
void (*rm_decode)(struct LogicalDecodingContext *ctx, struct XLogRecordBuffer *buf): 解码函数,
361+
用于逻辑解码场景,将 WAL日志记录转换为逻辑变更事件。
362+
*/
357363
void (*rm_mask) (char *pagedata, BlockNumber blkno);
358364
void (*rm_decode) (struct LogicalDecodingContext *ctx,
359365
struct XLogRecordBuffer *buf);
360366
} RmgrData;
367+
/**
368+
2. 掩码函数的作用
369+
掩码函数的主要作用是屏蔽页面中某些动态变化但不影响逻辑一致性的部分。这些部分可能在页面修改后发生变化,但不会影响页面的逻辑内容。例如:
370+
- 事务状态信息: 页面中的事务状态(如 xmin、xmax)可能会随着事务的提交或回滚而变化,但这些变化不影响页面的逻辑一致性。
371+
- 空闲空间指针: 页面中的空闲空间指针可能会随着插入或删除操作而变化,但这些变化不影响页面的逻辑内容。
372+
- 填充字节: 页面中用于对齐的填充字节可能会发生变化,但这些字节对页面的逻辑内容没有影响。
373+
374+
通过屏蔽这些部分,掩码函数确保了 wal_consistency_checking 只关注页面的逻辑一致性,而忽略无关的物理变化。
375+
*/
376+
361377

362378
extern PGDLLIMPORT RmgrData RmgrTable[];
363379
extern void RmgrStartup(void);

0 commit comments

Comments
 (0)