Skip to content

fix(checker): stop flagging concurrent committed overwrites as corruption#28

Merged
overtrue merged 1 commit into
mainfrom
fix/checker-concurrent-overwrite-false-positive
Jul 15, 2026
Merged

fix(checker): stop flagging concurrent committed overwrites as corruption#28
overtrue merged 1 commit into
mainfrom
fix/checker-concurrent-overwrite-false-positive

Conversation

@overtrue

Copy link
Copy Markdown
Contributor

Summary

successful_read_anomalies kept one latest committed value per key and compared every successful GET against it — a fully serialized model. Under a hotspot workload, two committed overwrites of one key can have overlapping completion windows, and a GET issued before the later write finished may legally return the earlier value (S3 gives concurrent overwrites no client-observable order). The checker reported that as successful_corrupted_reads / data_corruption.

This is review finding C-exec-P1 from rustfs/backlog#1100 ('hotspot workload treats completion order as serialization truth → false DataCorruption'), and it reproduced verbatim on a real minikube cluster the first time a large-payload (EC shard path) hotspot profile ran:

op-000064 put  ok  t+11.7s..11.9s  sha=8ac41189…   <- the value the GET returned
op-000065 put  ok  t+11.8s..12.0s  sha=870a90f7…   <- checker's 'latest'
op-000070 get  ok  t+11.9s..12.0s  returned 8ac41189…  -> flagged data_corruption
op-000082 put / get / delete / 404 …                -> final state fully consistent

Same byte length, both values committed, GET started before the second PUT finished — a legal concurrent read, not corruption. Left unfixed, every hotspot profile (including #26's versioned-hot-mutations) is a false-red generator.

Fix

Track every committed write's value + completion time per key; exempt a mismatching GET only when it is a legal concurrent read:

  • the matching write's completion window overlaps the GET itself, or
  • the latest write was still in flight when the GET started and the GET returned the immediately-previous committed value.

Detection power is preserved deliberately:

  • once all writes settle before the GET starts, an old value is still corruption (real stale-read detection intact — pinned by a test);
  • committed deletes clear the key's history, so post-delete resurrection detection is unchanged;
  • values matching no committed write are still corruption.

Validation

  • cargo test: 239+15 pass — new tests replay the observed race (must pass), a settled-overwrite stale read (must still flag), and a GET racing the latest in-flight write returning the previous value (legal).
  • cargo clippy --all-targets / cargo fmt --check clean.
  • E2E on minikube: the ec-shard hotspot suite that false-reded on this exact race re-run with the fix — see follow-up comment with the rerun result.

Independent of #26/#27 (touches only checker.rs).

Copilot AI review requested due to automatic review settings July 11, 2026 04:43

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@overtrue

Copy link
Copy Markdown
Contributor Author

E2E rerun evidence (minikube, same ec-shard hotspot suite that false-reded):

  • Suite: rustfs-ec-shard-path — stress-cpu with a payload distribution deliberately covering both commit paths: 4KiB×18 + 64KiB×12 (inline) and 2MiB×18 + 8MiB×12 (sharded EC path), hotspot 15%/60%, ~139MB total.
  • Before the fix: successful_corrupted_reads fired on the exact concurrent-overwrite race in the PR description (object-000000, two PUTs with overlapping windows, GET racing the second one) → suite red with data_corruption.
  • After the fix (same profile, fresh run): checker passed=true, 39/39 committed objects verified, successful_corrupted_reads=[], hash_mismatches=[], suite failures: 0.

The stale-read guard stays intact — the settled-overwrite unit test pins that an old value returned long after both writes complete is still flagged.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 53b96cc825

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/fault/checker.rs
Comment on lines +1913 to +1917
let history = committed_history
.get(key)
.map(Vec::as_slice)
.unwrap_or_default();
if !concurrent_committed_read(history, record.started_at_ms, &actual) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Account for overlapping writes that finish after the GET

When a GET overlaps a committed overwrite but the GET record is appended before that PUT's Ok record, the matching value is not in committed_history yet because this check only looks at writes already processed in the single pass. For example, if PUT B starts while PUT A is live, the GET overlaps B, returns B's body, and B completes successfully just after the GET, this still reports expected A, got B even though the PUT/GET overlap is a legal concurrent read under the same ordering rule this patch is trying to model. The exemption needs to consider committed writes later in the history whose window overlaps the GET, not just the prefix seen so far.

Useful? React with 👍 / 👎.

…tion

successful_read_anomalies kept one latest committed value per key and
compared every successful GET against it — a fully serialized model.
Under a hotspot workload two committed overwrites of one key can have
overlapping completion windows, and a GET issued before the later write
finished may legally return the earlier value; S3 gives concurrent
overwrites no client-observable order. The checker reported that as
successful_corrupted_reads — reproduced verbatim on a real minikube
cluster the first time a large-payload (EC shard path) hotspot profile
ran: two PUTs at t+11.7..11.9s and t+11.8..12.0s, a GET starting at
t+11.9s returning the first value, flagged as data_corruption while the
object's final state was fully consistent. This is backlog#1100 review
finding C-exec-P1 (hotspot completion-order serialization) caught live.

Track every committed write's value and completion time per key, and
exempt a mismatching GET only when it is a legal concurrent read:
- the matching write's window overlaps the GET itself, or
- the latest write was still in flight when the GET started and the GET
  returned the immediately-previous committed value.

Both legs keep real stale-read detection intact: once all writes settle
before the GET starts, an old value is still corruption. Committed
deletes clear the key's history, so post-delete resurrection detection
is unchanged.

cargo test: 239+15 pass (new: replay of the observed race must pass;
settled-overwrite stale read must still flag; GET racing the latest
in-flight write may read the previous value).
@overtrue
overtrue force-pushed the fix/checker-concurrent-overwrite-false-positive branch from 53b96cc to 5d693e1 Compare July 12, 2026 02:08
@overtrue

Copy link
Copy Markdown
Contributor Author

Rebased onto latest main. Complementary to the merged #32, not redundant: #32's stable_live_objects_at_read_starts exempts a GET returning the value stable-live at its start (across an overlapping delete); this PR's concurrent_committed_read additionally exempts a GET returning a value whose write window overlaps the GET, or the previous value while the latest write was in flight (the hotspot-overwrite race). Verified: this PR's race test still FAILs on main-with-#32. Both coexist; this PR's tests + #32's overlapping-get-delete tests all pass (247+15). #30 is stacked on this.

@overtrue
overtrue added this pull request to the merge queue Jul 15, 2026
Merged via the queue into main with commit 09bf2aa Jul 15, 2026
1 check passed
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