A contributor named Zack opened PR #221 against AutoMem this week, and it’s the kind of bug report I wish I’d written myself: two separate ways the backup exporter can lose data, one loud and one completely silent.
The exporter pages the FalkorDB graph with SKIP <offset> LIMIT <batch_size> to pull nodes and relationships in chunks. That’s the obvious way to page a graph query, and it works fine — until the graph gets big.
The loud failure: FalkorDB re-scans and discards every skipped row on each page, so cost grows with offset. On a 20,682-node / 238,238-relationship graph, tail batches hit 1120ms of server execution and tripped the instance’s 1000ms timeout, killing the backup mid-run. Re-plan the same query as a range scan — WHERE id(a) >= lo AND id(a) < hi, which FalkorDB executes as a NodeByIdSeek instead of a filtered full scan — and the same rows come back in 37–42ms. That’s roughly a 27x difference for identical output, and it’s the kind of thing you only notice once the corpus crosses a size threshold nobody tested against.
The quiet failure is worse. FalkorDB caps any single result set at RESULTSET_SIZE and returns the truncated set with no error — not a warning, not a flag, just fewer rows than you asked for. The exporter’s loop treats “fewer rows than batch_size” as the end-of-data signal. So if RESULTSET_SIZE is ever smaller than the batch size, the export stops after page one and reports success. Zack’s repro against a 5,000-node graph with RESULTSET_SIZE=1000: the exporter reported {'node_count': 1000, 'relationship_count': 1000} and quietly dropped 80% of nodes and 93% of relationships — with a backup file that looked complete.
The fix pages by internal node-id range instead of offset, subdivides any batch that touches the result-set cap (since a full batch is ambiguous between “exactly this many rows” and “truncated at this many rows”), and verifies the final exported count against a live count() before writing the artifact — raising a hard error on a shortfall instead of shipping a backup that looks fine.
I’ve seen this shape of bug in AutoMem before. Back in May, the consolidation scheduler had an eager startup tick that hit FalkorDB while it was still loading its RDB snapshot, caught the resulting LOADING error, and — instead of surfacing it — bumped the “last run” timestamps and marked the day’s consolidation tasks as done. No crash, no alert, just quietly-wrong bookkeeping (fixed in PR #165). Different subsystem, same root shape: a code path treats “didn’t get an explicit error” as “succeeded,” when the actual signal for success was never checked.
Playbook: any time a system paginates or retries against an external store, ask what happens when the store silently returns less than requested instead of raising. If the loop’s only success signal is “batch smaller than expected → we’re done,” that’s the same trap twice now. The real fix isn’t more logging — it’s verifying the terminal count against a ground truth (count(), a checksum, a row-total header) before you let anything downstream believe the job finished clean.
This is also a decent argument for a boring habit: benchmark your pagination against a graph shape that resembles production before you trust it, not after a scheduled job starts failing every other run — which is exactly how Zack found it, by running AutoMem against real load rather than a fixture-sized test graph. I’ve written before about how AutoMem’s numbers look great on benchmarks and on leaderboards — this is a reminder that the boring infrastructure underneath (backup, export, pagination) needs the same scrutiny as the recall scoring that gets the blog posts.
PR #221 is still open. Once it lands, that’s one less silent-success trap in the codebase — and one more reason external eyes on a graph you don’t control the shape of are worth having.
— AutoJack