Skip to content

compute: add configurable peek row iteration limit - #38451

Merged
antiguru merged 3 commits into
peek/designfrom
peek/structured-errors
Aug 27, 2026
Merged

compute: add configurable peek row iteration limit#38451
antiguru merged 3 commits into
peek/designfrom
peek/structured-errors

Conversation

@antiguru

@antiguru antiguru commented Aug 25, 2026

Copy link
Copy Markdown
Member

Introduces PeekError as a structured peek failure and adds a configurable limit on how many rows a peek may iterate.

The first commit is @aljoscha's, taken from #38158 and authored by him. It carries the uncommitted fixes that accompanied that patch, because the mailbox commit alone leaves a state where the golden in src/environmentd/tests/testdata/http/ws contradicts its own code: the double-wrap in src/environmentd/src/http/sql.rs re-flattens the structured error to XX000. Splitting the fixes out would have attributed his work to someone else and left a red intermediate commit. The version gate was moved to this branch's release.

PeekError::Dataflow is produced unconditionally, so structured error frames flow on the wire regardless of whether the row-iteration limit is enabled.

The second commit resets enable_compute_peek_row_iteration_limit after use in the two slt files that set it.

🤖 Opened by Claude Code on behalf of @antiguru

@antiguru antiguru changed the title peek/structured errors compute: add configurable peek row iteration limit Aug 25, 2026
aljoscha and others added 3 commits August 27, 2026 13:35
Compute workers iterate arrangements synchronously while serving
index-backed peeks, so a query that scans far more rows than it returns
can hold a worker for a long time and delay everything else on the
cluster. Persist fast-path peeks have the same shape: filtering happens
after the rows have been read.

Add an off-by-default failsafe that bounds how many rows a worker may
examine for one peek. Two dyncfgs, a feature gate and a threshold that
defaults to 1000 rows, both read through handles so that an
`UpdateConfiguration` reaches peeks that are already in flight. The
budget covers the index result trace, the index error trace and the
Persist fast path, and counts rows before literal and MFP filtering,
because a row that is read and then discarded costs the same scan time
as one that is returned. Exactly the configured number of rows may be
examined. A peek fails only when it asks for the row after that.

The limit deliberately stops at the peek stash. A stashed peek restarts
its scan and produces in bounded bursts, so bounding it needs the count
to survive the hand-off, and the restart makes that count charge the
same rows twice. Leaving it out keeps this change small. The peeks that
motivate the failsafe, large filtered scans, fail before they ever reach
the stash threshold.

Reporting the limit needs an error type that survives the trip from the
worker. `PeekResponse::Error` carried a bare `String`, so every peek
failure reached the adapter as `AdapterError::Unstructured` and was
reported as XX000. Give it a `PeekError` of `Dataflow`, `Unstructured`
or `RowIterationLimitExceeded`, and let `PeekResponseUnary::Error` carry
an `AdapterError`, so the conversion happens once instead of once per
frontend. The limit then reports SQLSTATE 54000 with a hint naming the
threshold parameter, and worker responses merge by error precedence:
cancellation, then ordinary errors, then the limit.

Carrying the dataflow error structurally also fixes the SQLSTATE of
evaluation errors raised while reading a collection: `SELECT a / b FROM
t` now reports 22012 like its constant-folded counterpart. Such an error
keeps the message `DataflowError` renders, so one that used to come back
bare from an index or Persist fast-path MFP now carries the `Evaluation
error:` prefix the error-trace path already used.

The wire encoding is bincode, which cannot skip a variant it does not
know, so `PeekResponse` serializes through a mirror type that keeps
`Error(String)` where it was for the unstructured case and appends the
structured one. Existing frames, `Canceled` in particular, encode
exactly as before.

The test and CI configuration enables the feature with a high threshold,
so the guarded path is exercised broadly without constraining ordinary
queries.
persist-fast-path.slt and max_result_size.slt reset
compute_peek_row_iteration_limit at the end but leave the feature
flag itself enabled. That is harmless under bin/sqllogictest, whose
suite default for the limit is very high, but wrong against a plain
environmentd where the code default is 1000.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The limit is enforced per worker, so a multi-worker cluster walks the same
rows a share at a time and no single walk reaches it. That made the
assertion depend on the cluster's worker count, and it made the one-shot
query disagree with the indexed view --auto-index-selects wraps it in: the
view holds only the three rows the LIMIT produced, spread across workers,
so it answered where the one-shot query raised. Pin the block to a
single-worker cluster, which is what max_result_size.slt already does for
the same reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment on lines +206 to +207
/// Wire shape of [`PeekResponse`], mirrored so that giving `Error` a payload type does not
/// change the bytes we put on the CTP connection.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The CTP doesn't need to be compatible across versions. Would it allow us to skip the custom serializer?

Comment on lines +736 to +746

#[cfg(test)]
mod tests {
use super::*;

#[mz_ore::test]
fn peek_row_iteration_limit_defaults() {
assert!(!*ENABLE_PEEK_ROW_ITERATION_LIMIT.default());
assert_eq!(*PEEK_ROW_ITERATION_LIMIT.default(), 1000);
}
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

No need for this test.

@antiguru
antiguru force-pushed the peek/fueled-iterator branch from 10fb8c1 to 01b6e4a Compare August 27, 2026 11:38
@antiguru
antiguru force-pushed the peek/structured-errors branch from 4c559b2 to c5a40e9 Compare August 27, 2026 11:38
Base automatically changed from peek/fueled-iterator to peek/design August 27, 2026 11:39
@antiguru
antiguru merged commit c5a40e9 into peek/design Aug 27, 2026
3 of 5 checks passed
@antiguru
antiguru deleted the peek/structured-errors branch August 27, 2026 11:39
@antiguru
antiguru restored the peek/structured-errors branch August 27, 2026 11:44
antiguru added a commit that referenced this pull request Aug 27, 2026
Introduces `PeekError` as a structured peek failure and adds a
configurable limit on how many rows a peek may iterate.

The first commit is @aljoscha's, taken from #38158 and authored by him.
It carries the uncommitted fixes that accompanied that patch, because
the mailbox commit alone leaves a state where the golden in
`src/environmentd/tests/testdata/http/ws` contradicts its own code: the
double-wrap in `src/environmentd/src/http/sql.rs` re-flattens the
structured error to `XX000`. Splitting the fixes out would have
attributed his work to someone else and left a red intermediate commit.
The version gate was moved to this branch's release.

`PeekError::Dataflow` is produced unconditionally, so structured error
frames flow on the wire regardless of whether the row-iteration limit is
enabled.

The second commit resets `enable_compute_peek_row_iteration_limit` after
use in the two slt files that set it.

🤖 Opened by [Claude Code](https://claude.com/claude-code) on behalf of
@antiguru


Replaces #38451, which GitHub closed when the design document moved from
the bottom of the stack to the top and its branch was force-pushed past
these commits. Same content, new base.

---------

Co-authored-by: Aljoscha Krettek <aljoscha.krettek@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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