Skip to content

Add a Neo4j plugin, and fix what syncing 157k nodes found in the merger - #59

Merged
JetSquirrel merged 2 commits into
mainfrom
feat/neo4j-plugin
Nov 5, 2026
Merged

JetSquirrel merged 2 commits into
mainfrom
feat/neo4j-plugin

Conversation

@JetSquirrel

Copy link
Copy Markdown
Owner

Adds a built-in neo4j plugin that copies a Neo4j database into a graph over its HTTP API, using read-only queries. Its main use is Cartography, but it knows nothing about Cartography's labels.

Testing it at scale found problems in the merger, which every plugin goes through, so this also changes how all syncs write.

Merger changes

Problem at 157k nodes Now
A sync with nothing changed took 1725 s and grew the graph Nodes store khonsu_hash, and unchanged ones aren't written
Updating a node with MERGE sometimes added a second node with the same key Known nodes are updated by internal ID
Deleting from a label over 100k rows failed on max_mutation_affected_rows, leaving the delete half done Deletes go by internal ID
Adding a source again doubled its relationships, because schema statistics return no patterns on large graphs Without patterns, relationships are read by type
The first edge read after a large write took 14 min A write of 10,000 records or more checkpoints; a failure there only warns

Visible changes:

  • The sync report gains unchanged_nodes.
  • nodes_written now counts only nodes actually written.
  • Nodes gain a khonsu_hash property. It stays out of saved investigations, so their fingerprints don't change.
  • khonsu_synced_at on a node can now be older than the last sync that saw it, as was already true of relationships.

Scale test

Release build, Neo4j in Docker on the same Windows desktop, 157,003 nodes and 247,502 relationships. Node and relationship counts matched Neo4j after every sync.

Step Time Graph on disk
First sync 132 s 919 MB
Nothing changed 32 s 919 MB
One node changed (6 times) 32–35 s 919 MB
One node deleted 35 s 919 MB
One relationship changed 37 s 919 MB
Five nodes changed label 41 s 919 MB
Source added again 43 s, nothing written 919 MB
Three-hop query 326 ms

Not verified

  • Real Cartography output. The test graph follows Cartography's AWS model, but Cartography didn't produce it. This is the biggest gap.
  • The cause of the duplicate MERGE. It happened twice in the 157k run, but a 100k probe without Neo4j didn't reproduce it, and it hasn't been reported to HawDB. Updates no longer go through MERGE, and six consecutive updates produced no duplicate, but new nodes still use it.
  • Upgrading a large existing graph. The first sync rewrites every node to add its hash. This was only exercised on small graphs.
  • Other plugins at scale. CSV and AWS only ran through the automated tests.
  • The 10,000-record checkpoint threshold. It's a judgment call, not measured below 157k.
  • Disk and file growth over time.
    • The graph grew from 239 MB to 919 MB on its first checkpoint, and old artifacts weren't reclaimed in these runs.
    • Each sync that changed something added about 4–6 files, from 170 to 213 over the run.
  • Packaged builds, and the source form in the desktop window. The release package wasn't built, and I didn't confirm the Neo4j source form in the window.
  • Larger graphs. Nothing above 157k was measured.

Tests

cargo test --workspace: 385 passed, 1 ignored. cargo clippy --locked --workspace --all-targets -- -D warnings passes. Only documentation changed after these runs.

🤖 Generated with Claude Code

JetSquirrel and others added 2 commits November 5, 2026 10:26
Syncing a Neo4j copy of 157k nodes found problems in the merger that every plugin
goes through:
- A sync with nothing changed took 29 minutes and grew the graph. Nodes now store
  khonsu_hash, like relationships, and an unchanged one is not written again.
- Updating an existing node with MERGE sometimes added a second node with the same
  key. Known nodes are now updated by internal ID. We haven't found what triggers
  the MERGE duplicate, and new nodes still use it.
- Deleting from a label over 100k rows failed on max_mutation_affected_rows, leaving
  the delete half done. Deletes now go by internal ID.
- Adding a source again doubled its relationships, because schema statistics return
  no patterns on large graphs. Without patterns, relationships are read by type.
- After a large write, the next edge read took 14 minutes with no checkpoint. A write
  of 10,000 records or more now checkpoints. A failure there only warns.

khonsu_hash stays out of saved investigations, so their fingerprints don't change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copies the nodes and relationships of a Neo4j database into a graph over its HTTP
API, with read-only queries in read access mode. Its main use is Cartography, but it
knows nothing about Cartography's labels.

Tested on Neo4j 5.26 Community with a graph built to follow Cartography's AWS model,
not on real Cartography output. The README lists what comes across differently and
how to rewrite queries written for Neo4j.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JetSquirrel
JetSquirrel merged commit 85dbd92 into main Nov 5, 2026
2 checks passed
@JetSquirrel
JetSquirrel deleted the feat/neo4j-plugin branch November 5, 2026 17:02
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.

1 participant