Streaming search

Streaming #

Search on Pizza is real-time — documents are visible the moment they are written. The stream block attaches delivery directives to a search request: how the engine should pace and batch the emission of a result that outgrows one response, instead of making the client poll.

POST /my-collection/_search

How it works #

stream is a native, parsed block of the Pizza request DSL (StreamConfig in lib/engine/src/search/request/mod.rs) — accepted, validated, and carried through the engine with every request. Its knobs describe the emission strategy of the search pipeline:

  • batch (the default) is the linear pipeline — execute fully, rank, return one JSON answer.
  • progressive paces emission per segment: partial results become available as each segment completes, rather than after all of them.
  • threshold holds hits back until scores drop below score_threshold (or max_wait_ms passes), trading a little latency for a stable top of the list.
  • buffer_size micro-batches whatever is emitted; aggs_priority decides whether aggregations in the same request yield to hit delivery (deferred ships hits first and fills the aggregation results in afterwards).

Honest delivery status: over the HTTP gateway every mode currently answers as one complete, buffered JSON body — the block is parsed and executed, but the gateway does not yet translate it into a chunked or event-stream HTTP response, and the in-page widget executes one-shot queries against a WebAssembly engine either way. What is fully real today is the realtime half of the story: there is no refresh interval, so a query re-issued immediately after a write sees that write (see How realtime works) — polling with search_after cursors is an effective stand-in for push delivery while wire-level streaming matures.

Example #

This page has no live playground demo: streaming directives shape how a long-running result is paced, while the in-page widget executes one-shot queries against a WebAssembly engine and renders a complete JSON result. Over the gateway today the request below simply returns the usual buffered JSON answer — the block is accepted and executed, wire-level chunked delivery is not switched on yet.

{
  "query": { "match": { "field": "message", "query": "error" } },
  "size": 0,
  "stream": {
    "mode": "progressive",
    "buffer_size": 4,
    "max_wait_ms": 250
  }
}

Use cases #

  • Dashboards over big scans. progressive with a generous buffer_size: the first segments’ hits reach the UI while later segments are still being searched, instead of a spinner until the whole corpus is ranked.
  • Alerting-style consumers. threshold mode with a high score_threshold and a small buffer: only strong matches flush early, and max_wait_ms bounds how long a partial buffer waits.
  • Hits before aggregates. A request combining a large hit page with heavy aggregations under aggs_priority: "deferred" ships the hits first and lets the aggregation totals follow.
  • Fresh-as-possible tails. Realtime visibility means a polling loop with search_after over logs sees a freshly written error line on its very next request — no refresh window to wait out.

Parameters for stream #

  • mode
    (Required, batch | progressive | threshold) Delivery mode:
    • batch — normal one-shot search; the block only configures timing.
    • progressive — flush partial results as the query makes progress, then keep pushing newly matching documents.
    • threshold — hold results back until a score threshold or wait budget is met, trading freshness for stability.
  • buffer_size
    (Optional, integer, default: 4) Micro-batching of pushed hits — more is cheaper per hit, less is lower latency.
  • score_threshold
    (Optional, float) In threshold mode, the score a hit must reach before the buffer flushes.
  • max_wait_ms
    (Optional, integer) Upper bound on how long a partially filled buffer may wait before flushing regardless.
  • aggs_priority
    (Optional, normal | low | deferred, default: normal) Whether aggregations in the same request block streaming delivery. deferred ships hits first and fills in aggregation results later.

Notes #

  • Streaming interacts with realtime visibility: a hit can arrive in the same request that indexed it — there is no refresh interval to wait for.
  • Cursors (search_after) combine with streaming for durable consumption; the change feed is the complementary write-path stream (CDC).
  • Keep buffer_size small for alerting-style consumers and large for dashboard-style consumers.
  • API conventions — the request envelope every stream block rides in, and pit snapshots for stable re-queries while data moves.
  • Sort and pagination — search_after cursors: the polling leg of the streaming story.
  • Change feed — the write-path stream (CDC) for consumers that need every change, not every match.
  • How realtime works — the write path (WAL, mutable layer, epoch freeze) that removes the refresh window streaming builds on.
Calendar October 6, 2026
Edit Edit this page