Skip to content

Commit 5c6586b

Browse files
PXB-3807 : Flag system/undo pages with an LSN ahead of the recovered LSN
https://perconadev.atlassian.net/browse/PXB-3807 Problem: -------- The system/undo checksum pass confirms a page's checksum is internally consistent, but a page can be checksum-valid yet carry an FIL_PAGE_LSN ahead of the LSN we recovered to -- e.g. an incomplete or over-applied backup, the wrong redo, or a corrupt LSN field. Such a page was accepted silently. Fix: ---- Extend xb_checksum_system_undo_spaces(): for every checksum-valid page, read FIL_PAGE_LSN and compare it to the recovered system LSN (log_get_lsn). A page whose LSN is ahead is reported and fails the check. A page that failed the checksum is skipped (its LSN field is unreliable). Unlike innochecksum, this runs inside the engine after recovery, so the recovered system LSN is known. Tests: ------ suites/check_tables/future_lsn_debug.sh uses the check_system_inject_future_lsn DBUG point to force a page LSN past the system LSN and asserts --check-tables reports it and exits non-zero. incremental_future_lsn.sh is a regression guard proving an incremental-merge prepare does not raise a false future-LSN report (the .delta pages' future LSNs are caught up by redo before check-tables runs). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 9979f60 commit 5c6586b

3 files changed

Lines changed: 155 additions & 0 deletions

File tree

storage/innobase/xtrabackup/src/xtrabackup.cc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6872,6 +6872,25 @@ static bool xb_checksum_system_undo_spaces() {
68726872
xb::error() << "check-tables: checksum mismatch on page " << space_id
68736873
<< ":" << page_no << "; the tablespace is corrupted.";
68746874
ok = false;
6875+
/* A corrupt page's LSN field is unreliable; skip the LSN check. */
6876+
continue;
6877+
}
6878+
6879+
/* The checksum is valid. Unlike innochecksum -- which has no system LSN
6880+
to compare against -- we run inside the engine after recovery, so we know
6881+
the recovered system LSN. A page whose LSN is ahead of it carries changes
6882+
beyond the redo we applied (an incomplete/over-applied backup, the wrong
6883+
redo, or a corrupt LSN field), so report it and fail the check. */
6884+
lsn_t page_lsn = mach_read_from_8(buf + FIL_PAGE_LSN);
6885+
const lsn_t sys_lsn = log_get_lsn(*log_sys);
6886+
DBUG_EXECUTE_IF("check_system_inject_future_lsn",
6887+
page_lsn = (page_no > 0) ? sys_lsn + 1 : page_lsn;);
6888+
if (page_lsn > sys_lsn) {
6889+
xb::error() << "check-tables: page " << space_id << ":" << page_no
6890+
<< " has LSN " << page_lsn
6891+
<< " ahead of the recovered system LSN " << sys_lsn
6892+
<< "; the backup is missing redo or the page is corrupt.";
6893+
ok = false;
68756894
}
68766895
}
68776896
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
############################################################################
2+
# PXB-3807 : during --check-tables the system/undo checksum pass also flags a
3+
# page whose LSN is AHEAD of the recovered system LSN. Such a page carries
4+
# changes beyond the redo that was applied (an incomplete/over-applied backup,
5+
# the wrong redo log, or a corrupt LSN field). Unlike innochecksum -- which
6+
# has no system LSN to compare against -- xtrabackup runs inside the engine
7+
# after recovery and knows log_get_lsn(*log_sys), so it can detect this.
8+
#
9+
# A checksum-valid page with a forged future LSN is impractical to build on
10+
# disk (it would need a recomputed checksum), so this uses the
11+
# check_system_inject_future_lsn DBUG point for a deterministic positive.
12+
############################################################################
13+
14+
. inc/common.sh
15+
16+
require_debug_pxb_version
17+
18+
start_server
19+
20+
mysql test <<EOF
21+
CREATE TABLE t1 (a INT PRIMARY KEY AUTO_INCREMENT, b VARCHAR(200));
22+
EOF
23+
for i in $(seq 1 50); do
24+
run_cmd $MYSQL $MYSQL_ARGS test -e \
25+
"INSERT INTO t1 (b) VALUES (REPEAT('x', 200));"
26+
done
27+
28+
vlog "Take backup"
29+
xtrabackup --backup --target-dir=$topdir/backup
30+
31+
#
32+
# Control: clean backup must pass (the future-LSN check must NOT false-positive
33+
# on legitimate pages, whose LSN is <= the recovered system LSN).
34+
#
35+
vlog "=== Control: clean backup passes (no false future-LSN) ==="
36+
cp -r $topdir/backup $topdir/backup_ok
37+
xtrabackup --prepare --check-tables --target-dir=$topdir/backup_ok 2>&1 \
38+
| tee $topdir/ok.log
39+
grep -q "All table checks passed" $topdir/ok.log || \
40+
die "Control: clean backup unexpectedly failed --check-tables"
41+
vlog "Control passed"
42+
43+
#
44+
# Injected future LSN: a system/undo page reported as ahead of the system LSN
45+
# must fail the prepare.
46+
#
47+
vlog "=== Injected future LSN: check_system_inject_future_lsn ==="
48+
cp -r $topdir/backup $topdir/backup_fl
49+
run_cmd_expect_failure $XB_BIN $XB_ARGS --prepare --check-tables \
50+
--debug=d,check_system_inject_future_lsn \
51+
--target-dir=$topdir/backup_fl 2>&1 | tee $topdir/fl.log
52+
53+
grep -q "ahead of the recovered system LSN" $topdir/fl.log || \
54+
die "Injected: future-LSN page not reported"
55+
grep -q "Table check failed" $topdir/fl.log || \
56+
die "Injected: Table check failed message not found"
57+
vlog "Injected future-LSN sub-test passed"
58+
59+
vlog "All PXB-3807 future-LSN sub-tests passed"
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
############################################################################
2+
# PXB-3807 : regression guard for the future-LSN check on INCREMENTAL prepare.
3+
#
4+
# Incremental --prepare merges .delta pages whose LSN is ahead of the current
5+
# system LSN; the system LSN only catches up afterwards, when the incremental's
6+
# redo is applied. The system/undo future-LSN check (which fails a page whose
7+
# LSN > log_get_lsn(*log_sys)) runs in the --check-tables block, AFTER redo
8+
# apply, so the system LSN has already reached the merged pages' LSN -- it must
9+
# NOT false-positive on a valid incremental backup.
10+
#
11+
# This test merges an incremental and runs --check-tables both right after the
12+
# delta merge and on the final prepare; both must pass.
13+
############################################################################
14+
15+
. inc/common.sh
16+
17+
start_server --innodb_file_per_table
18+
19+
mysql test <<EOF
20+
CREATE TABLE t1 (a INT PRIMARY KEY AUTO_INCREMENT, b VARCHAR(200));
21+
EOF
22+
for i in $(seq 1 200); do
23+
run_cmd $MYSQL $MYSQL_ARGS test -e \
24+
"INSERT INTO t1 (b) VALUES (REPEAT('x', 200));"
25+
done
26+
27+
vlog "Full backup"
28+
xtrabackup --backup --target-dir=$topdir/full
29+
30+
vlog "Make changes after the full backup (generates higher-LSN delta pages)"
31+
for i in $(seq 1 400); do
32+
run_cmd $MYSQL $MYSQL_ARGS test -e \
33+
"INSERT INTO t1 (b) VALUES (REPEAT('y', 200));"
34+
done
35+
run_cmd $MYSQL $MYSQL_ARGS test -e "UPDATE t1 SET b = REPEAT('z', 200);"
36+
# Force a checkpoint so the changed pages are flushed with their new LSNs.
37+
shutdown_server
38+
start_server --innodb_file_per_table
39+
run_cmd $MYSQL $MYSQL_ARGS test -e \
40+
"BEGIN; UPDATE t1 SET b = REPEAT('w', 200); ROLLBACK;"
41+
42+
vlog "Incremental backup"
43+
xtrabackup --backup --incremental-basedir=$topdir/full \
44+
--target-dir=$topdir/inc1
45+
46+
vlog "Prepare full (apply-log-only)"
47+
xtrabackup --prepare --apply-log-only --target-dir=$topdir/full
48+
49+
vlog "Merge incremental + --check-tables (delta pages just written, redo applied)"
50+
xtrabackup --prepare --apply-log-only --incremental-dir=$topdir/inc1 \
51+
--check-tables --target-dir=$topdir/full 2>&1 | tee $topdir/merge.log
52+
grep -q "verifying checksums of tablespace" $topdir/merge.log || \
53+
die "checksum pass did not run during incremental merge"
54+
grep -q "ahead of the recovered system LSN" $topdir/merge.log && \
55+
die "FALSE POSITIVE: future-LSN check fired on a valid incremental merge"
56+
# Engine oracle: the B-tree phase reads pages through the buffer pool with
57+
# InnoDB's own future-LSN check active (recv_lsn_checks_on is on post-recovery).
58+
# It must report NO "in the future" page once recovery has caught the system
59+
# LSN up to the merged delta pages -- the condition under which our check fires.
60+
grep -q "is in the future" $topdir/merge.log && \
61+
die "a page was still in the future when check-tables ran on the incremental merge"
62+
grep -q "All table checks passed" $topdir/merge.log || \
63+
die "incremental merge --check-tables failed unexpectedly"
64+
vlog "Incremental merge check passed (no false future-LSN)"
65+
66+
vlog "Final prepare + --check-tables"
67+
xtrabackup --prepare --check-tables --target-dir=$topdir/full 2>&1 \
68+
| tee $topdir/final.log
69+
grep -q "ahead of the recovered system LSN" $topdir/final.log && \
70+
die "FALSE POSITIVE: future-LSN check fired on the final prepare"
71+
grep -q "is in the future" $topdir/final.log && \
72+
die "a page was still in the future when check-tables ran on the final prepare"
73+
grep -q "All table checks passed" $topdir/final.log || \
74+
die "final --check-tables failed unexpectedly"
75+
vlog "Final prepare check passed"
76+
77+
vlog "PXB-3807 incremental future-LSN regression guard passed"

0 commit comments

Comments
 (0)