fix(cubestore): guard replay handle seq pointer / location length mismatch#11093
fix(cubestore): guard replay handle seq pointer / location length mismatch#11093waralexrom wants to merge 1 commit into
Conversation
…match A stale ReplayHandle whose seq_pointers_by_location length no longer matches the table's locations (e.g. after metastore restore where a table id is reused with a different partition count) poisoned both the stream import path and the replay handle merge pass, causing imports to fail forever and handles to accumulate unbounded. - Refuse to persist replay handles whose seq pointer vector length does not match the table's locations (validate_seq_pointers_by_location in both create_replay_handle_from_seq_pointers and replace_replay_handles). - Report the read-path length mismatch as corrupt_data so the scheduler deactivates the table instead of looping the error. - Isolate per-table merging in merge_replay_handles so one corrupt handle no longer aborts cleanup for every other table. - Add unit tests for the length guard and an integration test for the write-path rejection.
|
Claude finished @waralexrom's task in 1m 55s —— View job ReviewVerdict: LGTM — the three-layer defense (write-time guard, read-time error classification, per-table merge isolation) is well-targeted at the failure mode in the PR description. 0 high, 0 medium, 3 low. DetailsCorrectness
Low-priority notes
TestingUnit + integration coverage for the new guard look adequate (match / mismatch / |
Summary
A stale
ReplayHandlewhoseseq_pointers_by_locationlength no longer matches the table'slocationspoisoned both the streaming import path and the replay-handle merge pass — stream imports for the table failed permanently and replay handles accumulated unbounded across the cluster. This adds write-time and read-time guards plus per-table isolation in the merge so a single corrupt handle can't take everything down.Root cause
The seq-pointer vector length is frozen at handle-creation time from
table.locations().len(), and table locations are immutable. So a handle of length N against a table with M≠N locations can only arise when atable_idis reused for a table with a different partition count (e.g. metastore restore from a snapshot older than the state where the handles were written, bypassingdrop_table's handle cleanup). The mismatch then makes:seq_pointer_for_location(read path, via stream importinitial_seq_for) fail with an internal error every cycle, andunion_seq_pointer_by_locationinmerge_replay_handlesabort the whole cleanup pass, so handles for all tables stop being merged and pile up.Changes
validate_seq_pointers_by_location, applied in bothcreate_replay_handle_from_seq_pointersandreplace_replay_handles.corrupt_datainstead ofinternal, so the scheduler's existingCorruptDatahandling deactivates the table (and Cube rebuilds it) rather than looping the error forever.merge_replay_handles: a corrupt handle for one table is logged and skipped, no longer aborting cleanup for every other table.Testing
cargo test -p cubestore --lib replay_handle— 3 unit tests for the length guard (match → ok, mismatch → err,None→ ok) and 1 integration test asserting the write path rejects a mismatching vector and persists nothing, while a matching one writes exactly one handle. All green.