Problem
FHIR type search (blaze.interaction.search-type) builds a page in two strictly sequential steps: it first materializes a full page of resource handles (the single-threaded index scan), and only afterwards pulls the match resource contents via pull-many. On servers with slower (disk) I/O the scan and the parallel pull therefore run back to back, so per-page latency is roughly scan + pull.
Measurements on representative code queries show scan and pull times in the same ballpark (e.g. ~24 ms / ~23 ms on a slower server), so overlapping them removes a meaningful fraction of per-page latency — most relevant for bulk export, where pages are fetched sequentially.
Change
Pull each match's resource content as soon as its handle is produced during the scan, so the resource I/O overlaps the scan instead of starting after it. Per-page latency moves from scan + pull toward max(scan, pull).
Supporting changes in the db module:
d/pull-fn — a streaming, per-handle pull primitive. It processes the options once (:variant / :elements / :skip-cache-insertion?) and returns a function of a resource handle to a CompletableFuture, avoiding pull-many's batch overhead.
rc/get-skip-cache-insertion — single-key counterpart to the existing multi-get-skip-cache-insertion, needed when paging (skips cache insertion).
In search-type:
- The scan reduction fires a pull for each match handle and collects the futures; they are awaited afterwards. The next-match handle (which only seeds the paging link) is not pulled. Includes still use
pull-many.
- Phase histograms
fhir_interaction_search_type_search_duration_seconds with phase = compile-query / scan / pull-matches / pull-includes, to measure the overlap on real servers (compare scan vs pull-matches).
Notes
- Deleted-resource handling and error propagation (including
too-costly includes) are unchanged.
- The
too-costly inclusion message is now built at call time so its thousands separator follows the default locale (tests pin it via tu/set-default-locale-english!).
Problem
FHIR type search (
blaze.interaction.search-type) builds a page in two strictly sequential steps: it first materializes a full page of resource handles (the single-threaded index scan), and only afterwards pulls the match resource contents viapull-many. On servers with slower (disk) I/O the scan and the parallel pull therefore run back to back, so per-page latency is roughlyscan + pull.Measurements on representative
codequeries show scan and pull times in the same ballpark (e.g. ~24 ms / ~23 ms on a slower server), so overlapping them removes a meaningful fraction of per-page latency — most relevant for bulk export, where pages are fetched sequentially.Change
Pull each match's resource content as soon as its handle is produced during the scan, so the resource I/O overlaps the scan instead of starting after it. Per-page latency moves from
scan + pulltowardmax(scan, pull).Supporting changes in the
dbmodule:d/pull-fn— a streaming, per-handle pull primitive. It processes the options once (:variant/:elements/:skip-cache-insertion?) and returns a function of a resource handle to aCompletableFuture, avoidingpull-many's batch overhead.rc/get-skip-cache-insertion— single-key counterpart to the existingmulti-get-skip-cache-insertion, needed when paging (skips cache insertion).In
search-type:pull-many.fhir_interaction_search_type_search_duration_secondswithphase=compile-query/scan/pull-matches/pull-includes, to measure the overlap on real servers (comparescanvspull-matches).Notes
too-costlyincludes) are unchanged.too-costlyinclusion message is now built at call time so its thousands separator follows the default locale (tests pin it viatu/set-default-locale-english!).