perf(r2rml): scan-side top-k for ORDER BY DESC LIMIT (PR-5, cold 37s→3.1s) - #1495
Closed
aaj3f wants to merge 6 commits into
Closed
perf(r2rml): scan-side top-k for ORDER BY DESC LIMIT (PR-5, cold 37s→3.1s)#1495aaj3f wants to merge 6 commits into
aaj3f wants to merge 6 commits into
Conversation
…(F13) The native micro-query perf baselines had drifted from the current machine state, firing recurring false SLOW alarms on every gate (q034 blessed 176ms vs ~320ms observed, q050 96ms vs ~300ms observed). A base A/B on c4a9b79 already showed the ratio pre-dates the code under test (04-findings-register F13), so this is machine drift, not a regression. Re-blessed from a fresh quiet-machine native-sf01 5-rep run: q034 176->322, q050 96->283; others stable (q046 202->193, q005 3->3, q012 1031->1047). Kills the false SLOWs before PR-5's gates.
…ers (PR-5 core) The correctness core for the PR-5 scan-side ORDER BY DESC LIMIT pushdown, ahead of the directive plumbing + async read branch + ORDER BY detection: - TopKBound: the running k-th bound over the sort column's non-null values, with strict-`<` over-keep at the boundary, a heap-FULL precondition for any prune (so k-exceeds-non-null reads everything), and NaN-safe compares (a NaN key only weakens pruning, never over-prunes). - plan_topk_read: the DESC read order by decoded upper_bound, with no-bound files (all-NULL column / missing stats) LAST so they are always read. - batch_sort_values: null-skipping extraction of the sort column as TypedValues. Pure + unit-tested (10 tests: tie boundary, evict-k-th, k-exceeds-non-null reads-all, all-null-file-last, null-skipping, int/string/double keys). Also adds the approved design doc (15-pr5-scan-topk.md) with the four riders + DESC-only.
Plumbs the DESC ORDER BY LIMIT top-k directive from the SortOperator down to the Iceberg scan, on top of the PR-5 core engine (d94d6e4): - provider: ScanTopK{sort_column,k} + a topk param on the scan_table trait. - query operator: a topk (sort_var,k) field + set_topk (mirroring set_row_budget, gated by FLUREE_R2RML_TOPK_PUSHDOWN) + resolve_topk_directive (var -> single scalar scan column, the build_scan_filters soundness gate). The main-table scan carries the resolved directive; parent/dimension and aggregate scans never do. A topk scan bypasses the scan cache (cacheable gains && topk.is_none()) -- a pruned subset under (table, projection) would silently drop rows for a later full scan. - detection: apply_solution_modifiers offers the primary DESC sort var + (limit + offset) to the scan below for a non-DISTINCT ORDER BY DESC LIMIT; ASC and the pre-sort-DISTINCT path decline. - api scan: when the directive resolves, read files in upper_bound-DESC order with the running k-th bound and stop early, streaming a strict superset; the sort above stays authoritative. Bounded by TOPK_SEQUENTIAL_CAP (128) -- an ineffective prune finishes on the normal parallel reader, so topk is never slower than the full scan. The disk artifact cache (path+size keyed, whole-file) can't be poisoned by a subset. All 1232 query + 182 iceberg unit tests pass; clippy/fmt clean.
…s guard (PR-5) Completes the PR-5 scan-side top-k pushdown: the directive now reaches the R2RML scan through the GraphOperator wrapper, with the residual-filter soundness guard, observability, and the cache-poison / OFFSET locks. - Forwarding: a virtual query's scan lives in a GraphOperator's per-parent inner subplan, so set_topk offered to the wrapper hit its default no-op. GraphOperator now threads the directive into both inner-subplan build sites (exactly as it does row_budget); ProjectOperator forwards it; Offset (above the sort, k already owns +offset) and Join decline. - Soundness guard: resolve_topk_directive DECLINES the pushdown when the scan carries a RESIDUAL filter (consumed_filter / object_constant / subject_constant / star_constraints) the operator enforces after emit -- otherwise the heap would be fed a superset and the k-th bound could ride too high and drop qualifying rows. Feed-site invariant comment; the cache bypass keys on the STORED directive so a declined-topk scan runs FULL and merely skips the cache (never caches a subset). - Observability: the topk branch emits an iceberg.scan_plan span (files_selected=reads, files_pruned=total-reads) so the harness counters capture the prune. On the cold miss arm the planner also emits a scan_plan span (the pre-topk plan), so files_selected double-counts there while files_pruned stays authoritative -- the gate ratio is files_pruned/total_files. - Tests: topk_declines_on_residual_filter (4 residual shapes + pure positive), topk_scan_bypasses_scan_cache (a topk scan re-reads each batch vs a cached full scan), topk_directive_carries_limit_plus_offset (k = LIMIT+OFFSET). The ScanTopK trait param threaded into the remaining mock impls. Closing gate (DW_SF01, 7670 files): q046 cold ~37s -> 3.1s (~12x), files_pruned 7660/7670 = 99.87%; hot ON 136ms vs OFF 406ms with byte-identical hashes (the revert differential); ORDER-BY sweep + native 54/54 + W3C green, 0 perf violations.
A data-identical virtual-sf01 clone used by the stacked-rebaseline protocol (docs/audit/2026-07-virtual-dataset-perf/09-stacked-rebaseline.md) to bless a post-PR-stack perf reference without clobbering the pre-PR baseline. It was left untracked in the worktree; tracking it so the protocol target is reproducible.
aaj3f
force-pushed
the
perf/r2rml-pr7-numeric-stats
branch
from
July 15, 2026 17:04
2a07bbb to
b0bba4f
Compare
…op-k resolve_topk_directive silently declined the scan-side top-k when the sort predicate mapped to more than one POM (a duplicate-predicate mapping), with nothing in the logs. Add a debug! breadcrumb matching PR-7's decline-observably convention (#1495 review). No behavior change; the other #1495 doc-comment optionals (can_topk !distinct note, topk.rs stats line) are noted in the response.
aaj3f
force-pushed
the
perf/r2rml-pr5-scan-topk
branch
from
July 15, 2026 17:06
a8de70e to
83347b7
Compare
Contributor
Author
|
Consolidating at maintainer request. #1495 (PR-5 — scan-side top-k for |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
PR-5 (the final roadmap item): scan-side top-k for
ORDER BY DESC(<scan col>) LIMIT kover a single-table R2RML/Iceberg scan. The scan reads files inupper_bound(sort_col)-DESC order, keeps a running k-th bound, and stops once no unread file can beat it — reading ~k files instead of the whole table.Closing gate (DW_SF01, 7670 files per fact table)
Cold ~37s → 3.1s (~12×), files_pruned 7660/7670 = 99.87% — the headline. ON is faster than OFF even hot. ORDER-BY-class parity sweep (q005/q012 declined via fallback, q046 identical), native 54/54, and the W3C ORDER BY/LIMIT suite: 0 hash mismatch, 0 perf violations.
Honest scope: q046 is the only corpus query this helps (raw-column top-k over a fact table), and the win is cold (warm q046 was already ~400ms). PR-7's numeric stats paid the hard cost; this is the harvest on the canonical "top N by measure" shape.
How
The
SortOperatoroffers aTopK(primary DESC var, k = LIMIT + OFFSET)directive to the scan below (mirroringrow_budget);GraphOperatorthreads it into its per-parent inner subplan (where a virtual query's scan lives), andProjectOperatorforwards it. The api scan reads files in bound-DESC order with a size-(k+offset) heap and early-stops; aTOPK_SEQUENTIAL_CAPfallback to the parallel reader means an ineffective prune is never slower than the full scan.sort.rs::new_topkabove stays the sole authority for the exact (compound) order + LIMIT — the scan only skips files it proves cannot contribute, so results are byte-identical to full sort.Invariants
resolve_topk_directiveDECLINES the pushdown when the scan carries a RESIDUAL filter (consumed_filter/object_constant/subject_constant/star_constraints) the operator enforces after emit — feeding a superset would ride the k-th bound too high and silently drop qualifying rows. Pushed scan filters are applied by the reader (the emitted batch is already post-filter), so they compose fine.cacheable && self.topk.is_none()), keyed on the STORED directive — so a declined-topk scan runs FULL and merely skips the cache, never caching a subset that a later full scan of the same(table, projection)could replay.Kill switch
FLUREE_R2RML_TOPK_PUSHDOWN(default on); off ⇒ no directive ⇒ today's full-materialize top-k.Tests
iceberg core (10):
TopKBoundbound/tie/NaN,plan_topk_read/batch_sort_values, read-loop simulations (prune + k-exceeds-non-null + all-null-file). query:topk_declines_on_residual_filter(all four residual shapes + pure-scan positive),topk_scan_bypasses_scan_cache(cache-poison — a topk scan re-reads each batch vs a cached full scan),topk_directive_carries_limit_plus_offset(k = LIMIT+OFFSET). All green; clippy/fmt clean; the full-p fluree-db-api --features icebergsuite is green.Note on observability: the topk branch emits an
iceberg.scan_planspan for the harness counters; on the cold miss arm the planner also emits one (the pre-topk plan), sofiles_selecteddouble-counts there whilefiles_prunedstays authoritative — the gate ratio isfiles_pruned / total_files.Stacked on
perf/r2rml-pr7-numeric-stats. The F13 native-perf re-bless rides on this branch. Closes the virtual-dataset perf roadmap.