Ingesting Massive DB with 600 Million Vertices and 14 Billion EdgesContext & BackgroundI am in the process of ingesting a massive graph dataset containing over 600 Million vertices (663M actual count) and billions of relationships/edges (scaling towards ~14 Billion total connections) into a graph database. I am posting this to share my hardware setup, data formats, 4-phase ingestion architecture, performance benchmarks, and real-world technical hurdles—and to ask for community feedback on how to optimize and strategize high-throughput graph ingestion at this scale. Hardware & Configuration Setup
Source Data FormatThe dataset snapshot consists of raw compressed
4-Phase Ingestion ArchitectureTo bypass expensive database index lookups during edge insertion, I designed a 4-Phase Ingestion Pipeline utilizing an external key-value store to map natural string IDs to internal database record pointers. Phase 1: Vertex Ingestion
Phase 2: Building the External ID-to-Record Pointer Map
Phase 3: Deduplication & Unique Index Creation
Phase 4: Direct Edge Ingestion
Issues & Challenges Encountered
Please AdviseDuring my scale testing, I found myself caught in a clear technical trade-off:
I am seeking advice and perspectives from practitioners on the optimal database configuration and ingestion strategy to resolve this trade-off when loading multi-billion-edge graphs:
Any insights, configuration recommendations, or strategy suggestions would be greatly appreciated! |
Replies: 1 comment 1 reply
|
Thanks for one of the most detailed reports we have had. I went through the code paths your pipeline exercises, and the headline is that the trade-off you describe is not real: both horns are the same root cause, and neither is on the batch-size axis. Four issues came out of it. What is actually happeningHeap growth is proportional to stream length, not to batch size. Your Your single-writer fallback was right for a reason you could not have known. Two concurrent What to change, in order of impact1. Declare the property-free edge types CREATE EDGE TYPE Cites LIGHTWEIGHTRight now you are creating 14 billion edge records you do not need, which is hundreds of GB of disk and a large share of your write time. Use a build that includes the recent lightweight-edge identity work (on Two conditions, per type. The edges carry no properties, now enforced with a clear error rather than silently promoted to a record. And parallel edges between the same pair are not meaningful, since identity is the triple (type, out, in). Citations and category associations are naturally at most one per pair; anything that needs a date range or a weight stays a regular type. The declaration is per type, so you can mix. Two notes for your pipeline specifically. Since you retry and recycle streams, budget for a 2. Move the vertex phase onto the same 3. Give the page cache real memory. 4. Edge-connect parallelism is capped by bucket count, not by cores. Work is dispatched as 5. Keep recycling the stream until #5664 lands. 4M records is a reasonable cadence. One knob to not reach for: One thing to plan for that you have not hit yetA graph loaded entirely through the bulk path never gets the striped super-node layout, so your category and affiliation hubs end up as chains of thousands of 8 KB segments and traverse badly, after a load that looked completely successful. That is #5667. Worth knowing before you commit to the full 14B run rather than after. On the payload truncationThe gRPC server allows 100 MB by default ( FinallyAt 14B edges the wire protocol itself is a real tax. Running the loader embedded in a JVM against the engine directly removes the protobuf conversion, the per-stream maps and the message-size limits entirely. There is also a declarative Please keep posting numbers as you go, this is exactly the scale where the interesting problems live. |
Thanks for one of the most detailed reports we have had. I went through the code paths your pipeline exercises, and the headline is that the trade-off you describe is not real: both horns are the same root cause, and neither is on the batch-size axis. Four issues came out of it.
What is actually happening
Heap growth is proportional to stream length, not to batch size.
GraphBatchkeeps two caches that map every distinct vertex an edge touches, and neither is cleared untilclose(). At roughly 80-90 bytes per entry in each of the two maps, a stream that touches 100M distinct vertices holds 16-18 GB before anything else. The deferred incoming-edge buffer adds 36 bytes per edge and is drained…