Bluetape4k Graph Part 3: Graph I/O and Benchmarks

Graph data often needs to leave the database: migration, snapshots, analytics, and reproducible test fixtures all need
export and import paths. At some point a team asks, “Can we dump this graph and reproduce the case locally?” That is
where console copy-paste stops being cute. graph-io splits the work into file formats and I/O decorators.

| Format | Good fit |
|---|---|
| CSV | Small fixtures and human inspection |
| Jackson2 NDJSON | Compatibility with existing Jackson2 services |
| Jackson3 NDJSON | New line-delimited service interchange |
| GraphML | Interoperability with graph tools |
OkIO is not a format. bluetape4k-io and bluetape4k-okio provide buffering, compression, encryption, and async I/O
paths as decorators around CSV, NDJSON, or GraphML. Fastjson2 would fit the NDJSON family as a future serializer path,
but the current graph-io source provides Jackson2 and Jackson3 modules. Keeping file format and I/O path separate pays
off when a later requirement adds compression or encryption without changing the graph format itself.
Benchmark implementation
Section titled “Benchmark implementation”The quick-run benchmark comes from BulkGraphIoBenchmark and BulkGraphIoBenchmarkState. The state creates a temporary
directory and a TinkerGraph in-memory graph. The small dataset has 1,000 Person vertices and 2,000 KNOWS edges with a
fixed random seed. Benchmark methods export that graph to CSV/NDJSON/GraphML files or import the files back into
TinkerGraph.
The run used Mac mini Pro M4, 48GB memory, and Java 25. This Graph I/O quick-run itself does not start a graph DB Docker container because it uses TinkerGraph and temporary files. Graph DB comparison benchmarks and integration tests use Docker/Testcontainers.
./gradlew :graph-io-benchmark:smokeBenchmarkBenchmark results
Section titled “Benchmark results”The current quick-run uses @Fork(0), one warmup, and two measurements. It is useful for development decisions, not as a
precise published performance claim. JMH mode is AverageTime, unit is ms/op, and lower is better. This smoke pass
uses mean values; run a longer raw-JSON benchmark when variance matters. If the numbers look too tidy, that is usually a
good reason to run them again.

Representative sync means: CSV export 1.017 ms/op, Jackson2 NDJSON export 1.194 ms/op, Jackson3 NDJSON export 1.275 ms/op, GraphML export 2.582 ms/op, CSV import 17.854 ms/op, Jackson2 NDJSON import 18.831 ms/op, Jackson3 NDJSON import 19.852 ms/op, and GraphML import 21.111 ms/op.
GraphML was especially sensitive to factory caching and buffered streams: before the fix, export could hit roughly 413 ms; after the fix, it dropped to roughly 2.5 ms. That is why the harness matters as much as the chart. Sometimes the villain is not the format; it is a very ordinary factory allocation sitting in the hot path.
For fixtures, start with CSV or NDJSON. CSV is easy when a human needs to open the file and inspect vertices and edges. NDJSON is a better fit for service interchange and snapshot replay. GraphML is the interoperability option when graph tools need a standard XML format.
Sources
Section titled “Sources”- graph-io core README
- CSV graph I/O
- Jackson2 graph I/O
- Jackson3 graph I/O
- GraphML graph I/O
- OkIO graph I/O
- BulkGraphIoBenchmark
- BulkGraphIoBenchmarkState
- Graph I/O benchmark result
Series
Section titled “Series”- Part 1: Database Selection Map
- Part 2: Core API and Execution Models
- Part 3: Graph I/O and Benchmarks
- Part 4: Workshop Scenarios and Service Integration
- Part 5: Reading the Virtual Threads Benchmark
Comments
Leave a note or reaction with your GitHub account.