Skip to content

Support pg_rewind used without wal_log_hints and data_checksums#1349

Open
movead123 wants to merge 2 commits into
IvorySQL:masterfrom
movead123:master
Open

Support pg_rewind used without wal_log_hints and data_checksums#1349
movead123 wants to merge 2 commits into
IvorySQL:masterfrom
movead123:master

Conversation

@movead123

@movead123 movead123 commented Jun 4, 2026

Copy link
Copy Markdown

引入问题
PostgreSQL中使用pg_rewind进行脑裂后旧主跟随新主的操作,但是需要开启wal_log_hints或者data_checksums才能使用此功能,这大大降低了pg_rewind的使用范围。

目前依赖wal_log_hints或者data_checksums的原因
在pg_rewind功能引入时,由于需要处理分岔点之前修改数据,分岔点之后修改tuple hint的场景。因此需要开启wal_log_hints或者data_checksums来规避一些场景。如下是如果不开启这两个参数而使用pg_rewind,可能引起的数据不一致问题。

1.我们有一个主库 A 和一个备库 B。
2.有一个事务 xact1 正在执行,并且 xact1 修改了一些页面。
3.在 A 上执行一个检查点。
4.备库 B 被提升为主库。
5.事务 xact1 提交,并且在 A 上查询所有被 xact1 修改的数据。
6.在 A 上执行 pg_rewind。

这时如果执行rewind那么, 步骤5的修改不会被rewind察觉,因此不会重新拷贝这些page。这将导致rewind后的A和B的数据不一致。好了,这是社区版本要求wal_log_hints或者data_checksums的理由。现在我们有不开启这两个参数而规避此问题的方法。

新的方案
我们需要记录在分叉点之后提交的所有事务 ID,并在分叉点之前进一步分析 WAL 日志,找到一个起点 LSN(称为 lsn_s),该 LSN 比所有这些事务都更早。然后,我们应该从 lsn_s 读取到分叉点的 LSN,以收集这些事务所影响的页面。这样,我们就可以在 rewind 阶段将这些页面复制过来,以此达到主备一致的目的。

使用介绍
在pg_rewind的使用参数中添加了--deep-dig, 如果不加这个参数将走原生pg_rewind的逻辑,添加这个参数之后,将会走新的rewind逻辑。

Summary by CodeRabbit

  • New Features

    • Added --deep-dig (-d) option to pg_rewind to enable a deeper scan for modified pages when basic page-consistency info is missing.
    • Reworked page-map discovery so pg_rewind can perform forward then optional backward analysis when requested.
  • Bug Fixes / Reliability

    • Improved safety checks and conditional behavior to avoid unnecessary failures; use --deep-dig to proceed with extended verification.

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR extends pg_rewind with bidirectional WAL scanning controlled by a new --deep-dig option: it performs a forward page-map extraction, evaluates tracked XID bounds for safety, and when page consistency is not guaranteed can optionally run a backward WAL scan to discover additional modified pages.

Changes

Bidirectional WAL Scanning with Deep-Dig Support

Layer / File(s) Summary
Build system integration
src/bin/pg_rewind/.gitignore, src/bin/pg_rewind/Makefile, src/bin/pg_rewind/meson.build
Adds compat.c and rmgr descriptor sources (xactdesc.c, standbydesc.c) to pg_rewind build, plus gitignore and clean rules for generated sources and symlink generation rules.
Public API and internal declarations
src/bin/pg_rewind/pg_rewind.h, src/bin/pg_rewind/parsexlog.c (headers/globals)
Replaces extractPageMap with extractPageMapForward/extractPageMapBackward, and adds check_rewind_safety and check_transaction_need_rewind declarations; introduces forward declarations and globals for XID bound tracking.
Bidirectional WAL scanning and rewind decision logic
src/bin/pg_rewind/parsexlog.c
Implements forward/backward-aware page extraction: refactors forward entry, adds backward WAL traversal with page-boundary adjustment, tracks committed XID ranges via snapshot scanning, provides per-transaction rewind filtering, and implements XID precedence helpers and safety checks.
CLI integration and orchestration
src/bin/pg_rewind/pg_rewind.c
Adds --deep-dig (-d) option, computes page_consistence from target control file, replaces unconditional extraction with forward extraction plus conditional check_rewind_safety() and optional backward extraction when --deep-dig is enabled.

Sequence Diagram

sequenceDiagram
  participant Main as pg_rewind main
  participant Extract as extractPageMapForward
  participant Safety as check_rewind_safety
  participant BackExtract as extractPageMapBackward
  Main->>Extract: scan forward from target state
  Extract-->>Main: page map
  alt page_consistence is false
    Main->>Safety: check if XID bounds safe
    alt unsafe and not deep_dig
      Main->>Main: fatal error
    else unsafe and deep_dig
      Main->>BackExtract: scan backward from checkpoint
      BackExtract-->>Main: additional page map
    else safe
      Main->>Main: proceed
    end
  else page_consistence is true
    Main->>Main: proceed
  end
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I tunneled through WAL, both ways I peep,
Forward to map, then backward to leap.
When checksums hide what changes did sprout,
Deep-dig finds the pages and figures it out.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main purpose of the PR: enabling pg_rewind to work without wal_log_hints and data_checksums.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 OpenGrep (1.22.0)

OpenGrep fatal error (exit code 2): [00.18][ERROR]: Error: exception Unix_error: No such file or directory stat src/bin/pg_rewind/pg_rewind.c
Raised by primitive operation at UTmp.replace_named_pipe_by_regular_file_if_needed in file "libs/commons/UTmp.ml", line 145, characters 8-27
Called from Scan_CLI.replace_target_roots_by_regular_files_where_needed.(fun) in file "src/osemgrep/cli_scan/Scan_CLI.ml", lines 1086-1087, characters 19-65
Called from List_.fast_map in file "libs/commons/List_.ml", line 81, characters 17-20
Called fro


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (5)
src/bin/pg_rewind/parsexlog.c (4)

49-51: ⚖️ Poor tradeoff

Potential re-entrancy hazard with file-scoped globals.

The global XID tracking variables (min_commited_xid, max_commited_xid, base_xid) are never explicitly reset. While the current call pattern in pg_rewind.c ensures they're used only once per execution, this design is fragile if the code is ever refactored to allow multiple rewind operations or if these functions are called in a different order.

Consider initializing these at the start of extractPageMapForward or introducing a reset function.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bin/pg_rewind/parsexlog.c` around lines 49 - 51, The file-scoped XID
trackers min_commited_xid, max_commited_xid, and base_xid are left set between
uses; modify the code to reset them before each rewind run by initializing them
at the start of extractPageMapForward (or call a new reset function from the
start of extractPageMapForward). Specifically, set min_commited_xid =
InvalidTransactionId, max_commited_xid = InvalidTransactionId, and base_xid =
InvalidTransactionId at the beginning of extractPageMapForward (or implement and
call a static reset_xid_trackers() that does the same) so repeated calls or
future refactors won’t retain stale state.

142-148: 💤 Low value

Duplicate page-boundary logic.

This page-header-skipping logic is identical to the code in findLastCheckpoint (lines 259-265). Consider extracting it into a helper function like SkipPageHeaderIfNeeded(XLogRecPtr ptr) to avoid maintenance drift.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bin/pg_rewind/parsexlog.c` around lines 142 - 148, The
page-header-skipping logic duplicated around startpoint should be extracted into
a single helper (e.g. SkipPageHeaderIfNeeded) so both this site and the
identical block in findLastCheckpoint can call it; implement
SkipPageHeaderIfNeeded taking an XLogRecPtr (or pointer/ref) and performing the
same checks using XLOG_BLCKSZ, XLogSegmentOffset(ptr, WalSegSz), and adding
SizeOfXLogLongPHD or SizeOfXLogShortPHD as appropriate, then replace the local
duplicated block around the variable startpoint and the block in
findLastCheckpoint with calls to that helper.

498-560: 💤 Low value

Tracking snapshot in both forward and backward scans.

The code correctly tracks base_xid from XLOG_RUNNING_XACTS in both forward and backward scans (lines 544-559 have no backward guard). This is an optimization: if a running-xacts record soon after the fork has a nextXid below all post-fork commits, the backward scan can be skipped.

However, the log message at line 556 says "Get base xid" as if finding a definitive value, when in backward scan multiple progressively earlier snapshots may be found. Consider logging "Found earlier base xid: ..." for clarity.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bin/pg_rewind/parsexlog.c` around lines 498 - 560, In
track_rewind_snapshot(), the log for updating base_xid (when parsing
RM_STANDBY_ID / XLOG_RUNNING_XACTS) should reflect that during backward scans we
may find progressively earlier snapshots; modify the pg_log_info call that
currently says "Get base xid: %u at %X/%X" to use a different message when
backward is true (e.g. "Found earlier base xid: %u at %X/%X") while retaining
the original wording for forward scans; locate this in function
track_rewind_snapshot() where base_xid is assigned from xlrec->nextXid and
conditionally call pg_log_info with the message chosen based on the backward
parameter.

678-695: 💤 Low value

Document function origin.

This function duplicates TransactionIdPrecedes from backend transam.c (frontend code cannot link backend code). Add a comment to document this, making future maintenance easier:

/*
 * RewindTransactionIdPrecedes --- is id1 logically < id2?
 *
 * Adapted from TransactionIdPrecedes in src/backend/access/transam/transam.c
 * for use in frontend pg_rewind.
 */
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bin/pg_rewind/parsexlog.c` around lines 678 - 695, Add a short provenance
comment above the RewindTransactionIdPrecedes function documenting that it
duplicates/adapts the backend implementation so maintainers know the origin;
specifically state it is adapted from TransactionIdPrecedes in the backend
transam.c for use in the frontend pg_rewind and include that context in the
existing function comment block.
src/bin/pg_rewind/pg_rewind.c (1)

107-107: ⚡ Quick win

Clarify --deep-dig help text.

The help text "perform a deep dig for more wal" is vague. Consider a more descriptive message that explains when this option is needed:

-	printf(_("  -d, --deep-dig                 perform a deep dig for more wal\n"));
+	printf(_("  -d, --deep-dig                 enable backward WAL scan when page\n"
+			 "                                 consistency cannot be guaranteed\n"));

This helps users understand the option is specifically for cases when neither wal_log_hints nor data checksums are enabled.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bin/pg_rewind/pg_rewind.c` at line 107, Update the help string printed in
pg_rewind.c for the --deep-dig option (the printf call that currently outputs " 
-d, --deep-dig                 perform a deep dig for more wal") to a clearer
description that states this option forces a more thorough scan of pages to
locate WAL when neither wal_log_hints nor data checksums are enabled; mention
that it is slower but required in those cases so users know when to use
-d/--deep-dig.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/bin/pg_rewind/parsexlog.c`:
- Around line 159-182: The function extractPageMapBackward lacks a defensive
guard against an unset min_commited_xid; add an early check at the top of
extractPageMapBackward that tests if min_commited_xid == InvalidTransactionId
and call pg_fatal with a clear message (e.g., "extractPageMapBackward called
without committed transactions") to prevent infinite backward WAL scanning; keep
the existing termination check (base_xid <= min_commited_xid) and the rest of
the loop unchanged.
- Around line 345-354: The safety logic in check_rewind_safety must explicitly
handle the "no snapshot found" case: if min_commited_xid == InvalidTransactionId
return true, then if base_xid == InvalidTransactionId return false (force a
backward scan), otherwise keep the existing comparison (if base_xid <=
min_commited_xid return true; else false); update the check_rewind_safety
function to add the explicit base_xid == InvalidTransactionId branch so an unset
base_xid (left as InvalidTransactionId by extractPageMapForward) does not
incorrectly permit rewinding.

In `@src/bin/pg_rewind/pg_rewind.c`:
- Around line 508-518: Fix the conditional block that handles the backward scan:
adjust style to use spaces after if (e.g., if (!page_consistence) and if
(!deep_dig)), correct grammar and clarity in the pg_fatal message (replace "Can
not safety rewind the target cluster, use --deep-dig to have a try." with a
clearer message such as "Cannot safely rewind the target cluster; rerun with
--deep-dig to attempt a deeper scan."), and leave the call to
extractPageMapBackward(datadir_target, chkptrec, lastcommontliIndex,
restore_command) unchanged when deep_dig is true; retain the
check_rewind_safety() logic and the use of page_consistence and deep_dig flags.

---

Nitpick comments:
In `@src/bin/pg_rewind/parsexlog.c`:
- Around line 49-51: The file-scoped XID trackers min_commited_xid,
max_commited_xid, and base_xid are left set between uses; modify the code to
reset them before each rewind run by initializing them at the start of
extractPageMapForward (or call a new reset function from the start of
extractPageMapForward). Specifically, set min_commited_xid =
InvalidTransactionId, max_commited_xid = InvalidTransactionId, and base_xid =
InvalidTransactionId at the beginning of extractPageMapForward (or implement and
call a static reset_xid_trackers() that does the same) so repeated calls or
future refactors won’t retain stale state.
- Around line 142-148: The page-header-skipping logic duplicated around
startpoint should be extracted into a single helper (e.g.
SkipPageHeaderIfNeeded) so both this site and the identical block in
findLastCheckpoint can call it; implement SkipPageHeaderIfNeeded taking an
XLogRecPtr (or pointer/ref) and performing the same checks using XLOG_BLCKSZ,
XLogSegmentOffset(ptr, WalSegSz), and adding SizeOfXLogLongPHD or
SizeOfXLogShortPHD as appropriate, then replace the local duplicated block
around the variable startpoint and the block in findLastCheckpoint with calls to
that helper.
- Around line 498-560: In track_rewind_snapshot(), the log for updating base_xid
(when parsing RM_STANDBY_ID / XLOG_RUNNING_XACTS) should reflect that during
backward scans we may find progressively earlier snapshots; modify the
pg_log_info call that currently says "Get base xid: %u at %X/%X" to use a
different message when backward is true (e.g. "Found earlier base xid: %u at
%X/%X") while retaining the original wording for forward scans; locate this in
function track_rewind_snapshot() where base_xid is assigned from xlrec->nextXid
and conditionally call pg_log_info with the message chosen based on the backward
parameter.
- Around line 678-695: Add a short provenance comment above the
RewindTransactionIdPrecedes function documenting that it duplicates/adapts the
backend implementation so maintainers know the origin; specifically state it is
adapted from TransactionIdPrecedes in the backend transam.c for use in the
frontend pg_rewind and include that context in the existing function comment
block.

In `@src/bin/pg_rewind/pg_rewind.c`:
- Line 107: Update the help string printed in pg_rewind.c for the --deep-dig
option (the printf call that currently outputs "  -d, --deep-dig                
perform a deep dig for more wal") to a clearer description that states this
option forces a more thorough scan of pages to locate WAL when neither
wal_log_hints nor data checksums are enabled; mention that it is slower but
required in those cases so users know when to use -d/--deep-dig.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6c0526a5-da16-4f0b-b41a-390756b43156

📥 Commits

Reviewing files that changed from the base of the PR and between 852ee63 and 1ba49fd.

📒 Files selected for processing (6)
  • src/bin/pg_rewind/.gitignore
  • src/bin/pg_rewind/Makefile
  • src/bin/pg_rewind/meson.build
  • src/bin/pg_rewind/parsexlog.c
  • src/bin/pg_rewind/pg_rewind.c
  • src/bin/pg_rewind/pg_rewind.h

Comment on lines +159 to +182
for (;;)
{
XLogBeginRead(xlogreader, searchptr);
record = XLogReadRecord(xlogreader, &errormsg);

if (record == NULL)
{
if (errormsg)
pg_fatal("could not find previous WAL record at %X/%X: %s",
LSN_FORMAT_ARGS(searchptr),
errormsg);
else
pg_fatal("could not find previous WAL record at %X/%X",
LSN_FORMAT_ARGS(searchptr));
}

extractPageInfo(xlogreader, true);
/* We can break if met a safety snapshot */
if (base_xid <= min_commited_xid)
break;
/* Walk backwards to previous record. */
searchptr = record->xl_prev;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing safety check for infinite backward scan.

If extractPageMapBackward is called when min_commited_xid is InvalidTransactionId (0), the termination condition base_xid <= min_commited_xid at line 177 will never be satisfied by any valid base_xid, causing the function to scan backward until WAL exhaustion or error.

While pg_rewind.c currently guards this with check_rewind_safety(), the function itself should be defensive.

Add an early safety check at the start of extractPageMapBackward:

if (min_commited_xid == InvalidTransactionId)
    pg_fatal("extractPageMapBackward called without committed transactions");
🛡️ Proposed safety check
 extractPageMapBackward(const char *datadir, XLogRecPtr startpoint, int tliIndex,
 				   const char *restoreCommand)
 {
+	/* Ensure we have valid XID bounds before scanning backward */
+	if (min_commited_xid == InvalidTransactionId)
+		pg_fatal("extractPageMapBackward called without committed transactions");
+
 	/* Walk backwards, starting from the given record */
 	XLogRecord *record;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bin/pg_rewind/parsexlog.c` around lines 159 - 182, The function
extractPageMapBackward lacks a defensive guard against an unset
min_commited_xid; add an early check at the top of extractPageMapBackward that
tests if min_commited_xid == InvalidTransactionId and call pg_fatal with a clear
message (e.g., "extractPageMapBackward called without committed transactions")
to prevent infinite backward WAL scanning; keep the existing termination check
(base_xid <= min_commited_xid) and the rest of the loop unchanged.

Comment thread src/bin/pg_rewind/parsexlog.c
Comment on lines +508 to +518
if(!page_consistence && !check_rewind_safety())
{
/*
* Next time we will search more wal which will spend more time, and
* if user prefer a deep dig we will do it. Otherwise, we just stop here.
*/
if(!deep_dig)
pg_fatal("Can not safety rewind the target cluster, use --deep-dig to have a try.");
extractPageMapBackward(datadir_target, chkptrec, lastcommontliIndex,
restore_command);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix grammar, style, and error message clarity.

Multiple issues in the conditional backward scan block:

  1. Grammar error (line 515): "Can not safety rewind" → "Cannot safely rewind"
  2. Style violation (lines 508, 514): Missing space after if — should be if (!page_consistence and if (!deep_dig) per PostgreSQL coding style
  3. Unclear error message (line 515): "have a try" is informal and vague

Consider:

📝 Proposed fix
-	if(!page_consistence && !check_rewind_safety())
+	if (!page_consistence && !check_rewind_safety())
 	{
 		/*
-		 * Next time we will search more wal which will spend more time, and
-		 * if user prefer a deep dig we will do it. Otherwise, we just stop here.
+		 * Backward WAL scan is required but will take additional time.
+		 * Proceed only if user explicitly requested --deep-dig.
 		 */
-		if(!deep_dig)
-			pg_fatal("Can not safety rewind the target cluster, use --deep-dig to have a try.");
+		if (!deep_dig)
+			pg_fatal("cannot safely rewind target cluster without wal_log_hints or checksums; use --deep-dig to enable backward WAL scanning");
 		extractPageMapBackward(datadir_target, chkptrec, lastcommontliIndex,
 							   restore_command);
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bin/pg_rewind/pg_rewind.c` around lines 508 - 518, Fix the conditional
block that handles the backward scan: adjust style to use spaces after if (e.g.,
if (!page_consistence) and if (!deep_dig)), correct grammar and clarity in the
pg_fatal message (replace "Can not safety rewind the target cluster, use
--deep-dig to have a try." with a clearer message such as "Cannot safely rewind
the target cluster; rerun with --deep-dig to attempt a deeper scan."), and leave
the call to extractPageMapBackward(datadir_target, chkptrec, lastcommontliIndex,
restore_command) unchanged when deep_dig is true; retain the
check_rewind_safety() logic and the use of page_consistence and deep_dig flags.

@gaoxueyu gaoxueyu requested a review from bigplaice June 5, 2026 02:03
@bigplaice

Copy link
Copy Markdown
Collaborator

Thanks for bringing new feature to IvorySQL!
I will review the code change and provide feedback.

Steven

@gaoxueyu gaoxueyu requested a review from evanchaoli June 11, 2026 08:54
@bigplaice

Copy link
Copy Markdown
Collaborator

Thanks for working on this — allowing pg_rewind to operate without wal_log_hints or data_checksums addresses a real production need, and the two-phase WAL scanning approach is conceptually interesting. However, there are several issues that need to be addressed before this can be merged.


Correctness Issues (must fix)

1. ABORT records corrupt min_commited_xid (critical)

In track_rewind_snapshot(), for XLOG_XACT_ABORT / XLOG_XACT_ABORT_PREPARED records, neither the XLOG_XACT_COMMIT nor XLOG_XACT_COMMIT_PREPARED branch is taken, leaving current_xid as InvalidTransactionId (0). There is no early return before the min/max update block, so RewindTransactionIdPrecedes(0, any_normal_xid) returns true and overwrites min_commited_xid with 0. This causes check_rewind_safety() to return true (hitting the InvalidTransactionId branch), allowing rewind to proceed silently when it should not — risking data corruption. This will trigger on any WAL that contains rolled-back transactions, which is virtually always the case.

2. Backward scan has no guaranteed termination

The loop in extractPageMapBackward() terminates only when base_xid <= min_commited_xid. Two failure modes exist: (a) if no XLOG_RUNNING_XACTS record is found, base_xid stays InvalidTransactionId (0), and 0 <= any_normal_xid is always true, so the loop exits immediately after the first record without scanning anything useful; (b) if the condition is never satisfied, the loop walks all the way back via xl_prev until it hits xl_prev == 0, at which point XLogBeginRead(xlogreader, 0) fails with a fatal error. A secondary termination condition based on checkpoint records (checking CheckPoint.nextXid <= min_commited_xid) and a hard stop for xl_prev == InvalidXLogRecPtr are both needed.

3. XID comparisons are not wraparound-safe

if (base_xid <= min_commited_xid) in both extractPageMapBackward() and check_rewind_safety() uses direct <= on 32-bit TransactionId values. This gives wrong results near XID wraparound. All such comparisons should use RewindTransactionIdPrecedes(), which is already defined in this patch.

4. check_rewind_safety() lacks documentation for its correctness argument

The function's comments just say "it's safe" without explaining why. The actual argument depends on PostgreSQL's full-page write (FPW) mechanism: if all post-fork committed transactions started after the last checkpoint, their page modifications all carry FPW records already captured by the forward scan. This reasoning should be documented clearly so reviewers can verify it. Additionally, the function needs a guard for base_xid == InvalidTransactionId to avoid a premature "safe" verdict when no XLOG_RUNNING_XACTS record has been seen yet.


Design Issues

5. Removing the sanity check unconditionally changes default behavior

The existing check in sanityChecks() was removed entirely rather than gated behind --deep-dig. This means users without checksums/wal_log_hints will now run a full forward scan before getting a fatal error — wasting I/O. Worse, if check_rewind_safety() incorrectly returns true (due to bug #1), the rewind proceeds silently with potentially missing page copies. The original check should be preserved and bypassed only when --deep-dig is explicitly specified, with a prominent warning.

6. XID range filter [min, max] is too coarse

check_transaction_need_rewind() copies pages for all transactions whose XID falls in [min_commited_xid, max_commited_xid], but this range includes aborted transactions whose page modifications should not be copied. The code comment acknowledges this but offers no path forward. A bitmap or list of actually-committed XIDs would be more accurate; palloc is available in frontend code.

7. Global XID trackers are never reset

min_commited_xid, max_commited_xid, and base_xid are static globals with no explicit reset at function entry. This is harmless for a single-use tool today, but will cause state pollution if pg_rewind is ever embedded in a library or run multiple times in a test harness. They should be initialised at the start of extractPageMapForward().

8. New backend dependencies increase maintenance coupling

The patch pulls in xactdesc.c and standbydesc.c from the backend via symlinks. These files carry heavy backend infrastructure (StringInfo, appendStringInfo, etc.) even though only ParseCommitRecord and a few struct definitions are actually needed. If upstream PostgreSQL changes the WAL record format or rmgrdesc interfaces, pg_rewind will need to track those changes. Consider extracting only the needed declarations instead of linking the full descriptor files.


Test Coverage

The patch adds no regression tests. Given that this bypasses a long-standing safety restriction, tests are essential before merging. At minimum, please add:

  • TAP end-to-end tests for rewind without checksums/wal_log_hints
  • TAP tests covering the --deep-dig path
  • Tests for check_rewind_safety() returning false (user-facing error path)
  • Tests with mixed commit/abort WAL to verify XID tracking correctness
  • XID wraparound boundary tests

@movead123

Copy link
Copy Markdown
Author

感谢指出代码中的诸多问题,已经根据review意见做出了修改,但是对第6点保持了原来的设计。
当我们在设计上达成一致之后,再去写用例。

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/bin/pg_rewind/parsexlog.c`:
- Around line 350-357: The safety check in check_rewind_safety incorrectly uses
the strict wraparound-safe comparison RewindTransactionIdPrecedes(base_xid,
min_commited_xid) which rejects the equal boundary; change this to the inclusive
wraparound-safe helper (e.g. RewindTransactionIdPrecedesOrEquals) or otherwise
perform an inclusive comparison so that base_xid == min_commited_xid is treated
as safe; update the conditional that references base_xid and min_commited_xid
accordingly.
- Around line 569-587: The RM_XLOG_ID checkpoint-handling block (checking
XLOG_CHECKPOINT_SHUTDOWN/ONLINE and updating base_xid) is never reached because
track_rewind_snapshot() returns early for any rmgr other than RM_XACT_ID or
RM_STANDBY_ID; change that early-return logic so RM_XLOG_ID is allowed through.
Update the condition in track_rewind_snapshot() (or the surrounding
early-return) to treat RM_XLOG_ID like RM_XACT_ID/RM_STANDBY_ID (i.e., do not
return for RM_XLOG_ID), so the XLogRecGetInfo/XLogRecGetData checkpoint handling
can run and set base_xid before the downstream safety checks that call
RewindTransactionIdPrecedes. Ensure the change still preserves existing behavior
for other rmgr ids and add a comment noting why RM_XLOG_ID must be processed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4ef5af29-0f28-43d3-988e-4586f24453ae

📥 Commits

Reviewing files that changed from the base of the PR and between 1ba49fd and 4b84791.

📒 Files selected for processing (2)
  • src/bin/pg_rewind/parsexlog.c
  • src/bin/pg_rewind/pg_rewind.c
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/bin/pg_rewind/pg_rewind.c

Comment on lines +350 to +357
bool check_rewind_safety(void)
{
/* If no committed transactions, it's safe to rewind */
if (!TransactionIdIsValid(min_commited_xid))
return true;
/* If base_xid is less than or equal to min_commited_xid, it's safe */
if (TransactionIdIsValid(base_xid) && RewindTransactionIdPrecedes(base_xid, min_commited_xid))
return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the inclusive XID comparison here.

Line 356 switched the safety test to the wraparound-safe helper, but it is still strict <. The backward pass stops on base_xid <= min_commited_xid, and pg_rewind.c treats false here as “deep-dig or fail”, so an equal boundary now rejects a safe rewind or forces unnecessary extra WAL scanning.

🩹 Minimal fix
 bool check_rewind_safety(void)
 {
 	/* If no committed transactions, it's safe to rewind */
 	if (!TransactionIdIsValid(min_commited_xid))
 		return true;
 	/* If base_xid is less than or equal to min_commited_xid, it's safe */
-	if (TransactionIdIsValid(base_xid) && RewindTransactionIdPrecedes(base_xid, min_commited_xid))
+	if (TransactionIdIsValid(base_xid) &&
+		RewindTransactionIdPrecedesOrEquals(base_xid, min_commited_xid))
 		return true;
 	return false;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
bool check_rewind_safety(void)
{
/* If no committed transactions, it's safe to rewind */
if (!TransactionIdIsValid(min_commited_xid))
return true;
/* If base_xid is less than or equal to min_commited_xid, it's safe */
if (TransactionIdIsValid(base_xid) && RewindTransactionIdPrecedes(base_xid, min_commited_xid))
return true;
bool check_rewind_safety(void)
{
/* If no committed transactions, it's safe to rewind */
if (!TransactionIdIsValid(min_commited_xid))
return true;
/* If base_xid is less than or equal to min_commited_xid, it's safe */
if (TransactionIdIsValid(base_xid) &&
RewindTransactionIdPrecedesOrEquals(base_xid, min_commited_xid))
return true;
return false;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bin/pg_rewind/parsexlog.c` around lines 350 - 357, The safety check in
check_rewind_safety incorrectly uses the strict wraparound-safe comparison
RewindTransactionIdPrecedes(base_xid, min_commited_xid) which rejects the equal
boundary; change this to the inclusive wraparound-safe helper (e.g.
RewindTransactionIdPrecedesOrEquals) or otherwise perform an inclusive
comparison so that base_xid == min_commited_xid is treated as safe; update the
conditional that references base_xid and min_commited_xid accordingly.

Comment on lines +569 to +587
else if (rmid == RM_XLOG_ID)
{
info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;

if (info == XLOG_CHECKPOINT_SHUTDOWN || info == XLOG_CHECKPOINT_ONLINE)
{
TransactionId next_xid = InvalidTransactionId;
char *rec = XLogRecGetData(record);
CheckPoint *checkpoint = (CheckPoint *) rec;

next_xid = XidFromFullTransactionId(checkpoint->nextXid);
if (!TransactionIdIsValid(base_xid) || RewindTransactionIdPrecedes(next_xid, base_xid))
{
base_xid = next_xid;
if (showprogress)
pg_log_info("Get next xid: %u at %X/%X", base_xid, LSN_FORMAT_ARGS(record->ReadRecPtr));
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

The new checkpoint fallback is unreachable.

This RM_XLOG branch never runs because track_rewind_snapshot() still returns at Lines 509-510 for any rmgr other than RM_XACT_ID or RM_STANDBY_ID. In histories where a checkpoint is the only available source of nextXid, base_xid stays invalid and both the safety check and backward termination logic misclassify the WAL as needing a deeper scan.

🩹 Minimal fix
-	if (rmid != RM_XACT_ID && rmid != RM_STANDBY_ID)
+	if (rmid != RM_XACT_ID &&
+		rmid != RM_STANDBY_ID &&
+		rmid != RM_XLOG_ID)
 		return;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bin/pg_rewind/parsexlog.c` around lines 569 - 587, The RM_XLOG_ID
checkpoint-handling block (checking XLOG_CHECKPOINT_SHUTDOWN/ONLINE and updating
base_xid) is never reached because track_rewind_snapshot() returns early for any
rmgr other than RM_XACT_ID or RM_STANDBY_ID; change that early-return logic so
RM_XLOG_ID is allowed through. Update the condition in track_rewind_snapshot()
(or the surrounding early-return) to treat RM_XLOG_ID like
RM_XACT_ID/RM_STANDBY_ID (i.e., do not return for RM_XLOG_ID), so the
XLogRecGetInfo/XLogRecGetData checkpoint handling can run and set base_xid
before the downstream safety checks that call RewindTransactionIdPrecedes.
Ensure the change still preserves existing behavior for other rmgr ids and add a
comment noting why RM_XLOG_ID must be processed.

@evanchaoli

Copy link
Copy Markdown
Collaborator

感谢提交。我看了这个提交,并且把你之前在pg社区提交的讨论也仔细看了一遍,在进入到代码层面之前,有几个问题我想先理解一下:

  1. 感觉上,这个功能主要的目的是为了少写WAL日志。请解释一下实际的使用场景以及这是否是一个在生产环境中遇到过的痛点问题?如果是,可否给一个真实案例。
  2. 这个提交的理论基础是通过回溯足够的WAL日志以准确判断两边的block是否一致。请具体阐述和证明一下这个理论的正确性。
  3. 请问如果在极端情况下找不到足够老的WAL的情况下,怎么处理?此时因为那两个开关都没打开,老的方式也无法使用了。
  4. 在PG19里,有几个新功能可能影响hint bits,比如b46e1e54d,可能在query的时候修改hint bits,请问是否有评估过对这个提交有没有影响?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants