#1592·graphiti

FalkorDB: edge_fulltext_search Node-By-Label-Scan per hit (redundant endpoint labels defeat the edge-uuid index) → add_episode times out at scale

Author: mbs-vhsCreated Jun 17, 2026Updated Sep 10, 2026

Summary

On the FalkorDB backend, edge_fulltext_search emits:

cypher
CALL db.idx.fulltext.queryRelationships('RELATES_TO', $query) YIELD relationship AS rel, score
MATCH (n:Entity)-[e:RELATES_TO {uuid: rel.uuid}]->(m:Entity)
...

FalkorDB's planner does not recognize that pinning the edge by its indexed uuid already determines the endpoints, so the (n:Entity) / (m:Entity) labels trigger a Node By Label Scan over all :Entity nodes — once per fulltext hit. With a broad fulltext query that matches many edges this is O(hits × entities), and add_episode fails with Query timed out as the graph grows.

Evidence (GRAPH.EXPLAIN, ~3,300 RELATES_TO edges / ~3,100 Entity nodes)

With the endpoint labels:

Limit → Sort → Project → Filter
        → Edge By Index Scan | [e:RELATES_TO]
            → Node By Label Scan | (n:Entity)     ← scans every Entity, per hit

Without the endpoint labels (MATCH (n)-[e:RELATES_TO {uuid: rel.uuid}]->(m)):

Limit → Sort → Project → Edge By Index Scan | [e:RELATES_TO]

Measured on the live graph: 25s+ timeout with labels → 0.007s without. Dropping the labels takes add_episode from 1/20 episodes persisting to 20/20.

Why it's FalkorDB-specific

graphiti is Neo4j-first and the FalkorDB path reuses the identical Cypher — the labelled MATCH is byte-identical in graphiti_core/search/search_utils.py, graphiti_core/driver/falkordb/operations/search_ops.py, and graphiti_core/driver/neo4j/operations/search_ops.py. Neo4j's planner uses the relationship index and skips the redundant label check; FalkorDB's does not. The endpoint labels are semantically redundant — a RELATES_TO edge always connects two Entity nodes.

Note: on FalkorDB the FalkorDriver leaves search_interface = None, so the actual path add_episode runs is the module-level search_utils.edge_fulltext_search fallback (the FalkorSearchOperations.edge_fulltext_search class method is not on the call path).

Proposed fix

Drop the redundant endpoint labels in the FalkorDB edge fulltext path:

diff
- MATCH (n:Entity)-[e:RELATES_TO {uuid: rel.uuid}]->(m:Entity)
+ MATCH (n)-[e:RELATES_TO {uuid: rel.uuid}]->(m)

at search_utils.edge_fulltext_search (primary, FalkorDB call path) and the FalkorSearchOperations class-method copy. The Neo4j path can keep the labels.

Smaller, separate item

_build_falkor_fulltext_query does not escape group_id, so a non-alphanumeric id (the default '_', or an id containing :) yields an invalid (@group_id:"...") RediSearch filter → Syntax error near group_id. v0.29.2's _escape_fulltext_group_id fixes that — but it does not touch the endpoint-label issue above (the edge_fulltext_search Cypher is unchanged between 0.29.0 and 0.29.2).

Environment

  • graphiti-core 0.29.0 (also verified the Cypher is unchanged in 0.29.2)
  • FalkorDB v4.18.9