fix(store): throw on compaction-busy snapshot save; validate data/ on load (#3162) - #3164
fix(store): throw on compaction-busy snapshot save; validate data/ on load (#3162)#3164vaijosh wants to merge 9 commits into
Conversation
… load (apache#3162) - Throw HgStoreException in onSnapshotSave when RocksDB compaction is in progress so JRaft retries rather than committing an empty snapshot dir. - In onSnapshotLoad, fall through to the real load path when should_not_load is present but data/ is missing (JVM-killed mid-checkpoint), so JRaft can signal the error and request a fresh snapshot from the leader. - Add unit tests covering both fix paths in HgSnapshotHandlerTest. - Add docker/test/test-snapshot-corruption.sh, a deterministic Docker reproducer that confirms the bug and validates the fix (--fixed mode). Fixes apache#3162 Co-Authored-By: Claude <noreply@anthropic.com>
imbajin
left a comment
There was a problem hiding this comment.
Blocking: yes. Summary: Snapshot loading still silently accepts a non-directory data path, and the added reproducer cannot resolve its compose/root paths on a clean checkout; fixed mode also references a missing Dockerfile. Evidence: exact head 7ee5d42; all 17 exact-head check runs completed successfully.
… load (apache#3162) - Addressed Review comments
|
Thanks @imbajin for review. I have addressed the review comments. |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #3164 +/- ##
============================================
+ Coverage 37.78% 40.45% +2.66%
- Complexity 6556 7068 +512
============================================
Files 800 800
Lines 68929 69018 +89
Branches 9157 9178 +21
============================================
+ Hits 26046 27921 +1875
+ Misses 39824 37868 -1956
- Partials 3059 3229 +170 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
imbajin
left a comment
There was a problem hiding this comment.
Blocking: no. Summary: The snapshot handling change is covered by green exact-head checks, but the new reproducer still cannot validate the compaction-busy save fix. Evidence: exact-head CI and Codecov checks completed successfully; see the inline finding.
… load (apache#3162) - Added comment in test-snapshot-corruption.sh to make clear that its just load-path reproducer for the HStore snapshot corruption bug
bitflicker64
left a comment
There was a problem hiding this comment.
Blocking: no. Summary: The load-side validation is correct (isDirectory() rather than exists(), and deliberately not requiring a non-empty data/, which keeps empty partitions working) and the fix lands in the handler that PartitionEngine actually wires up; the save-side change is broader than the defect needs, and the new tests and reproducer have a few rough edges. Evidence: read of SnapshotHandler.java, HgSnapshotHandlerTest.java and docker/test/test-snapshot-corruption.sh at 8e121d4; PartitionEngine.java:176-177, PartitionStateMachine.java:192-206 and BusinessHandlerImpl.dbCompaction read for the surrounding lifecycle; gh -R apache/hugegraph pr checks 3164 (all 17 pass).
… load (apache#3162) - Addressed review comments. Added few Unit test cases, -Removed test-snapshot-corruption.sh because scenario is already covered by UTs.
bitflicker64
left a comment
There was a problem hiding this comment.
Blocking: no. Summary: The save-side throw is the right shape and is handled by PartitionStateMachine as a raft EIO status, but none of the new tests runs in any build, so the change merges with 0% patch coverage. The load-side data/ guard sits inside the should_not_load branch, so it covers only the flag-present variant of the signature #3162 records. Evidence: exact-head diff against merge-base 98477f0f (4 files, +216/-3); surefire include lists at hugegraph-store/hg-store-test/pom.xml:225-302; CoreSuiteTest.java:22-44 with the suite annotations commented out; .github/workflows/pd-store-ci.yml:281-296 running common/client/rocksdb/raftcore only; codecov on this head reporting 0% patch coverage, 8 lines missing, all in SnapshotHandler.java; RocksDBSession.java:740-745 already throwing for a missing snapshot path; and git ls-tree -r --name-only 0e1c319 showing docker/test/test-snapshot-corruption.sh absent from this head, though the description still names it as the verification path.
imbajin
left a comment
There was a problem hiding this comment.
Blocking: yes. Summary: The snapshot guard still has a check-then-act race and does not guarantee that snapshots cannot overlap compaction. Evidence: exact-head static review of SnapshotHandler.java:97-105, BusinessHandlerImpl.java:1413-1421, and PartitionStateMachine.java:192-204; all non-Codecov exact-head checks are completed.
…es (apache#3162) Addresses review comments: - onSnapshotSave/dbCompaction shared a non-atomic state check, letting saves race with compaction; coordinate both through a dedicated per-partition lock, checked non-blockingly on both sides - add EC_RKDB_SNAPSHOT_SAVE_BUSY_FAIL so the busy-save case has its own grep-able error code, and fix the exception text (compaction, not "skipped") and a stray non-ASCII em dash - onSnapshotLoad checks data/ before should_not_load, so a snapshot missing its flag is reported as corrupt instead of failing later with an unrelated RocksDB path error - drop the duplicate jraft/protobuf imports in HgSnapshotHandlerTest - register SnapshotHandlerTest in RaftSuiteTest and HgSnapshotHandlerTest in CoreSuiteTest, and run store-core-test in CI, so both actually execute instead of being skipped by every bound surefire profile
The pd-store-ci.yml store job gained a store-core-test profile and hg-store-core module in a prior commit, but test-check-jacoco-report.sh's hardcoded aggregation contract still asserted the old 4-profile set, breaking CI with an AssertionError on the set-equality checks.
bitflicker64
left a comment
There was a problem hiding this comment.
Blocking: yes. Summary: On compaction-busy the new throw reaches jraft as RaftError.EIO, the one code SnapshotExecutorImpl escalates to reportError, which ends in PartitionEngine.restartRaftNode(); and the Run core test step this PR adds is red on this head. Two blocking comments (SnapshotHandler.java, CoreSuiteTest.java) and three nits (SnapshotHandler.java load check, RaftRocksdbOptions.java, HgStoreException.java). Evidence: failing check store at 98fdaac, https://github.com/apache/hugegraph/actions/runs/33957642990/job/101289530213 ; jraft 1.3.13 sources for the snapshot error path; line references in each comment.
…he#3164) - report EBUSY instead of EIO when a snapshot save is skipped due to an in-progress compaction, so jRaft retries later instead of escalating to a full raft node restart (only EIO triggers that in SnapshotExecutorImpl#onSnapshotSaveDone) - check should_not_load before validating the data/ directory in onSnapshotLoad, so a locally-saved snapshot (which has no data/ by design) is skipped instead of reported as corrupt - keep the raftRocksdbConfigRegistered guard flag unset until registration actually completes, so a failure partway through can be retried instead of being silently swallowed forever - restore EC_RKDB_TRUNCATE_FAIL, EC_RKDB_TRANSFER_SNAPSHOT_FAIL, and EC_METRIC_FAIL, which were unintentionally dropped and would have broken binary compatibility for downstream consumers - stop CoreSuiteTest and BatchGraphIsolationTest from sharing a surefire fork: HgStoreEngine's `closing` flag is set by the former's teardown and never reset, so the latter failed with "store is closing" whenever both ran in the same JVM Updates HgSnapshotHandlerTest's should_not_load/data-missing case to expect a skip rather than a throw, matching the corrected check order.
bitflicker64
left a comment
There was a problem hiding this comment.
Blocking: no. Summary: The range lock closes the check-then-act race and the EBUSY mapping keeps a busy save from restarting the raft node; one regression remains: a full compaction that collides with a snapshot save is now silently dropped with no retry. Evidence: exact head 468e234 static review of BusinessHandlerImpl.java:1413-1481, SnapshotHandler.java:92-132, PartitionStateMachine.java:192-212 and every dbCompaction caller (PartitionEngine.java:1017,1263; HgStoreEngine.java:505; PartitionAPI.java:207; TTLCleaner.java:207,264); gh -R apache/hugegraph pr checks 3164 all 24 checks pass, store job runs CoreSuiteTest (7 tests) and RaftSuiteTest (6 tests).
… load (apache#3162)- apache#3164 - Implemented logic to wait for compactionRangeLock instead of failing dbCompaction fast. - Added UT to cover this scenario.
The race reproduces deterministicallyOn a 3-VM lab (1 PD + 3 Store + 1 Server, replication 1, 25 M edges, partitions of 60–275 MB) the window is not narrow: a full compaction of a partition holding ~250 MB of unflushed data takes 7 s, and a snapshot requested 300 ms after it lands in The only case where the window really disappears is a freshly compacted partition: What master does, step by step
Two things this says about the PR
Numbers for the 10 s discussion
If it helps, I can repeat the same runs on the next PR head or at replication 3 (where the interesting part is what a follower installing a snapshot in that window receives). |
Thanks, @SebastianGruza. Yes, this will really help! |
bitflicker64
left a comment
There was a problem hiding this comment.
Blocking: no. Summary: The previous rounds' findings are resolved at this head. Snapshot save and compactRange() now share a per-partition ReentrantLock, a busy save reports RaftError.EBUSY instead of restarting the raft node, a compaction that meets a running save waits up to 10s before skipping with a WARN, and the data/ check sits after the should_not_load return. Evidence: exact-head diff and the incremental diff 468e2343..1c6bc23a (BusinessHandlerImpl.java:1440-1463, SnapshotHandler.java, PartitionStateMachine.java); all 24 checks on 1c6bc23a pass, and the store job log shows CoreSuiteTest (8 tests, including the skip path: Partition 4 skip dbCompaction ... after 200ms wait), BatchGraphIsolationTest (6) and RaftSuiteTest (6) passing. Optional nit: the comment at BusinessHandlerImpl.java:1446-1449 still says the next compaction pass will succeed, but the event-driven triggers are one-shot.
Purpose of the PR
__raft_snapshot_metabut nodata/#3162This PR addresses a race condition that occurs during snapshot saves when compaction is busy, which could previously lead to corrupted snapshots or stuck partitions.
Main Changes
data/on load: Added validation during the snapshot load process to verify the presence of thedata/directory, preventing the system from loading incomplete snapshots.test-snapshot-corruption.shto deterministically reproduce the bug and validate the fix across different storage states.Verifying these changes
test-snapshot-corruption.shscript to verify the corrupted snapshot detection and prevention.Does this PR potentially affect the following parts?
Documentation Status
Doc - TODODoc - DoneDoc - No Need