Skip to content

Commit f29b660

Browse files
antiguruclaude
andcommitted
compute: serve fast-path peeks on the interactive runtime
The interactive runtime holds no local traces, so an index peek there resolves against the sharing registry instead. `PeekScan` and the error walk beneath it become generic over the traces they read, and an interactive peek opens its scan over the registry's `SharedOksHandle` and `SharedErrsHandle`. Both flavours of index peek then spend one budget, report one set of metrics, and reach the peek stash through the one offload driver. A shared peek whose arrangement is not yet published, or whose upper has not sealed the peek timestamp, waits in `pending_work` keyed by a `WorkId` and indexed by the id it waits on. A publication or seal marks that id dirty and wakes the worker, which gives a turn to exactly the items indexed under the ids that changed, so wakeups scale with what changed rather than with total pending work. Past that gate a shared peek is an ordinary index peek: it queues for a turn like any other, and a walk that outruns the activation's fuel or fills a batch bound for the stash leaves for a driver. The sweep still runs on every step, because a persist read and an offloaded walk each wake the worker through a channel of their own rather than through the dirty set, but nothing waiting on a publication or a seal is ever swept. Interactive dataflows build immediately in command arrival order rather than deferring until their dependency is published. An import over an unadopted placeholder produces no data and holds its output frontier at the minimum until a publisher adopts the same slot, so late binding replaces the deferral. The interactive runtime reports only its transient collections' frontiers. It shares the identity of every non-transient collection with maintenance, which owns and reports the real frontiers, and the controller keeps one frontier stream per collection, so reporting the shared ones would race the owner and regress it. For the same reason its logging is forced off: it serves introspection peeks from maintenance's published copies, and its own empty copies would clobber them. The consequence is that nothing the interactive runtime does appears in introspection, tracked as CPU-222. Reconciliation drops `pending_work` and `dep_index`, whose peeks belong to the reconciled-away connection. The standing holds in the registry deliberately survive: one is per collection, carries no dataflow identity, and only rises, so clearing it would drop the arrangement's bound to the minimum until replayed compactions raised it again. Reachable only on a runtime holding the `Interactive` role, which requires the dyncfg that is still off everywhere. Tests are out of line in `compute_state/tests.rs`, per the convention in `src/compute/AGENTS.md`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 8f441a1 commit f29b660

11 files changed

Lines changed: 1871 additions & 201 deletions

File tree

‎src/compute/src/compute_state.rs‎

Lines changed: 597 additions & 148 deletions
Large diffs are not rendered by default.

‎src/compute/src/compute_state/error_scan.rs‎

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,58 @@
88
99
use std::time::{Duration, Instant};
1010

11-
use differential_dataflow::trace::{Cursor, TraceReader};
11+
use differential_dataflow::trace::{Cursor, Navigable, TraceReader};
1212
use mz_compute_client::protocol::response::PeekError;
1313
use mz_repr::{Diff, GlobalId, Timestamp};
1414
use timely::order::PartialOrder;
1515
use tracing::error;
1616

1717
use crate::arrangement::manager::PaddedTrace;
1818
use crate::compute_state::{PeekRowIterationTracker, peek_result_iterator};
19+
use crate::render::errors::DataflowErrorSer;
1920
use crate::typedefs::ErrAgent;
2021

2122
/// The error trace of an index, as
2223
/// [`TraceBundle::errs_mut`](crate::arrangement::manager::TraceBundle::errs_mut) hands it out.
2324
pub(super) type ErrsHandle = PaddedTrace<ErrAgent<Timestamp, Diff>>;
2425

26+
/// A trace an index peek's error walk can read.
27+
///
28+
/// The bound is spelled once here, so the walk and everything that carries it name the shape
29+
/// rather than restate it.
30+
pub(super) trait PeekErrsTrace:
31+
TraceReader<
32+
Batch: Navigable<
33+
Cursor: for<'a> Cursor<
34+
Key<'a> = &'a DataflowErrorSer,
35+
TimeGat<'a>: PartialOrder<Timestamp>,
36+
DiffGat<'a> = &'a Diff,
37+
>,
38+
>,
39+
>
40+
{
41+
}
42+
43+
impl<Tr> PeekErrsTrace for Tr where
44+
Tr: TraceReader<
45+
Batch: Navigable<
46+
Cursor: for<'a> Cursor<
47+
Key<'a> = &'a DataflowErrorSer,
48+
TimeGat<'a>: PartialOrder<Timestamp>,
49+
DiffGat<'a> = &'a Diff,
50+
>,
51+
>,
52+
>
53+
{
54+
}
55+
2556
/// A walk over an index peek's error trace, suspendable between cursor positions.
2657
///
2758
/// Holds nothing of the ok trace or of the rows a peek returns. A peek reaches those only once
2859
/// this walk reports [`ErrorScanStep::Finished`] with an `Ok`.
29-
pub(super) struct ErrorScan {
30-
cursor: peek_result_iterator::TraceCursor<ErrsHandle>,
31-
storage: peek_result_iterator::TraceStorage<ErrsHandle>,
60+
pub(super) struct ErrorScan<Tr: PeekErrsTrace> {
61+
cursor: peek_result_iterator::TraceCursor<Tr>,
62+
storage: peek_result_iterator::TraceStorage<Tr>,
3263
/// The limit spans this walk and the ok scan after it, so the count accrued here is handed
3364
/// on with [`ErrorScanStep::Finished`].
3465
row_iteration_tracker: PeekRowIterationTracker,
@@ -48,12 +79,12 @@ pub(super) enum ErrorScanStep {
4879
OutOfFuel,
4980
}
5081

51-
impl ErrorScan {
82+
impl<Tr: PeekErrsTrace> ErrorScan<Tr> {
5283
/// Opens a walk over `errs`.
5384
///
5485
/// The walk starts without a row-iteration limit. The limit in effect is the caller's to
5586
/// supply through [`ErrorScan::set_row_iteration_limit`] before each step.
56-
pub(super) fn new(errs: &mut ErrsHandle) -> Self {
87+
pub(super) fn new(errs: &mut Tr) -> Self {
5788
let scan_start = Instant::now();
5889
let (cursor, storage) = errs.cursor();
5990
let mut scan = Self::from_cursor(cursor, storage);
@@ -63,8 +94,8 @@ impl ErrorScan {
6394

6495
/// Opens a walk over an already-opened cursor.
6596
pub(super) fn from_cursor(
66-
cursor: peek_result_iterator::TraceCursor<ErrsHandle>,
67-
storage: peek_result_iterator::TraceStorage<ErrsHandle>,
97+
cursor: peek_result_iterator::TraceCursor<Tr>,
98+
storage: peek_result_iterator::TraceStorage<Tr>,
6899
) -> Self {
69100
Self {
70101
cursor,

‎src/compute/src/compute_state/error_scan/tests.rs‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@ pub(crate) fn error_batch(
5151

5252
/// Builds a walk over a single-batch error trace holding `updates`, bounded by
5353
/// `row_iteration_limit`.
54-
pub(crate) fn error_scan(updates: ErrorUpdates, row_iteration_limit: Option<usize>) -> ErrorScan {
54+
pub(crate) fn error_scan(
55+
updates: ErrorUpdates,
56+
row_iteration_limit: Option<usize>,
57+
) -> ErrorScan<ErrsHandle> {
5558
let storage = vec![error_batch(updates)];
5659
let cursor = CursorList::new(vec![storage[0].cursor()], &storage);
5760
let mut scan = ErrorScan::from_cursor(cursor, storage);
@@ -79,7 +82,10 @@ pub(crate) fn holding(error: &DataflowErrorSer) -> ErrorUpdates {
7982

8083
/// Runs `scan` to an answer in slices of `fuel_per_step` units, and returns that answer, the
8184
/// fuel the walk spent, and the number of calls it took.
82-
fn run_sliced(scan: &mut ErrorScan, fuel_per_step: usize) -> (ErrorScanStep, usize, usize) {
85+
fn run_sliced(
86+
scan: &mut ErrorScan<ErrsHandle>,
87+
fuel_per_step: usize,
88+
) -> (ErrorScanStep, usize, usize) {
8389
let mut consumed = 0;
8490
// Bounded so that a walk which restarts from the first key on each resumption fails the
8591
// test instead of hanging it.

‎src/compute/src/compute_state/index_peek_tests.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,8 +282,8 @@ enum Answer {
282282
Ready(PeekResponse),
283283
}
284284

285-
impl From<PeekStatus> for Answer {
286-
fn from(status: PeekStatus) -> Self {
285+
impl From<PeekStatus<IndexPeekScan>> for Answer {
286+
fn from(status: PeekStatus<IndexPeekScan>) -> Self {
287287
match status {
288288
PeekStatus::NotReady => Answer::NotReady,
289289
// The scan an offload carries has no comparison of its own. What is comparable

‎src/compute/src/compute_state/peek_offload.rs‎

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@ use tracing::{debug, warn};
3838
use uuid::Uuid;
3939

4040
use crate::compute_state::PeekRowIterationConfig;
41+
use crate::compute_state::error_scan::PeekErrsTrace;
4142
use crate::compute_state::peek_metrics::PeekWalkMetrics;
42-
use crate::compute_state::peek_scan::{IndexPeekScan, RowBatch, ScanOutcome, rows_response};
43+
use crate::compute_state::peek_scan::{
44+
PeekOksTrace, PeekScan, RowBatch, ScanOutcome, rows_response,
45+
};
4346
use crate::compute_state::peek_stash::{StashTarget, StashUpload};
4447

4548
/// The bound on how many offloaded peek walks run at once.
@@ -221,15 +224,20 @@ impl OffloadedPeek {
221224
///
222225
/// The scan may already hold a full batch. This driver takes it, so offloading is how a peek
223226
/// too large to answer inline reaches the stash.
224-
pub(super) fn start(
227+
pub(super) fn start<Tr, ETr>(
225228
peek: Peek,
226-
scan: IndexPeekScan,
229+
scan: PeekScan<Tr, ETr>,
227230
stash: Option<StashTarget>,
228231
permits: Arc<PeekPermits>,
229232
config: OffloadConfig,
230233
metrics: PeekWalkMetrics,
231234
worker: Thread,
232-
) -> Self {
235+
) -> Self
236+
where
237+
Tr: PeekOksTrace,
238+
ETr: PeekErrsTrace,
239+
PeekScan<Tr, ETr>: Send + 'static,
240+
{
233241
let (mut result_tx, result_rx) = oneshot::channel();
234242
permits.resize(config.permit_fraction.get());
235243

@@ -305,14 +313,19 @@ impl OffloadedPeek {
305313
/// Drives the scan in `state` to the peek's answer, writing what it may not answer with inline
306314
/// to `stash`. `None` means the peek was cancelled, which is the one way the walk ends without
307315
/// an answer.
308-
async fn walk(
309-
mut state: WalkState,
316+
async fn walk<Tr, ETr>(
317+
mut state: WalkState<Tr, ETr>,
310318
peek_uuid: Uuid,
311319
stash: Option<StashTarget>,
312320
config: &OffloadConfig,
313321
metrics: &PeekWalkMetrics,
314322
order_by: Arc<[ColumnOrder]>,
315-
) -> (WalkState, Option<PeekResponse>) {
323+
) -> (WalkState<Tr, ETr>, Option<PeekResponse>)
324+
where
325+
Tr: PeekOksTrace,
326+
ETr: PeekErrsTrace,
327+
PeekScan<Tr, ETr>: Send + 'static,
328+
{
316329
// Opened by the first batch the scan hands over, so a walk that never crosses the stash
317330
// threshold neither opens a shard nor writes a byte. Whether it is open is also what
318331
// decides how the peek is answered: an upload answers with a handle, and no upload means
@@ -436,15 +449,23 @@ impl OffloadedPeek {
436449
/// on a blocking thread that an abort cannot interrupt, and the permit accounts for that thread
437450
/// until the scan leaves it. Fields drop in declaration order, so the scan and its batches go
438451
/// before the permit that accounts for them.
439-
struct WalkState {
440-
scan: IndexPeekScan,
452+
struct WalkState<Tr, ETr>
453+
where
454+
Tr: PeekOksTrace,
455+
ETr: PeekErrsTrace,
456+
{
457+
scan: PeekScan<Tr, ETr>,
441458
_permit: WalkPermit,
442459
/// The sending end of the peek's result channel. Its receiver is dropped by cancellation and
443460
/// by nothing else, so a closed channel is the cancellation signal.
444461
result_tx: oneshot::Sender<(PeekResponse, Duration)>,
445462
}
446463

447-
impl WalkState {
464+
impl<Tr, ETr> WalkState<Tr, ETr>
465+
where
466+
Tr: PeekOksTrace,
467+
ETr: PeekErrsTrace,
468+
{
448469
/// Steps the scan until it ends, offers a batch, or the peek is cancelled, whichever comes
449470
/// first. `None` is the cancellation.
450471
///

‎src/compute/src/compute_state/peek_offload/tests.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use crate::compute_state::index_peek_tests::{
2727
cancelling_errors, index_peek, ok_row, rows_answer, trace_bundle, trivial_finishing,
2828
wide_ok_rows,
2929
};
30-
use crate::compute_state::peek_scan::{PeekScan, StashBounds};
30+
use crate::compute_state::peek_scan::{IndexPeekScan, PeekScan, StashBounds};
3131
use crate::compute_state::peek_stash::tests::{CountedBlob, stashed_rows};
3232
use crate::metrics::{ComputeMetrics, WorkerMetrics};
3333
use crate::server::ComputeRuntimeRole;

‎src/compute/src/compute_state/peek_scan.rs‎

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ use std::mem;
1414
use std::num::{NonZeroI64, NonZeroUsize};
1515
use std::time::{Duration, Instant};
1616

17-
use differential_dataflow::trace::cursor::BatchCursor;
1817
use differential_dataflow::trace::implementations::BatchContainer;
1918
use differential_dataflow::trace::{Cursor, Navigable, TraceReader};
2019
use mz_compute_client::protocol::command::Peek;
@@ -27,13 +26,51 @@ use mz_repr::fixed_length::ExtendDatums;
2726
use mz_repr::{Diff, GlobalId, Row, Timestamp};
2827
use timely::order::PartialOrder;
2928

30-
use crate::compute_state::error_scan::{ErrorScan, ErrorScanStep, ErrsHandle};
29+
use crate::arrangement::manager::PaddedTrace;
30+
use crate::compute_state::error_scan::{ErrorScan, ErrorScanStep, ErrsHandle, PeekErrsTrace};
3131
use crate::compute_state::peek_result_iterator::{PeekResultIterator, Step};
32+
use crate::shared_trace::{SharedErrsHandle, SharedOksHandle};
33+
use crate::typedefs::RowRowAgent;
3234

33-
/// The scan an index peek builds, over the ok trace of the arrangement that answers it.
34-
pub(super) type IndexPeekScan = PeekScan<
35-
crate::arrangement::manager::PaddedTrace<crate::typedefs::RowRowAgent<Timestamp, Diff>>,
36-
>;
35+
/// A trace an index peek's ok walk can read.
36+
///
37+
/// The bound is spelled once here, so the walk and everything that carries it name the shape
38+
/// rather than restate it.
39+
pub(super) trait PeekOksTrace:
40+
TraceReader<
41+
Batch: Navigable<
42+
Cursor: for<'a> Cursor<
43+
Key<'a>: ExtendDatums + Eq,
44+
KeyContainer: BatchContainer<Owned = Row>,
45+
Val<'a>: ExtendDatums,
46+
TimeGat<'a>: PartialOrder<Timestamp>,
47+
DiffGat<'a> = &'a Diff,
48+
>,
49+
>,
50+
>
51+
{
52+
}
53+
54+
impl<Tr> PeekOksTrace for Tr where
55+
Tr: TraceReader<
56+
Batch: Navigable<
57+
Cursor: for<'a> Cursor<
58+
Key<'a>: ExtendDatums + Eq,
59+
KeyContainer: BatchContainer<Owned = Row>,
60+
Val<'a>: ExtendDatums,
61+
TimeGat<'a>: PartialOrder<Timestamp>,
62+
DiffGat<'a> = &'a Diff,
63+
>,
64+
>,
65+
>
66+
{
67+
}
68+
69+
/// The scan an index peek over a trace this runtime maintains builds.
70+
pub(super) type IndexPeekScan = PeekScan<PaddedTrace<RowRowAgent<Timestamp, Diff>>, ErrsHandle>;
71+
72+
/// The scan an index peek over an arrangement the sharing registry publishes builds.
73+
pub(super) type SharedIndexPeekScan = PeekScan<SharedOksHandle, SharedErrsHandle>;
3774

3875
/// Rows a scan hands to its driver, in the order the scan produced them.
3976
///
@@ -128,9 +165,9 @@ pub(super) enum ScanOutcome {
128165
/// The state of a [`PeekScan`]'s walk over its error trace.
129166
///
130167
/// Both ended states drop the walk, so a peek pins error batches only while it reads them.
131-
enum ErrorPhase {
168+
enum ErrorPhase<Tr: PeekErrsTrace> {
132169
/// The walk is under way, and resumes from the cursor position it stopped on.
133-
Scanning(ErrorScan),
170+
Scanning(ErrorScan<Tr>),
134171
/// The error trace holds no error at the peek's timestamp, which is the only way to the ok
135172
/// trace. The rows the walk examined have been handed to the ok walk.
136173
Clean,
@@ -146,15 +183,16 @@ enum ErrorPhase {
146183
/// A stash-eligible scan retains at most the threshold before its first batch and the batch size
147184
/// after, plus the row that crossed either. A scan that cannot use the stash fills no batch, and
148185
/// `max_result_size` alone bounds its prefix.
149-
pub(super) struct PeekScan<Tr>
186+
pub(super) struct PeekScan<Tr, ETr>
150187
where
151-
Tr: TraceReader<Batch: Navigable>,
188+
Tr: PeekOksTrace,
189+
ETr: PeekErrsTrace,
152190
{
153191
/// The time at which the error trace is read.
154192
peek_timestamp: Timestamp,
155193
/// The collection the peek reads, for logging.
156194
target_id: GlobalId,
157-
error_phase: ErrorPhase,
195+
error_phase: ErrorPhase<ETr>,
158196
/// The walk over the ok trace, reached only once the error walk reports the error trace
159197
/// clean. Its cursor is opened with the scan, and nothing advances it before then.
160198
oks: PeekResultIterator<Tr>,
@@ -195,16 +233,10 @@ where
195233
pub(super) rows_thinned: usize,
196234
}
197235

198-
impl<Tr> PeekScan<Tr>
236+
impl<Tr, ETr> PeekScan<Tr, ETr>
199237
where
200-
Tr: TraceReader<Batch: Navigable>,
201-
for<'a> BatchCursor<Tr>: Cursor<
202-
Key<'a>: ExtendDatums + Eq,
203-
KeyContainer: BatchContainer<Owned = Row>,
204-
Val<'a>: ExtendDatums,
205-
TimeGat<'a>: PartialOrder<Timestamp>,
206-
DiffGat<'a> = &'a Diff,
207-
>,
238+
Tr: PeekOksTrace,
239+
ETr: PeekErrsTrace,
208240
{
209241
/// Opens a scan of `peek` over the traces that answer it.
210242
///
@@ -213,7 +245,7 @@ where
213245
/// the caller's to supply to each [`PeekScan::step`].
214246
pub(super) fn new(
215247
peek: &Peek,
216-
errs_handle: &mut ErrsHandle,
248+
errs_handle: &mut ETr,
217249
oks_handle: &mut Tr,
218250
max_result_size: u64,
219251
stash: StashBounds,

‎src/compute/src/compute_state/peek_scan/tests.rs‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ fn ok_iterator_with_copies(keys: &[Row], copies: Diff) -> PeekResultIterator<Tes
116116

117117
/// A walk over an error trace holding `keys` errors that each cancel to zero at
118118
/// [`PEEK_TIMESTAMP`], so the walk examines every one of them and finds no error.
119-
fn clean_error_scan(keys: usize) -> ErrorScan {
119+
fn clean_error_scan(keys: usize) -> ErrorScan<ErrsHandle> {
120120
crate::compute_state::error_scan::tests::error_scan(cancelling_errors(keys), None)
121121
}
122122

@@ -125,7 +125,7 @@ fn clean_error_scan(keys: usize) -> ErrorScan {
125125
/// Mirrors what [`PeekScan::new`] builds. Tests use this rather than `new` to hold a second
126126
/// cursor layout under test, and to start from an [`ErrorPhase`] that a fresh scan cannot be
127127
/// in.
128-
fn scan(error_phase: ErrorPhase, keys: &[Row]) -> PeekScan<TestTrace> {
128+
fn scan(error_phase: ErrorPhase<ErrsHandle>, keys: &[Row]) -> PeekScan<TestTrace, ErrsHandle> {
129129
PeekScan {
130130
peek_timestamp: PEEK_TIMESTAMP,
131131
target_id: GlobalId::User(1),
@@ -613,7 +613,7 @@ fn open(
613613
max_result_size: u64,
614614
peek_stash_eligible: bool,
615615
peek_stash_threshold_bytes: usize,
616-
) -> PeekScan<OksHandle> {
616+
) -> PeekScan<OksHandle, ErrsHandle> {
617617
let (oks, errs) = bundle.oks_errs_mut();
618618
PeekScan::new(
619619
peek,
@@ -635,7 +635,7 @@ fn open(
635635
/// this reports is comparable across runs that cross the stash threshold and runs that do
636636
/// not.
637637
fn run_sliced(
638-
subject: &mut PeekScan<OksHandle>,
638+
subject: &mut PeekScan<OksHandle, ErrsHandle>,
639639
fuel_per_step: usize,
640640
row_iteration_limit: Option<usize>,
641641
) -> (ScanOutcome, RowBatch, usize) {

0 commit comments

Comments
 (0)