Skip to content

perf(neo4j): stream Cypher snapshots to disk - #120

Merged
rahlk merged 1 commit into
codellm-devkit:mainfrom
MikeeI:fix/issue-001
Sep 2, 2026
Merged

perf(neo4j): stream Cypher snapshots to disk#120
rahlk merged 1 commit into
codellm-devkit:mainfrom
MikeeI:fix/issue-001

Conversation

@MikeeI

@MikeeI MikeeI commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Stream Cypher statement blocks directly to file instead of joining the complete snapshot in memory.
  • Keep renderCypher as a compatibility API backed by the same ordered block iterator.

Evidence

  • Self-analysis projected 58,447 rows into 14,419,011 bytes of Cypher.
  • Five streamed writes took 121.9–147.6 ms and matched compatibility rendering byte for byte.

Validation

  • bun test test/neo4j-schema.test.ts test/neo4j-edge-identity.test.ts
  • bun run typecheck
  • Exact 14,419,011-byte comparison between compatibility rendering and streamed file output.

Prior art

Disclosure

Investigated thoroughly with GPT-5.6 (extra high reasoning effort), using Oh My Pi as the agent framework.
This report is not generic or unreviewed AI-generated output.
Its claims were checked against the cited evidence, and it includes the relevant detail intended to help maintainers resolve the issue.
If reports like this are not useful to the project, please let me know and I will refrain from submitting similar ones.
My intent is to help without wasting maintainer time or energy or discouraging their work.
Thank you for your work.

@rahlk
rahlk self-requested a review September 2, 2026 13:18
@rahlk rahlk self-assigned this Sep 2, 2026
@rahlk

rahlk commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Thanks for this — taking it.

The snapshot writer was the one path still joining its whole output in memory. I fixed the same ceiling for analysis.json in #137 and left graph.cypher untouched, so this closes the other half.

I reviewed the diff carefully and verified it rather than taking the description on trust:

Review. node:fs only, no new dependency. The file handle is opened, written and closed in a finally, so it will not leak on a throw. The path still comes from the caller as path.join(dir, "graph.cypher") — unchanged. No network calls, no process spawning, no dynamic evaluation. The generator conversion is mechanical (out.pushyield, return blocksyield*) and the emitted Cypher is character-for-character the same; only the template-literal indentation moved. The local batches() duplicates the imported chunk(), which is harmless — worth collapsing to one helper eventually, not worth holding this up.

Verified locally at scale, since the test in the PR uses a small fixture. Projecting Apache Superset's frontend (124,866 nodes / 314,675 edges) and comparing the streamed file against the compatibility renderer:

bytes: streamed=123,211,141 legacy=123,211,141
BYTE-IDENTICAL

Full suite passes on the branch: 244 pass, 0 fail.

Keeping renderCypher as a compatibility API backed by the same block iterator was the right call — it is what makes that byte-for-byte comparison possible as a test.

@rahlk
rahlk merged commit cf87a40 into codellm-devkit:main Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants