Skip to content

feat(hotblocks): let a stream request ask for the new query engine - #125

Merged
mo4islona merged 2 commits into
masterfrom
feat/hotblocks-query-engine-header
Oct 23, 2026
Merged

mo4islona merged 2 commits into
masterfrom
feat/hotblocks-query-engine-header

Conversation

@mo4islona

@mo4islona mo4islona commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

The shadow compares the new query engine's answers with the legacy ones but never serves them. This lets a caller ask for the new engine on a stream request, including requests only the new engine understands, such as value sets.

x-sqd-query-engine on POST /datasets/{id}/stream and /finalized-stream:

Value Answer
no header, legacy the legacy engine, as before; a sampled query is still shadowed
dynamic the new engine alone, no shadow
anything else 400 MALFORMED_REQUEST

dynamic is the name the network's query message already gives the new engine (QUERY_ENGINE_DYNAMIC). A dynamic request to a dataset kind with no loaded catalog is a 400 UNSUPPORTED_QUERY. Nothing routes a query to the new engine on its own: only the header does.

How it works:

  • With dynamic the legacy parser never sees the body. The service reads fromBlock, toBlock and parentBlockHash itself, since it needs them to wait for the head and find the first chunk; the kind comes from the dataset. The new engine parses and checks the rest, type included.
  • The new engine compiles the body in an admitted executor task (step compile in the query task metrics) before the query waits for the head, as a legacy query is validated before it: a large request never holds a runtime thread, a busy service refuses it before any parsing, and a request the engine refuses is a 400 at once rather than a 204 when the wait runs out.
  • RunningQuery keeps each chunk's bounds itself, so both engines read the same blocks, and an answer cut at the weight budget resumes from the cut.
  • The new engine's errors keep the legacy status codes: a fork is the same 409 with previousBlocks, a missing table or column is ITEM_UNAVAILABLE, a validation kind is MALFORMED_REQUEST, anything else is a 500.
  • hotblocks_http_status and hotblocks_http_seconds_to_first_byte get an engine label (legacy, dynamic, invalid) on the two stream routes, to compare error rates and latency per engine.
  • Bumps sqd-query-engine from 155460e to fee829e (value sets, a 16 MiB request bound). The catalogs are the same in both revisions, so tests/metadata stays as it is.
  • Spec: IB-3a in spec/13-interface-binding.md.

Tests (tests/query_engine_header.rs, query/range.rs):

  • seven query shapes and a finalized stream give the same status and bytes from both engines; requests with the header are not shadowed;
  • a catalog edit that changes the new engine's answer shows that the new engine is the one that answers;
  • a catalog where every block outweighs the budget: the cut answers still give the legacy bytes;
  • a wrong parentBlockHash gives the same 409 from both engines;
  • a set query: without the header the legacy parser refuses it (422); with dynamic it answers what the same values give as a list;
  • a query the new engine cannot compile, or with another dataset's type, is a 400, also with a fromBlock above the head; with no query capacity it is a 503 instead, so it is compiled only after admission;
  • an unknown value and a missing catalog are refused; the block keys read from a body match the legacy parser's.

🤖 Generated with Claude Code

@mo4islona
mo4islona force-pushed the feat/hotblocks-query-engine-header branch from 3dfaa78 to 4592f5a Compare October 23, 2026 21:39
…B request bound

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@mo4islona
mo4islona force-pushed the feat/hotblocks-query-engine-header branch from 5237d42 to af78746 Compare October 23, 2026 23:14
The shadow compares the new engine's answers but never serves them. With
`x-sqd-query-engine: dynamic` the new engine alone parses and answers a stream
request, with no shadow; without the header nothing changes. The service reads
only the block bounds from the body, so a request may use what the legacy parser
refuses, such as value sets. The plan compiles in an admitted task before the
query waits for the head, so a request the engine refuses is refused at once.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@mo4islona
mo4islona force-pushed the feat/hotblocks-query-engine-header branch 2 times, most recently from d3a0f45 to 7b45c4e Compare October 23, 2026 23:41
@mo4islona
mo4islona merged commit 9f9d008 into master Oct 23, 2026
5 of 6 checks passed
@mo4islona
mo4islona deleted the feat/hotblocks-query-engine-header branch October 23, 2026 23:57
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.

1 participant