Skip to content

fix(engine): transactional merge publication and rollback - #140

Merged
xerj-org merged 3 commits into
mainfrom
redo/pr137-merge-transactional
Aug 2, 2026
Merged

fix(engine): transactional merge publication and rollback#140
xerj-org merged 3 commits into
mainfrom
redo/pr137-merge-transactional

Conversation

@xerj-org

@xerj-org xerj-org commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Supersedes #137 (contains its three commits, rebased onto main after #135 landed; authorship preserved). #137 is a probelabs fork with maintainer-edit off, so it could not be rebased in place.

Makes the production merge path transactional across provisional version-map ownership and durable segment-snapshot publication. Before this, a merge worker repointed live document identities to its output segment before apply_merge made that output authoritative — an apply error, cancellation, or panic could leave the global version map aimed at an unpublished segment, so reads reject the still-authoritative input rows as stale and cannot resolve the replacement: missing documents.

The repoint is now wrapped in an RAII VersionRepointTransaction, committed only inside apply_merge_inner immediately after the durable manifest save, and rolled back on Drop on every earlier exit (apply error, cancel, panic).

Verification (adversarial, xhigh)

Every begin→commit ordering was traced for panic/cancel. Rollback restores each version-map entry to its captured prior under a guarded (seq_no, failed_segment) check, so a concurrent newer PUT/DELETE is never clobbered. The merged output is written by SegmentWriter::finish (seg+sidx only) and never receives .ids/.complete, so it can't be orphan-resurrected → no post-merge duplication. Fault-injection tests drive the exact orderings (failpoints before/mid/after repoint, double snapshot-save failure, partial multi-input disarm). Rebased-on-main gates: xerj-storage + xerj-engine 378 + 140 tests, 0 failed; clippy -D warnings clean; fmt clean.

Disclosed, non-blocking

ghost_events is incremented on repoint but not decremented on rollback (monotonic by design) — a rolled-back merge leaves it slightly inflated, which only keeps delete-aware slow paths on (conservative). A pre-existing success-path transient (worker repoints before apply publishes, so a GET-by-id can momentarily miss a surviving doc during a normal merge) is not introduced or fixed here.

buger added 3 commits August 2, 2026 10:46
Merge workers repoint live identities to their output before publishing the replacement snapshot. If apply_merge failed, the async task was cancelled, or the worker panicked, those repoints survived and referred readers to an output that was never authoritative.

Add a conditionally-applied VersionRepointTransaction that preallocates its rollback log, records exact displaced entries, and restores an entry only when it still matches the transaction's installed value. This preserves concurrent newer writes and deletes. Commit the transaction only after apply_merge succeeds, and count publication failures in the existing merge failure result.

Also make apply_merge restore its exact in-memory input snapshot when manifest persistence fails. Irreversible version-map cleanup now runs only after durable snapshot publication. A delayed-save test exercises the post-RCU failure boundary and verifies the restored inputs survive restart.

Tests: cargo test -p xerj-storage version_map::tests::merge_repoint --lib (3 passed); cargo test -p xerj-storage apply_merge_manifest_failure_restores_exact_input_snapshot --lib (1 passed); cargo check -p xerj-engine; cargo clippy -p xerj-storage -p xerj-engine --all-targets -- -D warnings; cargo fmt --all --check.
A merge publication save can fail after changing the in-memory snapshot. If the compensating input-manifest save also fails, restart authority is unknown and callers must not receive the ordinary NotPublished classification.

Extend the deterministic snapshot-save fault injector to emit consecutive failures and prove apply_merge_with_repoints returns Indeterminate for the publication-plus-rollback failure sequence. The existing single-failure test continues to prove a successful rollback is classified as NotPublished and survives restart.

Test: cargo test -p xerj-storage apply_merge_reports_indeterminate_when_publication_and_rollback_saves_fail --lib.
Promote the merge publication prerequisite with deterministic engine-level coverage at the boundary where workers have repointed identities but have not yet published their output.

The suite proves an apply failure restores exact sources, search totals, restart state, and permits a successful retry; real concurrent PUT and DELETE operations survive rollback with their source and tombstone intact across restart; task cancellation drops the transaction and restores input ownership; and a panic immediately after durable publication leaves both snapshot and version map on the output across GET, search, and restart.

The multi-batch case exposed a separate control-flow bug: an apply failure continued before replenishing the in-flight queue, suppressing remaining disjoint batches. Top up the queue before continuing so successful batches still publish while the pass reports the failed batch. The regression proves all sources survive, one batch commits, the failure propagates, and restart query totals remain exact.

Tests: cargo test -p xerj-engine merge_publication_transaction_tests --lib (5 passed); cargo test -p xerj-storage version_map::tests::merge_repoint --lib (3 passed); cargo test -p xerj-storage apply_merge_manifest_failure_restores_exact_input_snapshot --lib; cargo test -p xerj-storage apply_merge_reports_indeterminate_when_publication_and_rollback_saves_fail --lib; cargo check -p xerj-engine; cargo clippy -p xerj-storage -p xerj-engine --all-targets -- -D warnings; cargo fmt --all --check.
@xerj-org
xerj-org merged commit ba7bd23 into main Aug 2, 2026
9 checks passed
@xerj-org
xerj-org deleted the redo/pr137-merge-transactional branch August 2, 2026 09:47
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.

2 participants