Fix sequence number race when allowing writes during external SST ingestion - #435
Conversation
Signed-off-by: gengliqi <gengliqiii@gmail.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughExternal SST ingestion now reserves sequence numbers before running ingestion jobs when foreground writes are allowed. Jobs receive the reserved sequence boundary. Tests cover concurrent writes, pending writers, sequence ordering, reads, and snapshot visibility. ChangesExternal SST sequence reservation
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant DBImpl
participant WriteQueues
participant VersionSet
participant ExternalSstFileIngestionJob
DBImpl->>WriteQueues: Block both write queues
DBImpl->>WriteQueues: Wait for pending writes
DBImpl->>VersionSet: Reserve sequence numbers
DBImpl->>ExternalSstFileIngestionJob: Run(last_seqno)
ExternalSstFileIngestionJob-->>DBImpl: Return consumed sequence count
DBImpl->>VersionSet: Apply manifest and conditionally update counters
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@db/db_impl/db_impl.cc`:
- Around line 5951-5964: The ingestion reservation currently publishes reserved
sequence numbers before the MANIFEST write completes. In the IngestExternalFiles
flow, keep SetLastPublishedSequence unchanged during reservation and advance it
only after LogAndApply succeeds, while preserving SetLastAllocatedSequence and
SetLastSequence updates and ensuring failure does not publish the reserved
range.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 42a6d5ce-d7f7-42c8-b402-ca1b2764d0e1
📒 Files selected for processing (4)
db/db_impl/db_impl.ccdb/external_sst_file_ingestion_job.ccdb/external_sst_file_ingestion_job.hdb/external_sst_file_test.cc
Signed-off-by: gengliqi <gengliqiii@gmail.com>
Signed-off-by: gengliqi <gengliqiii@gmail.com>
Signed-off-by: gengliqi <gengliqiii@gmail.com>
There was a problem hiding this comment.
Pull request overview
This PR addresses a race in external SST ingestion when allow_write=true, where ingestion and foreground writes could concurrently assign sequence numbers, potentially causing duplicates or regressions in global seqno state. It introduces a short write-stall to reserve a safe seqno range before running ingestion jobs, and updates ingestion job interfaces/tests accordingly.
Changes:
- Briefly blocks writers to reserve enough sequence numbers prior to ingestion when
allow_write=true. - Refactors
ExternalSstFileIngestionJob::Runto accept alast_seqnovalue from the caller instead of reading it internally. - Expands/adjusts tests to validate assigned seqnos and writer interaction during allow-write ingestion.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| db/external_sst_file_test.cc | Updates/extends tests for allow-write ingestion sequencing and writer interactions. |
| db/external_sst_file_ingestion_job.h | Changes ingestion job API to pass last_seqno into Run(). |
| db/external_sst_file_ingestion_job.cc | Uses passed-in last_seqno and exposes assigned seqno via sync point callback. |
| db/db_impl/db_impl.cc | Adds seqno reservation logic under a brief write stop and passes last_seqno into ingestion jobs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Signed-off-by: gengliqi <gengliqiii@gmail.com>
cb50569 to
3235185
Compare
|
/retest |
Signed-off-by: gengliqi <gengliqiii@gmail.com>
| // User must ensure no writes overlap with the ingested data. | ||
| // User must ensure that concurrent writes do not overlap the ingested key | ||
| // ranges. | ||
| // Reads using snapshots created before ingestion are allowed. A snapshot |
There was a problem hiding this comment.
The description is not clear. How about snapshot consistency is not promised, because xxxx.
| // that period will not be stable if VersionSet last seqno is updated | ||
| // before LogAndApply. | ||
| int consumed_seqno_count = | ||
| ingestion_jobs[0].ConsumedSequenceNumbersCount(); |
There was a problem hiding this comment.
add assert for ConsumedSequenceNumbersCount vs reserved_last_seqno
There was a problem hiding this comment.
Added this assert after ExternalSstFileIngestionJob::Run.
2d55373 to
23f84ae
Compare
Signed-off-by: gengliqi <gengliqiii@gmail.com>
23f84ae to
7528f73
Compare
Deep Review#435 — Fix sequence number race when allowing writes during external SST ingestion Problem SummaryPR #18096 added Defect 1 — duplicate sequence numbers (silent). Defect 2 — sequence number regression (leads to an upper-layer panic). After Defect 2 has a second path that an atomic RMW alone cannot fix. Multi-batch write publishes with The current mitigation on the TiKV side is to hard-code Solution WalkthroughThe idea is not to remove the write stall but to shrink it. The old stall covered the memtable flush and the MANIFEST write plus fsync in ① The reservation window — if (allow_write) {
// Briefly stop writes while reserving sequence numbers for ingestion.
write_thread_.EnterUnbatched(&w, &mutex_);
if (two_write_queues_) {
nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_);
}
WaitForPendingWrites();
}Both steps are required, and the second one is the load-bearing one.
The first step alone is not enough. Multi-batch write releases the write thread at The code reuses
Both exits release ② Taking the base — SequenceNumber last_seqno = versions_->LastSequence();This line sits outside the ③ Computing the reservation — // Each file consumes at most one sequence number. Jobs for different
// column families share the same sequence range, so reserve the maximum
// file count. Unused sequence numbers are harmless gaps.
SequenceNumber reserved_seqno_count = 0;
for (size_t i = 0; i != num_cfs; ++i) {
reserved_seqno_count =
std::max(reserved_seqno_count,
static_cast<SequenceNumber>(
ingestion_jobs[i].files_to_ingest().size()));
}
assert(reserved_seqno_count > 0);It takes the max, not the sum, and that is correct. It is not a conservative estimate — it is what the semantics require. All jobs share one The upper bound holds. In
④ Publishing the reservation — const SequenceNumber reserved_last_seqno = last_seqno + reserved_seqno_count;
versions_->SetLastAllocatedSequence(reserved_last_seqno);
versions_->SetLastPublishedSequence(reserved_last_seqno);
versions_->SetLastSequence(reserved_last_seqno);The order is correct and it has to be this order.
⑤ Passing the base into Run — Status ExternalSstFileIngestionJob::Run(SequenceNumber last_seqno) {The stale read inside the function is gone, and so is the comment above it. Removing that comment is right — "we are the only active writer" was the source of the defect. The ⑥ The old publication moves under The whole block, including the comment explaining why publication belongs after ⑦ New comment above LogAndApply — // With allow_write, a concurrent flush may persist a higher last sequence
// before this ingestion edit is applied. LogAndApplyHelper raises this edit's
// last sequence as needed to keep VersionEdit::last_sequence values
// non-decreasing in the MANIFEST.This comment is accurate. Findings (ordered by severity)F1 [Critical] The shipped code is correct. What is critical is that the correctness is unprotected: delete
The reason is structural, not carelessness. The test hangs the writer on The problem is that on the plain There is a ready-made sync point in the right place: Park a writer at
F2 [High] The new snapshot-instability window is documented nowhere The comment that was preserved says:
The For TiKV's add-peer path this is acceptable. The range is owned exclusively by the peer applying the snapshot and serves no reads during the window. But:
Recommendation: state the precondition in the F3 [High] Under
If that lead exceeds Scope and priority:
If this PR is intended for upstream rather than only for TiKV, use F4 [Medium] Removed: // REQUIRES: we have become the only writer by entering both write_thread_ and
// nonmem_write_thread_The header at The contract did not disappear. It forked:
The new parameter F5 [Medium] Nothing asserts that consumption stays within the reservation "Each file consumes at most one sequence number" is the foundation of the whole reservation calculation, and right now it exists only as a comment. assert(ingestion_jobs[i].ConsumedSequenceNumbersCount() <=
static_cast<int>(reserved_seqno_count));( If F6 [Medium] The multi-CF path has no Taking the max instead of the sum is the decision in this PR that most needs explaining, and it is correct and consistent with the old code. But both Worth adding: two column families with unequal file counts (say 1 and 3), asserting that the first file of each CF gets the same sequence number and that the reservation is 3. F7 [Low] No test for the failure path // The reservation cannot be rolled back if ingestion fails because a
// foreground write may have already consumed a later sequence number.The conclusion is right and the consequence is harmless. An unpersisted gap disappears on restart, since Partial failure across multiple column families is also safe, and I traced it rather than assuming: What is missing is a test. Injecting one F8 [Low] It depends on This is not a defect, it is a division of labor. The test's value is pinning down the new behavior ( F9 [Nit] The
Costs and Negative Impacts
Engineering Rules CheckThis PR lives in tikv/rocksdb (C++), so the Engineering Rules in
Questions and Assumptions
Suggested Tests / ValidationIn order of value:
|
Signed-off-by: gengliqi <gengliqiii@gmail.com>
| // ingestion because ingestion sequence numbers are published before the | ||
| // ingested files become visible. Such snapshots must not be used to read the | ||
| // ingested key ranges. | ||
| bool allow_write = false; |
There was a problem hiding this comment.
It would have some issues with two_write_queues
How about narrowing down the scope
if (allow_write && two_write_queues_) {
return Status::NotSupported(
"allow_write is incompatible with two_write_queues");
}
There was a problem hiding this comment.
Oh, it also has the issues for !allow_write. Forget about it.
[LGTM Timeline notifier]Timeline:
|
close #19954\n\n- Update `rust-rocksdb` to include [tikv/rocksdb#435](tikv/rocksdb#435), which fixes the sequence-number publication race during allow-write ingestion. - Remove the temporary switch introduced by #19906 and re-enable `allow_write` for external SST ingestion.\n\nSigned-off-by: gengliqi <gengliqiii@gmail.com>
|
/cherry-pick 6.29.tikv |
|
@gengliqi: new pull request created to branch DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the ti-community-infra/tichi repository. |
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: Connor1996, hhwyt, overvenus The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
…estion (#435) (#437) ref tikv/tikv#19891\n\nSigned-off-by: ti-chi-bot <ti-community-prow-bot@tidb.io>\nSigned-off-by: gengliqi <gengliqiii@gmail.com>\n\nCo-authored-by: Liqi Geng <gengliqiii@gmail.com>\nCo-authored-by: gengliqi <gengliqiii@gmail.com>
… (#19976) close #19954\n\n- Update `rust-rocksdb` to include [tikv/rocksdb#435](tikv/rocksdb#435), which fixes the sequence-number publication race during allow-write ingestion. - Remove the temporary switch introduced by #19906 and re-enable `allow_write` for external SST ingestion.\n\nSigned-off-by: ti-chi-bot <ti-community-prow-bot@tidb.io>\nSigned-off-by: gengliqi <gengliqiii@gmail.com>\n\nCo-authored-by: Liqi Geng <gengliqiii@gmail.com>\nCo-authored-by: gengliqi <gengliqiii@gmail.com>
… (#19982) close #19954\n\n- Update `rust-rocksdb` to include [tikv/rocksdb#435](tikv/rocksdb#435), which fixes the sequence-number publication race during allow-write ingestion. - Remove the temporary switch introduced by #19906 and re-enable `allow_write` for external SST ingestion.\n\nSigned-off-by: ti-chi-bot <ti-community-prow-bot@tidb.io>\nSigned-off-by: gengliqi <gengliqiii@gmail.com>\n\nCo-authored-by: Liqi Geng <gengliqiii@gmail.com>\nCo-authored-by: gengliqi <gengliqiii@gmail.com>
… (#19978) close #19954\n\n- Update `rust-rocksdb` to include [tikv/rocksdb#435](tikv/rocksdb#435), which fixes the sequence-number publication race during allow-write ingestion. - Remove the temporary switch introduced by #19906 and re-enable `allow_write` for external SST ingestion.\n\nSigned-off-by: ti-chi-bot <ti-community-prow-bot@tidb.io>\nSigned-off-by: gengliqi <gengliqiii@gmail.com>\n\nCo-authored-by: Liqi Geng <gengliqiii@gmail.com>\nCo-authored-by: gengliqi <gengliqiii@gmail.com>
… (#19985) close #19954\n\n- Update `rust-rocksdb` to include [tikv/rocksdb#435](tikv/rocksdb#435), which fixes the sequence-number publication race during allow-write ingestion. - Remove the temporary switch introduced by #19906 and re-enable `allow_write` for external SST ingestion.\n\nSigned-off-by: ti-chi-bot <ti-community-prow-bot@tidb.io>\nSigned-off-by: gengliqi <gengliqiii@gmail.com>\n\nCo-authored-by: Liqi Geng <gengliqiii@gmail.com>\nCo-authored-by: gengliqi <gengliqiii@gmail.com>
… (#19984) close #19954\n\n- Update `rust-rocksdb` to include [tikv/rocksdb#435](tikv/rocksdb#435), which fixes the sequence-number publication race during allow-write ingestion. - Remove the temporary switch introduced by #19906 and re-enable `allow_write` for external SST ingestion.\n\nSigned-off-by: gengliqi <gengliqiii@gmail.com>\n\nCo-authored-by: gengliqi <gengliqiii@gmail.com>
… (#19986) close #19954\n\n- Update `rust-rocksdb` to include [tikv/rocksdb#435](tikv/rocksdb#435), which fixes the sequence-number publication race during allow-write ingestion. - Remove the temporary switch introduced by #19906 and re-enable `allow_write` for external SST ingestion.\n\nSigned-off-by: ti-chi-bot <ti-community-prow-bot@tidb.io>\nSigned-off-by: gengliqi <gengliqiii@gmail.com>\n\nCo-authored-by: Liqi Geng <gengliqiii@gmail.com>\nCo-authored-by: gengliqi <gengliqiii@gmail.com>
… (#19987) close #19954\n\n- Update `rust-rocksdb` to include [tikv/rocksdb#435](tikv/rocksdb#435), which fixes the sequence-number publication race during allow-write ingestion. - Remove the temporary switch introduced by #19906 and re-enable `allow_write` for external SST ingestion.\n\nSigned-off-by: ti-chi-bot <ti-community-prow-bot@tidb.io>\nSigned-off-by: gengliqi <gengliqiii@gmail.com>\n\nCo-authored-by: Liqi Geng <gengliqiii@gmail.com>\nCo-authored-by: gengliqi <gengliqiii@gmail.com>
… (#19981) close #19954\n\n- Update `rust-rocksdb` to include [tikv/rocksdb#435](tikv/rocksdb#435), which fixes the sequence-number publication race during allow-write ingestion. - Remove the temporary switch introduced by #19906 and re-enable `allow_write` for external SST ingestion.\n\nSigned-off-by: Liqi Geng <gengliqiii@gmail.com>\nSigned-off-by: gengliqi <gengliqiii@gmail.com>\n\nCo-authored-by: Liqi Geng <gengliqiii@gmail.com>\nCo-authored-by: gengliqi <gengliqiii@gmail.com>
… (#19977) close #19954\n\n- Update `rust-rocksdb` to include [tikv/rocksdb#435](tikv/rocksdb#435), which fixes the sequence-number publication race during allow-write ingestion. - Remove the temporary switch introduced by #19906 and re-enable `allow_write` for external SST ingestion.\n\nSigned-off-by: ti-chi-bot <ti-community-prow-bot@tidb.io>\nSigned-off-by: Liqi Geng <gengliqiii@gmail.com>\nSigned-off-by: gengliqi <gengliqiii@gmail.com>\n\nCo-authored-by: Liqi Geng <gengliqiii@gmail.com>\nCo-authored-by: gengliqi <gengliqiii@gmail.com>
… (#19995) close #19954\n\n- Update `rust-rocksdb` to include [tikv/rocksdb#435](tikv/rocksdb#435), which fixes the sequence-number publication race during allow-write ingestion. - Remove the temporary switch introduced by #19906 and re-enable `allow_write` for external SST ingestion.\n\nSigned-off-by: ti-chi-bot <ti-community-prow-bot@tidb.io>\nSigned-off-by: jebter <jebter@126.com>\nSigned-off-by: gengliqi <gengliqiii@gmail.com>\n\nCo-authored-by: Liqi Geng <gengliqiii@gmail.com>\nCo-authored-by: jebter <jebter@126.com>\nCo-authored-by: gengliqi <gengliqiii@gmail.com>
… (#19996) close #19954\n\n- Update `rust-rocksdb` to include [tikv/rocksdb#435](tikv/rocksdb#435), which fixes the sequence-number publication race during allow-write ingestion. - Remove the temporary switch introduced by #19906 and re-enable `allow_write` for external SST ingestion.\n\nSigned-off-by: ti-chi-bot <ti-community-prow-bot@tidb.io>\nSigned-off-by: gengliqi <gengliqiii@gmail.com>\n\nCo-authored-by: Liqi Geng <gengliqiii@gmail.com>\nCo-authored-by: gengliqi <gengliqiii@gmail.com>
…19975) (tikv#19995) close tikv#19954\n\n- Update `rust-rocksdb` to include [tikv/rocksdb#435](tikv/rocksdb#435), which fixes the sequence-number publication race during allow-write ingestion. - Remove the temporary switch introduced by tikv#19906 and re-enable `allow_write` for external SST ingestion.\n\nSigned-off-by: ti-chi-bot <ti-community-prow-bot@tidb.io>\nSigned-off-by: jebter <jebter@126.com>\nSigned-off-by: gengliqi <gengliqiii@gmail.com>\n\nCo-authored-by: Liqi Geng <gengliqiii@gmail.com>\nCo-authored-by: jebter <jebter@126.com>\nCo-authored-by: gengliqi <gengliqiii@gmail.com>
…19975) (tikv#19995) close tikv#19954\n\n- Update `rust-rocksdb` to include [tikv/rocksdb#435](tikv/rocksdb#435), which fixes the sequence-number publication race during allow-write ingestion. - Remove the temporary switch introduced by tikv#19906 and re-enable `allow_write` for external SST ingestion.\n\nSigned-off-by: ti-chi-bot <ti-community-prow-bot@tidb.io>\nSigned-off-by: jebter <jebter@126.com>\nSigned-off-by: gengliqi <gengliqiii@gmail.com>\n\nCo-authored-by: Liqi Geng <gengliqiii@gmail.com>\nCo-authored-by: jebter <jebter@126.com>\nCo-authored-by: gengliqi <gengliqiii@gmail.com> Signed-off-by: gengliqi <gengliqiii@gmail.com>
… (#19997) close #19954\n\n- Update `rust-rocksdb` to include [tikv/rocksdb#435](tikv/rocksdb#435), which fixes the sequence-number publication race during allow-write ingestion. - Remove the temporary switch introduced by #19906 and re-enable `allow_write` for external SST ingestion.\n\nSigned-off-by: gengliqi <gengliqiii@gmail.com>
Ref tikv/tikv#19891 (comment)
When
allow_write=true, external SST ingestion may assign sequence numbersconcurrently with foreground writes. This can cause duplicate sequence numbers
or make the global sequence-number state move backwards.
This change briefly blocks writers to reserve enough sequence numbers before
running the ingestion jobs. Writers are resumed immediately after the
reservation, so SST processing and MANIFEST updates remain outside the
write-blocking period.
The existing behavior for
allow_write=falseis unchanged.This also fixes an
enable_multi_batch_writeedge case where writers that failto write WAL still publish their allocated sequence numbers but were not counted
as pending. They are now included in pending-write accounting, preventing
ingestion from reserving sequence numbers before their sequence publication
completes.
Summary by CodeRabbit
Bug Fixes
Documentation
Tests