Observed (master b18d808 / #2117 tree, pandas, 20k nodes / 100k edges, warmed medians; local = direction only; from #2116 item 3c)
| query |
ms |
MATCH (a)-[e]->(b) RETURN b LIMIT 5 |
81.0 |
MATCH (a)-[e]->(b) RETURN b.id AS id LIMIT 5 |
69.0 |
native [n(), e_forward(), n()] |
3.6 |
The colleague's ~800 ms figure was the same shape at a larger graph.
Profile
76 of the 81 ms are inside graphistry/compute/gfql/row/pipeline.py::_gfql_connected_bindings_row_table (via rows(binding_ops=...)): 44 ms in _gfql_connected_bindings_state and 24 ms in _gfql_connected_bindings_row_frame_from_state — the full 100k-row a/e/b binding table is materialized (pandas merges, take, vstack) and only then does LIMIT 5 apply. Nothing is wrong; nothing is pushed down.
Shape of a fix (design item, not a one-liner)
Either a LIMIT pushdown into the binding-table builder for the unordered, unfiltered case (take the first k edges before the node gathers — valid only without ORDER BY/DISTINCT/aggregation), or a lazy binding table that materializes on demand. Either way: pin result identity against the current path (row set, and row order where ORDER BY is present), A/A control before any number is quoted, and profile the seeded form too so the pushdown does not regress the index path (#2117).
Related: #2128 fixed the other 3c item (the string-sniffer cost in cross-alias OR predicates).
🤖 Generated with Claude Code
Observed (master b18d808 / #2117 tree, pandas, 20k nodes / 100k edges, warmed medians; local = direction only; from #2116 item 3c)
MATCH (a)-[e]->(b) RETURN b LIMIT 5MATCH (a)-[e]->(b) RETURN b.id AS id LIMIT 5[n(), e_forward(), n()]The colleague's ~800 ms figure was the same shape at a larger graph.
Profile
76 of the 81 ms are inside
graphistry/compute/gfql/row/pipeline.py::_gfql_connected_bindings_row_table(viarows(binding_ops=...)): 44 ms in_gfql_connected_bindings_stateand 24 ms in_gfql_connected_bindings_row_frame_from_state— the full 100k-rowa/e/bbinding table is materialized (pandas merges,take,vstack) and only then doesLIMIT 5apply. Nothing is wrong; nothing is pushed down.Shape of a fix (design item, not a one-liner)
Either a LIMIT pushdown into the binding-table builder for the unordered, unfiltered case (take the first
kedges before the node gathers — valid only withoutORDER BY/DISTINCT/aggregation), or a lazy binding table that materializes on demand. Either way: pin result identity against the current path (row set, and row order where ORDER BY is present), A/A control before any number is quoted, and profile the seeded form too so the pushdown does not regress the index path (#2117).Related: #2128 fixed the other 3c item (the string-sniffer cost in cross-alias
ORpredicates).🤖 Generated with Claude Code