Skip to content

perf(gfql): secondary node property index is consulted only at the connected-bindings seam — single-alias seeded lookups still full-scan #1780

Description

@lmeyerov

Summary

The secondary node property index landed in #1777 works, but it is reachable from exactly one query shape. Every other seeded shape builds the index, keeps it resident and valid, and then never consults it — falling back to a full scan of the node frame.

Measured on official LDBC SNB SF1 (pandas, master 84be35fb, 4 runs/arm, position-balanced ABBA+BAAB), property index ON vs OFF:

query OFF ms ON ms speedup verdict
IS1 seed-lookup 725.6 759.6 0.96x built, never engaged
IS2 expand-order-limit 826.0 842.1 0.98x built, never engaged
IS4 message-content 227.9 231.8 0.98x built, never engaged
IS5 message-creator 549.3 580.1 0.95x built, never engaged
IS6 message-forum 4286.6 4633.1 0.93x built, never engaged
IS7 message-replies 71.7 19.5 3.68x engaged

Non-IS7 deltas (0.5-7%) sit inside per-arm run-to-run spread (1.4-16.4%), i.e. no effect. The IS7 arms do not overlap (ON 18.6-20.0, OFF 69.8-73.0).

Root cause

The property index is consulted at one seam only: connected_bindings — multi-alias rows() over a connected fixed-hop path (_seed_rows_via_property_index). IS7 is the only interactive-short query with that shape.

  • IS1 / IS2 / IS4 / IS5 use single-alias rows(source=...) or a node-only lookup and never reach the seam at all (instrumented node_prop_seam_probes: 0).
  • IS6 reaches the seam and is declined unsupported_shape.

The single-alias seeded path (chain_fast_paths._resident_seed_indexes, feeding the destination_return seam) requires the seed predicate to be on the node-id binding. When the seed is on any other property it full-scans the node frame, without ever asking whether a resident property index covers that column.

That is the same degeneracy #1777 was filed to fix — SNB binds nodes on __pgbench_node_key__ while every seed predicate is on the id property — but it was only fixed for the connected-bindings shape.

Isolated reproduction

Same one-hop query at SF1 node counts, differing only in binding shape:

shape without index with index property-index calls
multi-alias (connected bindings) 737.8 ms 15.1 ms engaged
single-alias (rows(source=...)) 682.6 ms 639.2 ms 0

Why it matters

This is the difference between #1777 accelerating one query and accelerating five. IS1/IS2/IS4/IS5 are all seeded point/short lookups — exactly the workload class the index exists for, and exactly the axis where we are weakest against indexed graph databases.

Suggested direction

Have the single-alias seeded path consult the resident property index before falling back to a scan, reusing #1777's existing lookup and cost gate rather than adding a second mechanism. Engagement must stay structural — driven by predicate/dtype/index preconditions, never by query or schema recognition — and must keep the existing decline-with-reason behaviour so a non-engaging shape is still explainable via index_trace().

Note on verification

index_trace() alone is not sufficient to prove engagement here: it reports the traversal seam's verdict, not where the seed rows came from. Confirming this required a pass-through counter on _seed_rows_via_property_index alongside the trace. Any fix should land with an engagement signal that is directly observable, otherwise 'built' will keep being mistaken for 'used'.

Found while re-baselining the LDBC lane after #1776/#1777 merged. Follows #1777.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions