Skip to content

fix(planner): credit unindexed equality filters in start-point selection - #209

Merged
knoguchi merged 2 commits into
mainfrom
fix/anchor-equality-selectivity
Aug 26, 2026
Merged

fix(planner): credit unindexed equality filters in start-point selection#209
knoguchi merged 2 commits into
mainfrom
fix/anchor-equality-selectivity

Conversation

@knoguchi

Copy link
Copy Markdown
Owner

Fixes #208 — see the issue for the measured symptom and the full pricing arithmetic.

What

EndpointCost's single rows splits into:

  • scan_rows — what the leaf physically visits: the index-match count when a seek fuses, else the full label count (an unindexed filter never shrinks a scan);
  • out_rows — what the leaf emits into the Expand: exact for a seek; an unindexed literal equality now gets a default 1/10 selectivity (UNINDEXED_EQ_SELECTIVITY_DIVISOR, System R's classic no-statistics default).

anchor_cost prices the scan term with scan_rows (+ the per-row filter multiplier as before) and prorates the edge walk with out_rows. Keeping the scan term full is what preserves the old model's correct cases: a low-selectivity equality against a tiny far endpoint still reverses, and the CONTAINS shape from the model's original motivating benchmark is untouched (non-equality filters keep selectivity 1).

Measured (LDBC-style SF 0.1, no indexes)

query shape before after
IS5 point-equality + 1 hop 33.9ms 5.58ms (parity with the hop-less IS4)
IC2 equality start, unlabeled far end 650ms 9.6ms (equal to its indexed time; AllNodesScan anchor gone)
all other suite rows unchanged within noise

Verification

  • 2 new regression tests in the planner test module reproducing both shapes at 1/100 scale (plus the existing reversal tests all passing — the filtered-start/small-edge-population and indexed-far-equality cases guard the non-regression side)
  • cargo test --workspace green; SF 0.1 --ignored workload test green; TCK 3880/3880 unchanged; fmt + both clippy configs clean

An unindexed literal-equality conjunct made its endpoint price *worse*
in plan_reversed_pattern's model: it left the row estimate at the full
label count and set `filtered`, doubling the scan term with no
selectivity credit. On the LDBC-style SF 0.1 workload without indexes,
`MATCH (m:Post {id: 100})-[:HAS_CREATOR]->(p:Person)` anchored at
Person and walked all 15k HAS_CREATOR edges (33.9ms vs 5.6ms for the
same scan without the hop), and a variant with an unlabeled far
endpoint anchored an AllNodesScan (650ms). Issue #208 has the full
pricing arithmetic.

Split EndpointCost's single `rows` into `scan_rows` (physical: index
count for a seek, else the full label — an unindexed filter never
shrinks the scan) and `out_rows` (emitted into the Expand: exact for a
seek; an unindexed literal equality now gets System R's classic 1/10
default selectivity, UNINDEXED_EQ_SELECTIVITY_DIVISOR). The scan term
prices scan_rows, the edge-walk proration uses out_rows. Keeping the
scan term full is what preserves the cases the old model got right: a
low-selectivity equality against a tiny far endpoint still reverses,
and the CONTAINS shape from the model's original motivating benchmark
is untouched (non-equality filters keep selectivity 1 — genuinely
unknowable).

Measured after (same workload, no indexes): 33.9ms -> 5.58ms (6.1x, now
at parity with the hop-less twin) and 650ms -> 9.6ms (68x, equal to its
indexed time). All other suite rows unchanged within noise.

TCK: 3880/3880, unchanged. Fixes #208.
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…h section

The table's indexed numbers are unaffected by #209 (verified against
criterion baselines); record the unindexed before/after where the
workload's numbers live.
@knoguchi
knoguchi merged commit 759c686 into main Aug 26, 2026
12 of 13 checks passed
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.

planner: unindexed equality filters make anchor endpoints price worse, inverting start-point selection (measured 6-70x)

2 participants