You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[Bug]: WAL replay of an entry referencing a deleted data file crashes InitPhase2 (exit 139), still on main with #3429 — is a supported way to discard unreplayable entries possible? #3497
Related: #3435 (same reporter, same instance — WAL files are never recycled and manual cleanup by timestamp/size crashes InitPhase2). This issue reports a related but different failure mode, and specifically tests whether #3429 covers it. As far as we can measure, it does not.
Version or Commit ID
Two builds, tested side by side on identical fresh copies of the same data volume:
Control:v0.7.3-0-d755c5a (image infiniflow/infinity:v0.7.3-x64-v3, build 2026-08-06) — the build our production instance runs.
Infinity deployment: Docker, standalone mode, via RAGFlow v0.27.1
Data layout: default /var/infinity layout on a named Docker volume (wal/, data/, persistence/, catalog/)
Same instance as #3435: at the time of this test, wal/ held 58,742 files (1.6 GB), 51,147 of them exactly 82 bytes
Config: stock infinity_conf.toml, default paths, log_level=info
Actual behavior and How to reproduce it
Context — how the volume got into this state
The engine crashed during a drop/cleanup operation. The WAL entries written after the last checkpoint reference data files that the crashed operation had already deleted. Since then, every boot of this volume crashes; the only recovery we have found is manually quarantining wal.log* files (moving, not deleting), which forfeits the entire post-checkpoint tail.
What happens on boot (6/6 runs, deterministic)
Startup reads the catalog fine and finds the latest checkpoint: Find and set checkpoint max commit ts: 499869.
WAL replay phases 1–2 complete: 15 entries are found after the checkpoint and parse without any checksum/size error — the WAL stream itself is intact.
While executing the replay of one of those entries (a WalCmdAppendV2), the engine fails to load the data file the entry references:
[critical] GetObjCache Failed to find object for local path db_1/tbl_11/seg_0/blk_0/col_3_out: Key not found: pm|object|db_1/tbl_11/seg_0/blk_0/col_3_out
[critical] Error: Failed to find object for local path /var/infinity/data/db_1/tbl_11/seg_0/blk_0/col_3_out@src/storage/buffer/file_worker/file_worker_impl.cpp:284
...
[critical] Error: TerminateHandler: Unhandled Exception: Failed to find object for local path /var/infinity/data/db_1/tbl_11/seg_0/blk_0/col_3_out@src/storage/buffer/file_worker/file_worker_impl.cpp:284
The terminate handler logs the unhandled exception and the process dies with exit code 139 (SIGSEGV) ~60 seconds into every start. Stack trace from the nightly build:
0# infinity::PrintStacktrace@infinity_core(...) at /infinity/src/common/utility/exception_impl.cpp:48
1# infinity::UnrecoverableError@infinity_core(...) at /infinity/src/common/utility/exception_impl.cpp:85
2# infinity::FileWorker@infinity_core::GetFilePathInner[abi:cxx11](bool) at /infinity/src/storage/buffer/file_worker/file_worker_impl.cpp:284
3# infinity::FileWorker@infinity_core::ReadFromFile(bool) at /infinity/src/storage/buffer/file_worker/file_worker_impl.cpp:207
4# infinity::BufferObj@infinity_core::LoadNoLock(bool) at /infinity/src/storage/buffer/buffer_obj_impl.cpp:117
5# infinity::BufferObj@infinity_core::Load(bool) at /infinity/src/storage/buffer/buffer_obj_impl.cpp:178
6# infinity::VarBufferManager@infinity_core::GetInnerNoLock() at /infinity/src/storage/column_vector/var_buffer_impl.cpp:187
7# infinity::VarBufferManager@infinity_core::TotalSize() at /infinity/src/storage/column_vector/var_buffer_impl.cpp:211
8# infinity::NewTxn@infinity_core::AppendInColumn(...) at /infinity/src/storage/new_txn/new_txn_data_impl.cpp:1016
9# infinity::NewTxn@infinity_core::AppendInBlock(...) at /infinity/src/storage/new_txn/new_txn_data_impl.cpp:948
10# infinity::NewTxn@infinity_core::CommitBottomAppend(infinity::WalCmdAppendV2@infinity_core*) at /infinity/src/storage/new_txn/new_txn_data_impl.cpp:1882
11# infinity::NewTxn@infinity_core::CommitBottom() at /infinity/src/storage/new_txn/new_txn_impl.cpp:4578
12# infinity::NewTxn@infinity_core::CommitReplay() at /infinity/src/storage/new_txn/new_txn_impl.cpp:2151
13# infinity::NewTxnManager@infinity_core::CommitReplayTxn(...) at /infinity/src/storage/new_txn/new_txn_manager_impl.cpp:327
14# infinity::WalManager@infinity_core::ReplayWalEntries(...) at /infinity/src/storage/wal/wal_manager_impl.cpp:719
15# infinity::Storage@infinity_core::AdminToWriter() at /infinity/src/storage/storage_impl.cpp:326
16# infinity::Storage@infinity_core::SetStorageMode(...) at /infinity/src/storage/storage_impl.cpp:835
17# infinity::InfinityContext@infinity_core::ChangeServerRole(...) at /infinity/src/main/infinity_context_impl.cpp:132
18# infinity::InfinityContext@infinity_core::InitPhase2(bool) at /infinity/src/main/infinity_context_impl.cpp:90
19# main at /infinity/src/bin/infinity_main.cpp:285
Why this looks like #3429 territory but isn't covered by it
#3429 fixes the WAL reading layer (out-of-bounds reads on corrupt entries, over-aggressive purge) — and in our repro the reading layer works: all 15 post-checkpoint entries are found and parsed cleanly on both builds. The crash is in the execution of replay — CommitReplay → CommitBottomAppend → append into a column whose backing file no longer exists. We verified this empirically with a controlled A/B:
Two fresh identical clones of the poisoned volume (cp -a into disposable volumes; source mounted read-only; one clone per arm, so the control run could not contaminate the treatment run).
Control arm — v0.7.3-0-d755c5a: 3/3 boots → exit 139.
Treatment arm — main@-0-eca7266 (with Fix corrupt WAL handling: out-of-bounds reads and over-aggressive purge #3429): 3/3 boots → exit 139, identical critical line (file_worker_impl.cpp:284) and the same stack (the only difference between builds is one frame's source line: new_txn_data_impl.cpp:1881 vs :1882).
(For the treatment arm, same commands with the nightly image referenced by digest. Standalone mode; --memory mirrors what our production container runs with; --restart=no, boots restarted manually to confirm determinism.)
Expected behavior
Two things we'd love your thoughts on:
Is there, or could there be, a supported way to selectively discard a WAL entry whose replay references a missing data file — without discarding the rest of the post-checkpoint WAL? The engine already detects the exact situation and logs it (GetObjCache Failed to find object ... Key not found) right before throwing UnrecoverableError. Skipping that one transaction — with a loud warning and some durable marker, e.g. in the catalog — would let such a volume boot with a bounded, explicit loss instead of requiring manual WAL surgery. Fix corrupt WAL handling: out-of-bounds reads and over-aggressive purge #3429's purge is intentionally conservative at file granularity (and its tests argue well for that); this would be one level more surgical: entry-level, at replay execution.
Is the failure mode itself intended?UnrecoverableError during replay propagates through the terminate handler and the process dies with SIGSEGV (exit 139) rather than exiting cleanly with the message. A clean non-zero exit with the same message would already make automated boot-loop detection and restart policies much easier to handle.
For our part: the operational workaround we use today (also referenced in #3435) is moving — never deleting — post-checkpoint wal.log* files into a quarantine directory. That boots the instance but forfeits the whole tail. We are not set up to contribute a fix ourselves — this report is mainly to (a) document, with a deterministic reproduction, that #3429 does not cover this path, and (b) ask whether a supported "discard unreplayable entry" path exists or is planned.
Happy to share more logs/details from the test environment if useful.
Is there an existing issue for the same bug?
Related: #3435 (same reporter, same instance — WAL files are never recycled and manual cleanup by timestamp/size crashes InitPhase2). This issue reports a related but different failure mode, and specifically tests whether #3429 covers it. As far as we can measure, it does not.
Version or Commit ID
Two builds, tested side by side on identical fresh copies of the same data volume:
v0.7.3-0-d755c5a(imageinfiniflow/infinity:v0.7.3-x64-v3, build 2026-08-06) — the build our production instance runs.main@-0-eca7266(imageinfiniflow/infinity:nightly-x64-v3, digestsha256:9203ca30ac080e62f1386bb18fa9912d72906abced1ba59c7d4ec4963473bcb7, build 2026-09-16 13:14), which includes807af1b05(Fix corrupt WAL handling: out-of-bounds reads and over-aggressive purge #3429, merged 2026-09-04).Other environment information
Infinity deployment: Docker, standalone mode, via RAGFlow v0.27.1 Data layout: default /var/infinity layout on a named Docker volume (wal/, data/, persistence/, catalog/) Same instance as #3435: at the time of this test, wal/ held 58,742 files (1.6 GB), 51,147 of them exactly 82 bytes Config: stock infinity_conf.toml, default paths, log_level=infoActual behavior and How to reproduce it
Context — how the volume got into this state
The engine crashed during a drop/cleanup operation. The WAL entries written after the last checkpoint reference data files that the crashed operation had already deleted. Since then, every boot of this volume crashes; the only recovery we have found is manually quarantining
wal.log*files (moving, not deleting), which forfeits the entire post-checkpoint tail.What happens on boot (6/6 runs, deterministic)
Find and set checkpoint max commit ts: 499869.WalCmdAppendV2), the engine fails to load the data file the entry references:Why this looks like #3429 territory but isn't covered by it
#3429 fixes the WAL reading layer (out-of-bounds reads on corrupt entries, over-aggressive purge) — and in our repro the reading layer works: all 15 post-checkpoint entries are found and parsed cleanly on both builds. The crash is in the execution of replay —
CommitReplay→CommitBottomAppend→ append into a column whose backing file no longer exists. We verified this empirically with a controlled A/B:cp -ainto disposable volumes; source mounted read-only; one clone per arm, so the control run could not contaminate the treatment run).v0.7.3-0-d755c5a: 3/3 boots → exit 139.main@-0-eca7266(with Fix corrupt WAL handling: out-of-bounds reads and over-aggressive purge #3429): 3/3 boots → exit 139, identical critical line (file_worker_impl.cpp:284) and the same stack (the only difference between builds is one frame's source line:new_txn_data_impl.cpp:1881vs:1882).So this is not a one-off crash report: the same poisoned WAL that crash-loops
v0.7.3also crash-loops the current tip ofmain.How we ran each arm (for reference)
(For the treatment arm, same commands with the nightly image referenced by digest. Standalone mode;
--memorymirrors what our production container runs with;--restart=no, boots restarted manually to confirm determinism.)Expected behavior
Two things we'd love your thoughts on:
GetObjCache Failed to find object ... Key not found) right before throwingUnrecoverableError. Skipping that one transaction — with a loud warning and some durable marker, e.g. in the catalog — would let such a volume boot with a bounded, explicit loss instead of requiring manual WAL surgery. Fix corrupt WAL handling: out-of-bounds reads and over-aggressive purge #3429's purge is intentionally conservative at file granularity (and its tests argue well for that); this would be one level more surgical: entry-level, at replay execution.UnrecoverableErrorduring replay propagates through the terminate handler and the process dies with SIGSEGV (exit 139) rather than exiting cleanly with the message. A clean non-zero exit with the same message would already make automated boot-loop detection and restart policies much easier to handle.For our part: the operational workaround we use today (also referenced in #3435) is moving — never deleting — post-checkpoint
wal.log*files into a quarantine directory. That boots the instance but forfeits the whole tail. We are not set up to contribute a fix ourselves — this report is mainly to (a) document, with a deterministic reproduction, that #3429 does not cover this path, and (b) ask whether a supported "discard unreplayable entry" path exists or is planned.Happy to share more logs/details from the test environment if useful.
Thanks!
Gisselle