Skip to content

perf(r2rml): admit subject-driven R2RML leaves to the batched correlated-OPTIONAL (PR-4b)#1487

Open
aaj3f wants to merge 7 commits into
perf/r2rml-pr4-parent-memofrom
perf/r2rml-pr4b-batched-optional
Open

perf(r2rml): admit subject-driven R2RML leaves to the batched correlated-OPTIONAL (PR-4b)#1487
aaj3f wants to merge 7 commits into
perf/r2rml-pr4-parent-memofrom
perf/r2rml-pr4b-batched-optional

Conversation

@aaj3f

@aaj3f aaj3f commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Companion to #1485 (PR-4), fixing the class PR-4 explicitly deferred: the correlated-OPTIONAL per-row operator rebuild.

Mechanism

inner_pattern_is_hash_join_safe admitted only Triple/Filter/PropertyPath — and the R2RML rewrite converts OPTIONAL inner triples to Pattern::R2rml — so every OPTIONAL over a virtual dataset fell to the per-row build: the whole inner operator tree rebuilt per correlated row (q050: 430+ scans, DNF; each rebuild also reset every operator-scoped cache, which is why #1485's memo couldn't help here). This PR admits the narrow shape — subject-driven scalar-POM / single-valued RefObjectMap leaves; stars, type-var, wildcard-predicate, and bound-subject shapes stay per-row with pointers to the soundness analysis (docs/audit/2026-07-virtual-dataset-perf/07-pr4b-batched-optional.md). Kill switch FLUREE_R2RML_BATCHED_OPTIONAL (default on; off = per-row exactly).

Prerequisite (own commit, df5cb353e): R2rmlPattern::referenced_vars hardened to correct-by-construction (exhaustive destructure + completeness test) — the correlation-set computation depends on it, and the audit confirmed no live omission existed.

Results (SF01, 16 cores)

before after
q050 (dims-only OPTIONAL, 37.5K products) DNF@120s, 430+ scans ok, 16.7s, 92 scans, hash == native oracle (30,000 rows)
switch off per-row DNF signature faithfully reproduced
native corpus 54/54, 0 hash mismatches

q016 verified declining on the STAR exclusion (its OPTIONAL inner fuses to a same-subject star) — checked from the admission path, not inferred; future widening targets star admission, and its inner is fact-scale anyway (H1 territory).

Correctness evidence — two halves, deliberately

  • (A) Live oracle differential: q050 batched result is hash-identical to the blessed native oracle on all 30K rows including genuine OPTIONAL-misses — covering the empty-bucket LEFT-JOIN partition path directly. Caveat honestly stated: SF01 has referential integrity by generation, so this run covers dangling-FKs only by code-path identity (a dangling FK and a filter-miss are indistinguishable at the partition layer).
  • (B) Hermetic differential (CI-enforced): batched_equals_per_row_on_dangling_fk drives build_batch and the per-row build head-to-head on a mock provider with a genuine dangling FK — row-for-row equality including the dangling row's miss. A live batched-vs-per-row comparison is impossible on q050 (the per-row side is the thing that DNFs), which is exactly why this hermetic artifact exists rather than a construction argument alone.

Suites: query 1204/1204, api all bins green. Composes with #1485: the parent memo now applies inside the single long-lived batched inner.

Stacks on #1485 (PR-4) ← #1484#1482#1478#1476#1475#1450.

aaj3f added 7 commits July 13, 2026 14:10
…uction (PR-4b P1)

Audit result: NO live omission — the old produced_vars delegation was
complete because scan_filters and consumed_filter reference only vars the
pattern itself produces (by documented invariant). De-fragilize anyway:
exhaustively destructure all 15 fields (no , so a new var-bearing
field is a compile error here until classified) and include the filter
vars explicitly rather than by invariant. Output identical for the current
struct; completeness test covers all 7 var-bearing fields. Precursor to
admitting R2RML leaves into the batched-OPTIONAL hash-join, whose
correlation-set computation depends on referenced_vars being exhaustive.
…ted-OPTIONAL (PR-4b)

A correlated OPTIONAL whose inner is an R2RML pattern always fell to the
per-row build path — inner_pattern_is_hash_join_safe admitted only
Triple/Filter/PropertyPath, and the R2RML rewrite converts OPTIONAL inner
triples to Pattern::R2rml — so the entire inner operator tree was rebuilt
per correlated row (q050: 430+ scans, DNF; this also reset every
operator-scoped cache, which is why PR-4's memo could not help here).

Admit the NARROW shape: subject-driven scalar-POM / single-valued
RefObjectMap leaves (subject_var + object_var + predicate_filter set; no
stars, star-constraints, type-var, wildcard predicate, or bound subject —
excluded with pointers to the design doc, docs/audit/.../07). Kill switch
FLUREE_R2RML_BATCHED_OPTIONAL (default on; off = per-row exactly).

Correctness evidence, both halves:
(A) live q050 batched result hash == the NATIVE oracle on all 30,000 rows
including genuine OPTIONAL-misses (covers the null/miss partition path
directly; SF01 has referential integrity so dangling-FK only by code-path
identity there);
(B) hermetic batched-vs-per-row differential on a mock provider with a
GENUINE dangling FK — row-for-row equality incl. the dangling row's miss
(covers dangling-FK directly, CI-enforced).

Live SF01 (16 cores): q050 DNF@120s -> ok 16.7s, 430+ -> 92 scans;
switch-off reproduces the per-row DNF signature. q016 verified declining
on the STAR exclusion (its inner fuses to a same-subject star), not
object-correlation — future widening targets star admission. Native
corpus 54/54, 0 hash mismatches; q005/q033 sentinels ok. Query suite
1204/1204; api suite all bins green. Prerequisite P1 (referenced_vars
correct-by-construction) is df5cb35. Composes with PR-4: the parent
memo now applies inside the single batched inner.
…oritization memo (WP9)

Full-corpus re-measure on the 10-PR stack: 49 ok / 2 dnf / 3 loud-by-design
of 54 (was 24 ok / 30 dnf), always-ok queries a median 3.8x faster.
Attribution: PR-2 carries 24 of 28 flips + the across-the-board speedup;
PR-1 the three COUNTs; PR-4b(+3) q050; PR-0 the silent-wrong conversions.

Remaining reds conclusively diagnosed: q031 = H3-family correlated parent
re-scan on the INNER-JOIN seam (1+1305=1306 scans, arithmetic-proven;
uncovered by PR-4's within-operator memo and PR-4b's OPTIONAL-only fix);
q016 = object-correlated OPTIONAL star (07 doc's open widening). Slow tail
classified: q008/q009/q032 operator-bound (H6 rollup materialization, now
visible post-PR-2), q012/q029 scan-bound. Cold subset re-measured: the
~1.6-2.1s/table OAuth+catalog floor is untouched — PR-8's exact target.

Memo recommendation: F9-formatter -> PR-6 rollup fused-agg -> PR-8 cold
floor -> q031+q016 (H3 family, P1-gated) -> PR-7+PR-5 as one cold-scoped
unit, decided on PR-8's numbers. Fixes q013 corpus bookkeeping (loud-refuse
expected, mirroring q034/q051). Supersedes the pre-restart 08 scaffold.
The restack merge onto the reviewed PR-4 head (4b2629f) brings the
ancestor per-PR rustfmt commits; this formats the PR-4b-authored
optional.rs hunks so cargo fmt --all --check is green at this HEAD.
… (post-restack lint)

redundant_closure_for_method_calls fires at -D on the restacked toolchain;
same idiom as the existing call at optional.rs:1584. Scoped clippy back
to 0 errors.
…ion-invariant doc, family switch, q013→F2

All three optional items from the #1487 review (the blocking own-delta fmt
was cleared by the restack's delta-fmt commit), plus the register note:

- Binding-VALUE assertion in the hermetic differential (recommended): the
  batched-vs-per-row comparison now asserts the per-required-row (row,
  binding) MULTISETS agree, not just row counts + miss sets — a batched
  path binding ?s to the wrong supplier IRI at the right cardinality now
  fails. Values compare over the vars the OPTIONAL side contributes; the
  correlation var is excluded because the two paths carry it differently
  BY DESIGN (per-row output includes the seeded var, batched partition
  batches don't re-project it — the partition row index pins the
  association, and the multiset compare keys on it, so wrong-row
  partitioning still fails). The first strengthened run surfaced exactly
  that schema asymmetry, which is why the comment spells it out.
- Fusion-invariant sentence on r2rml_leaf_is_hash_join_safe (doc
  completeness): consumed_filter/scan_filters need no exclusion because
  filter fusion only folds FILTERs whose operands are all produced by the
  scan (rewrite.rs::consume_scan_local_filters requires vars ⊆
  produced_vars) — the fused filter reads only values carried by the
  produced row, so it evaluates identically batched or per-row. If that
  operand rule ever loosens, this admission is where it breaks.
- batched_optional_r2rml_enabled routes through the family
  r2rml::env_switch_enabled helper (the restack brought it in-crate) —
  ends the third falsy-spelling variant; "no" now disables it like every
  other R2RML switch.
- Findings register: q013 cross-linked to F2 alongside q051 (entry +
  summary table) — same subquery root, DNF-masked until this stack made it
  runnable, expected_status bookkeeping in 09-stacked-rebaseline.md §4.

Gates: fluree-db-query 1264/1264 (nextest); scoped clippy 0; cargo fmt
--all --check green.
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