Skip to content

Bluetape4k Graph Part 3: Graph I/O and Benchmarks

Robotic builders moving CSV, NDJSON, GraphML, and OkIO files through a graph import/export conveyor on a 3D workbench
Bulk I/O supports migration, reproducible tests, and repeatable analysis.

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.

Graph I/O pipeline from GraphOperations to graph-io-core, data formats, I/O decorators, operational uses, and benchmark gate
Format adapters separate graph operations from file shape; OkIO composes the I/O path.
FormatGood fit
CSVSmall fixtures and human inspection
Jackson2 NDJSONCompatibility with existing Jackson2 services
Jackson3 NDJSONNew line-delimited service interchange
GraphMLInteroperability 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.

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.

Terminal window
./gradlew :graph-io-benchmark:smokeBenchmark

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.

Graph I/O quick-run benchmark chart in milliseconds per operation
GraphML factory caching changed results by two orders of magnitude, so benchmark context matters.

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.

Comments

Leave a note or reaction with your GitHub account.