tici: support versioned lookup via per-key read ts (range_versions)#19302
tici: support versioned lookup via per-key read ts (range_versions)#19302ti-chi-bot[bot] merged 26 commits intotikv:feature/ftsfrom
Conversation
| check_has_newer_ts_data: bool, | ||
| /// If true, skip all txn-related checks (lock checking, RcCheckTs newer-ts | ||
| /// check). Used by special read paths (e.g. TiCI versioned lookup). | ||
| bypass_txn_check: bool, |
There was a problem hiding this comment.
As I mentioned in previous PR #19019 (comment), the existing semantics of the standard DAG entry are designed to serve transactional coprocessor reads, which require read-write concurrency control and conflict handling.
The bypass_txn_check flag reduces the maintainability of the entire path, as it introduces a bypass transaction check flag within the transaction semantic logic. It is recommended to align it with a similar flag used by the coprocessor endpoint, such as is_versioned_lookup.
src/coprocessor/endpoint.rs
Outdated
| let data = req.take_data(); | ||
| let ranges: Vec<_> = req.take_ranges().into(); | ||
| let mut start_ts = req.get_start_ts(); | ||
| let range_versions = req.take_range_versions(); |
There was a problem hiding this comment.
Need comment to explain this is specfially for tici, and the endpoint kv entry of the kv-service.
Another question is, how to ensure it would not be used besides tici, considering the robustness.
Perhaps an request souce in RPC context is really needed. /cc @MyonKeminta @ekexium
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ 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 |
src/server/service/kv.rs
Outdated
| if let Some(err @ Error::ClusterIDMisMatch { .. }) = item.server_err { | ||
| let e = RpcStatus::with_message(RpcStatusCode::INVALID_ARGUMENT, err.to_string()); | ||
| GrpcResult::<(BatchCommandsResponse, WriteFlags)>::Err(GrpcError::RpcFailure(e)) | ||
| if let Some(err) = item.server_err.take() { |
There was a problem hiding this comment.
Is this unrelated change?
src/server/service/kv.rs
Outdated
| if let Err(e) = sink.send_all(&mut response_retriever).await { | ||
| let e = RpcStatus::with_message(RpcStatusCode::INVALID_ARGUMENT, e.to_string()); | ||
| sink.fail(e).await?; | ||
| match e { |
| /// Defaults to `false`. | ||
| #[inline] | ||
| #[must_use] | ||
| pub(crate) fn is_versioned_lookup(mut self, enabled: bool) -> Self { |
There was a problem hiding this comment.
Should be set_versioned_lookup.
| /// | ||
| /// WARNING: This method intentionally violates transactional semantics. It | ||
| /// may read through locks and ignore `RcCheckTs` write conflicts. Do not | ||
| /// use it for normal reads. |
There was a problem hiding this comment.
Better to describe the current usage scneario, like TiCI.
| primary_prefix_column_ids, | ||
| ) | ||
| None, | ||
| )?) |
There was a problem hiding this comment.
Unrelated refactor? Please re-check to remove unrelated refactor if they are generated by AI agent.
There was a problem hiding this comment.
All unrelated refactoring have been removed. PTAL again
48d8366 to
6fbe56d
Compare
| fn get_entry_at_ts( | ||
| &mut self, | ||
| is_key_only: bool, | ||
| load_commit_ts: bool, |
There was a problem hiding this comment.
In what scenarios do we need to get load_commit_ts?
There was a problem hiding this comment.
load_commit_ts belongs to other feature and is not related to versionedlookup. Here we are just preserving the existing behavior.
| } | ||
| }; | ||
|
|
||
| check_memory_locks_for_ranges(&self.concurrency_manager, &self.req_ctx, key_range)?; |
There was a problem hiding this comment.
Do we need to bypass this based on is_versioned_lookup here?
There was a problem hiding this comment.
It’s not needed. check_memory_locks_for_ranges already performs transaction-related checks, which are not required for VersionedLookup
|
lgtm |
There was a problem hiding this comment.
Pull request overview
This PR introduces a “versioned lookup” coprocessor read path that allows supplying per-key (per-point-range) read timestamps via versioned_ranges, exposed through a dedicated VersionedKv RPC and plumbed through DAG/TableScan down to point-get MVCC reads with txn checks bypassed.
Changes:
- Add
VersionedKv.versioned_coprocessorRPC and rejectversioned_rangeson the existing coprocessor RPCs. - Plumb per-range read timestamps (
range_versions) through Endpoint → DAG runner → TableScan/ScanExecutor → RangesScanner → Storage, and add storage APIs to fetch a key at an explicit ts while bypassing txn checks. - Add/extend unit and integration tests covering validation/guardrails and per-range ts behavior.
Reviewed changes
Copilot reviewed 22 out of 23 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/integrations/coprocessor/test_select.rs | Adds integration test verifying reading an older version via versioned_ranges. |
| tests/integrations/coprocessor/test_checksum.rs | Wires new range_versions option for checksum path (set to None). |
| src/storage/txn/store.rs | Adds Store::get_entry_at_ts and implements it for SnapshotStore (plus internal helper). |
| src/storage/mvcc/reader/point_getter.rs | Adds is_versioned_lookup flag to bypass lock + RcCheckTs newer-ts checks; adds tests. |
| src/server/service/kv.rs | Introduces VersionedKv RPC handler; rejects versioned_ranges on existing coprocessor RPCs. |
| src/server/server.rs | Registers the new VersionedKv gRPC service. |
| src/coprocessor/statistics/analyze_context.rs | Passes range_versions: None to scanner options. |
| src/coprocessor/statistics/analyze.rs | Extends table scan builder calls with range_versions: None. |
| src/coprocessor/endpoint.rs | Parses versioned_ranges, validates invariants, disables cache, and skips memory-lock checking for versioned lookup. |
| src/coprocessor/dag/storage_impl.rs | Implements Storage::get_entry_at_ts via Store::get_entry_at_ts. |
| src/coprocessor/dag/mod.rs | Plumbs range_versions into DAG handler construction. |
| src/coprocessor/checksum.rs | Passes range_versions: None to scanner options. |
| components/tidb_query_executors/src/util/scan_executor.rs | Validates/reverses range_versions and passes them into RangesScanner. |
| components/tidb_query_executors/src/util/mock_executor.rs | Adds stub get_entry_at_ts required by the updated Storage trait. |
| components/tidb_query_executors/src/table_scan_executor.rs | Accepts range_versions and enables point-range acceptance when present; adds executor tests. |
| components/tidb_query_executors/src/runner.rs | Plumbs range_versions into executor building/runner construction. |
| components/tidb_query_executors/src/index_scan_executor.rs | Sets range_versions: None for index scan executor options. |
| components/tidb_query_executors/src/index_lookup_executor.rs | Extends table task scan construction with range_versions: None. |
| components/tidb_query_common/src/storage/test_fixture.rs | Implements get_entry_at_ts for fixture storage (ts encoded into key) to test plumbing. |
| components/tidb_query_common/src/storage/scanner.rs | Adds range_versions support for point ranges in RangesScanner. |
| components/tidb_query_common/src/storage/mod.rs | Extends Storage trait with get_entry_at_ts and forwards it for Box<T>. |
| components/test_backup/src/lib.rs | Updates scanner options with range_versions: None. |
| Cargo.lock | Updates kvproto git revision and resulting lockfile resolution changes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| self.ranges_iter.notify_drained(); | ||
| self.scanned_rows_per_range.push(0); | ||
| self.storage | ||
| .get_entry(self.is_key_only, self.load_commit_ts, r)? | ||
| if let Some(versions) = self.range_versions.as_ref() { | ||
| let idx = self.scanned_rows_per_range.len() - 1; | ||
| self.storage.get_entry_at_ts( | ||
| self.is_key_only, | ||
| self.load_commit_ts, | ||
| r, | ||
| versions[idx], | ||
| )? |
There was a problem hiding this comment.
range_versions indexing is derived from scanned_rows_per_range.len() - 1, but collect_scanned_rows_per_range() drains scanned_rows_per_range between response slices (streaming/paging). After the first slice, this length no longer corresponds to the original range index, so subsequent point ranges can be read using the wrong read_ts. Track an independent monotonically-increasing range index (e.g. current_range_idx incremented on each NewRange) and use that to index range_versions, so stats collection doesn't affect per-range ts selection.
| None, | ||
| ); | ||
|
|
||
| let mut req = coppb::Request::default(); |
There was a problem hiding this comment.
The test case should cover both "point range" in which point getter is used and "multi range" in which range scanner is used situations and verify the max_ts is not changed.
There was a problem hiding this comment.
multi range can only pass multiple point ranges for testing. Requests with non-point ranges will be rejected directly. I’ve added the test—PTAL again.
| } | ||
|
|
||
| impl<E: Engine, L: LockManager, F: KvFormat> VersionedKv for Service<E, L, F> { | ||
| fn versioned_coprocessor( |
There was a problem hiding this comment.
Need to add E2E test sending real RPC requests(like test_kv_service.rs) thourgh tikv client, and verify:
- Coprocessor requests with version ranges to
coprocessorapi would be rejected. - Versioned requests to the versioned api, the results are expected and the
max_tsof the TiKV node is not affected.
|
Besides, please reference the related issues in the description. |
Signed-off-by: wshwsh12 <793703860@qq.com>
Signed-off-by: wshwsh12 <793703860@qq.com>
Signed-off-by: wshwsh12 <793703860@qq.com>
Signed-off-by: wshwsh12 <793703860@qq.com>
Signed-off-by: wshwsh12 <793703860@qq.com>
Signed-off-by: wshwsh12 <793703860@qq.com>
Signed-off-by: wshwsh12 <793703860@qq.com>
Signed-off-by: wshwsh12 <793703860@qq.com>
Signed-off-by: wshwsh12 <793703860@qq.com>
Signed-off-by: wshwsh12 <793703860@qq.com>
Signed-off-by: wshwsh12 <793703860@qq.com>
Signed-off-by: wshwsh12 <793703860@qq.com>
Signed-off-by: wshwsh12 <793703860@qq.com>
Signed-off-by: wshwsh12 <793703860@qq.com>
Signed-off-by: wshwsh12 <793703860@qq.com>
Signed-off-by: wshwsh12 <793703860@qq.com>
Signed-off-by: wshwsh12 <793703860@qq.com>
Signed-off-by: wshwsh12 <793703860@qq.com>
Signed-off-by: wshwsh12 <793703860@qq.com>
Signed-off-by: wshwsh12 <793703860@qq.com>
Signed-off-by: wshwsh12 <793703860@qq.com>
Signed-off-by: wshwsh12 <793703860@qq.com>
PR 19302 Codex Review
Findings (ordered by severity)1. Improvement (non-blocking): Add an optional server-side defensive guard
Impact under the stated assumption
Suggested follow-up (optional)
Conclusion
|
Signed-off-by: wshwsh12 <793703860@qq.com>
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: cfzjywxk, gengliqi 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 |
What is changed and how it works?
Issue Number: Close #19357
What's Changed:
Related changes
pingcap/docs/pingcap/docs-cn:Check List
Tests
Side effects
Release note