Repository navigation
Self-pointing Cypher pattern causes lance-graph to hang indefinitely for high cardinality datasets #111
Description
I've created a second benchmark to push lance-graph to its breaking point, and it breaks :)
This one uses the well-known LDBC SNB (SF1) dataset, and the query suite and results are here - should be quite easy to reproduce.
Issue
The query pattern that breaks is Q30 in the suite: "Are there comments replying to posts created by the same person?". Basically, a self-reference to a node variable in a query.
Attempt 1
Here's how it looks in Neo4j/Kuzu/Ladybug:
// Are there comments replying to posts created by the same person?
MATCH (c:Comment)-[:commentHasCreator]->(p:Person)<-[:postHasCreator]-(post:Post)<-[:replyOfPost]-(c)
RETURN COUNT(DISTINCT c.ID) > 0 AS has_self_reply;The above pattern doesn't work in lance-graph, because of #108: COUNT(DISTINCT x.id) isn't yet supported in lance-graph.
Attempt 2
I then tried just returning the distinct IDs to take them to an external system (e.g., Polars) to aggregate. Not ideal from a performance standpoint, as this requires materializing a lot of results.
But this doesn't work either.
Trying:
MATCH (c:Comment)-[:commentHasCreator]->(p:Person)<-[:postHasCreator]-(post:Post)<-[:replyOfPost]-(c)
RETURN DISTINCT c.id AS idGives:
Traceback (most recent call last):
File "/Users/prrao/code/graph-benchmark-ldbc-snb/lance_graph/query.py", line 685, in <module>
main(selected_queries)
~~~~^^^^^^^^^^^^^^^^^^
File "/Users/prrao/code/graph-benchmark-ldbc-snb/lance_graph/query.py", line 678, in main
func(cfg, datasets)
~~~~^^^^^^^^^^^^^^^
File "/Users/prrao/code/graph-benchmark-ldbc-snb/lance_graph/query.py", line 606, in run_query30
return _execute_distinct_count(
cfg,
...<5 lines>...
as_bool=True,
)
File "/Users/prrao/code/graph-benchmark-ldbc-snb/lance_graph/query.py", line 135, in _execute_distinct_count
result = execute_query(query, cfg, datasets)
File "/Users/prrao/code/graph-benchmark-ldbc-snb/lance_graph/query.py", line 108, in execute_query
result = cypher.with_config(cfg).execute(datasets)
ValueError: Query planning error: Failed to join source to relationship: Schema error: No field named posthascreator_1__dst. Valid fields are c__id, c__creationdate, c__locationip, c__browserused, c__content, c__length, comment.id, comment.creationdate, comment.locationip, comment.browserused, comment.content, comment.length.
Based on the error message, it seems that the query planner can't handle query patterns that point back to the same variable?
Attempt 3
Finally, I tried rewriting it another way, with an additional WHERE predicate that checks where c1 and c2, so as to force a different query plan.
// Are there comments replying to posts created by the same person?
MATCH (c1:Comment)-[:commentHasCreator]->(p:Person)<-[:postHasCreator]-(post:Post)<-[:replyOfPost]-(c2:Comment)
WHERE c1.id = c2.id
RETURN DISTINCT c1.id AS idThis doesn't work either: Due to extremely high cardinality in these intermediate paths, this query never returns (it just hangs).
As can be seen, this issue is multifold and has a more complex cause.
How to reproduce
The LDBC SNB SF1 dataset was used for this experiment. The dataset can be downloaded via the script provided here.
Once the dataset is downloaded locally, the code to build the graph in lance-graph and query it are available in the lance_graph directory of the project, here.
# Ingest dataset into in lance_graph
cd lance_graph
uv run build_graph.py
# Uncomment L649 of query.py and aim to run Q30 as follows
uv run query.py "30"
Activity
aheev commentedon Jan 29, 2026
Looking
prrao87 commentedon Jan 29, 2026
Yeah the concern is also that Ctrl + C in the terminal doesn't kill the running process to stop the query execution. Once it hangs, I have to open another terminal and use the kill -9 command to manually stop the Python process. Seems like a many-layered issue for sure.
aheev commentedon Jan 31, 2026
explain on Q30 gave me
So, OOM can be attributed to inefficient HashJoins which could be fixed as part of LanceNative Execution strategy
kuzu/ladybug uses factorized vectors and ASP joins hence the low memory footprint
aheev commentedon Jan 31, 2026
I will take a look at this
aheev commentedon Feb 3, 2026
Sorry for the delay. Got occupied with something else. I am done with the changes with e2e/integration tests pending. Will make a PR by tmrw
ChunxuTang commentedon Feb 6, 2026
@prrao87 Thanks for bringing up the issue.
@aheev Thanks for raising a PR to help fix it.
Let me think more about the issue and reply in this thread afterwards. At my first glance, the issue actually involves fundamental design options.
ChunxuTang commentedon Feb 7, 2026
Hi @prrao87 @aheev , thanks for your patience. After investigation, let me share my thoughts in detail:
The target natural-language question in GraphRAG is:
“Are there comments replying to posts created by the same person?”
The question is actually a relational query in nature (checking whether two foreign keys are equal,
comment.creator_id == post.creator_id), rather than a traversal-based graph query (finding a circular path). Since lance-graph is designed to target SQL execution engines and columnar storage optimizations in lance, we should prefer relational-type queries (but still in the Cypher format) over self-reference graph queries.To put it in another way, we should reject traversal-heavy queries such as (your attempt 1)
and instead favor JOIN-friendly queries like
Note that the two queries are semantically the same, but they would lead to very different query plans in lance-graph.
At this moment, the JOIN-friendly query also fails due to a duplicate-column issue (shared variables), which we need to resolve so we can run the query 30 in the LDBC benchmark.
@beinan: I think @prrao87 @aheev 's work also indicated that we may need a
query rewritercomponent in the long-term planning, to help users convert queries into lance-graph-friendly queries (instead of rejecting the queries). What do you think?prrao87 commentedon Feb 7, 2026
Thanks @ChunxuTang, makes sense. If the query rewriter is a longer term task, then in the interim, it's still better to outright reject (and inform the user) rather than have a generic error message that doesn't help the user rewrite it in a join-friendly way. Hope that makes sense.
aheev commentedon Feb 7, 2026
How exactly 🤔
+1 to this
ChunxuTang commentedon Feb 8, 2026
Yes, it makes sense to have a more user-friendly error for the self-reference graph queries. Let me create an issue to track that.
ChunxuTang commentedon Feb 8, 2026
The first query
will generate a sequence of
Expandoperations based on the self-reference.By contrast, the second query
will generate two
Expandoperations, then aJOINoperation on the comment id and creator id.