Zettel — jDisc Stateless Searcher Chain Execution and Two-Phase Fill

In the stateless Java container (vespa/container-search/src/main/java/com/yahoo/search/searchchain/Execution.java), a search request traverses an immutable, dependency-ordered chain of Searcher components as a two-call protocol: search(Query, Execution) followed lazily by fill(Result, String summaryClass, Execution).

Mechanism & Upstream Grounding

  1. Per-Request Execution Cursor Over Shared Searcher Instances:
    • Searcher components are singleton OSGi-managed graph nodes wired by vespa/container-disc; all per-request state lives inside the Query, Result, and Execution stack frame (INV-jdisc-stateless).
  2. Phase 1 — Execution.search(Query) (Thin Hit Scatter-Gather):
    • Each Searcher mutates or inspects the Query (YQL parsing, query profile resolution, embedding generation,federation) and delegates downstream via execution.search(query).
    • The terminal cluster dispatcher broadcasts the query to C++ proton content nodes, returning only global sort/relevance scores, GlobalId/LID handles, and optional match-features — without fetching heavy document summary fields over the network.
  3. Phase 2 — Execution.fill(Result, summaryClass) (On-Demand Summary Fetch):
    • Only after container-side merging, global-phase reranking, and pagination (offset / hits) trim the Result to the final winning window does execution.fill(result) request docsum payloads from the content nodes that own the winning hits.

Boundary Conditions & Trade-offs

  • Early Fill Anti-Pattern: Calling execution.fill(result) inside a custom Searcher before top-K trimming forces proton nodes to decompress and serialize full document summaries for all intermediate scatter-gather candidates, multiplying network and disk I/O (see inv-jdisc-searcher-stateless-fill-discipline and querying-yql).