Algorithms on billion-scale graph using 10GB RAM: I love DataFusion

Jul 31, 2026 10:53 PM - 1 month ago 647

TLDR;

I implemented a chart map-reduce utilizing Apache DataFusion. Where possible, I offloaded everything to disk, and designed the algorithms to trust connected bulk scans alternatively than random access. DataFusion handles spillover, sort-merge joins, aggregations, readying and execution, truthful my codification is very lightweight. I tested it successful strict mode by moving it via systemd-run pinch a difficult representation limit. It works. Of course, I person encountered immoderate issues: for example, I often acquisition deadlocks from FairSpillPool successful utmost scenarios, and I person not yet recovered a measurement to make SMJ usage pre-sorting of the information connected disk. But it works. I tin compute PageRank connected a directed chart pinch 1 cardinal edges (graph500-26 from the Graphalytics dataset) utilizing 5 GB of memory. Alternatively, I tin place each the weakly connected components successful a chart pinch 2 cardinal edges (twitter_mpi from the aforesaid dataset collection) utilizing 10 GB of memory. Neither NetworkX nor Igraph tin do this; astir existing chart algorithms require the chart to fresh into memory. Previously, I thought you needed Apache Spark and GraphFrames for billion-scale chart analytics. Now, however, I deliberation each you request is simply a laptop. I person wholly changed my aged opinion astir utilizing Apache DataFusion for chart analytics.

Setup

I tested 2 tasks.

PageRank

What is PageRank?

The task is to compute PageRank connected graph500-26 from Graphalytics dataset:

KeyValue
Num nodes32,804,978
Num edges1,051,922,853
DirectedFalse
Memory Limit5 GB
DataFusion Pool Size4 GB

PageRank is 1 of the astir celebrated chart centrality algorithm and is utilized from hunt results ranking to anti-fraud scoring. My DataFusion implementation is classical Pregel: bulk-synchronous parallel algorithm (aka Map-Reduce) which I expressed utilizing joins and aggregate. Very akin to what is successful the halfway of Spark's GraphFrames library.

Weakly Connected Components

What are Weakly Components?

The task is to place each the weakly connected components connected twitter_mpi from the aforesaid dataset:

KeyValue
Num nodes52,579,682
Num edges1,963,263,821
DirectedTrue
Memory Limit10 GB
DataFusion Pool Size8 GB

WCC is the halfway portion of immoderate personality (entity) solution problem. For example, erstwhile you request to do information deducplication from different strategy done transitive IDs you extremity up pinch WCC problem. My DataFusion implementation is based connected the "In-database connected constituent analysis", Bögeholz et al., arXiv 1802.09478. I already implemented the aforesaid algorithm for the Spark's GraphFrames truthful it was an evident choice.

Results

PageRank

An easy part. I utilized SMJ conscionable to proove the scalability but it is besides imaginable to usage HJ because vertices are mini (32M) and PageRank authorities is trivial: 1 file rank (f64), 1 file out-degree (i64), connected information emblem (bool). With HJ it is faster. PageRank useful complete directed edges truthful it deos not require to symmetrize the graph. Just offload edges to disk and iterate by updating authorities (and offload to disk arsenic good to break the lineage) until converged.

The compute clip is long: astir 30 minutes for 15 afloat iterations. But the setup is astir memory, not speed. Give it immoderate much realistic numbers for 1B chart analytics and it will activity accelerated capable (I tested). I checked numbers against the crushed truth: 100% lucifer (with 0.0001 tolerance). A batch of optimizations tin beryllium done present arsenic well: successful mentation it is imaginable to bucket edges by scope aliases do benignant of range-partitioing, truthful the SMJ does not request to benignant again the biggest join-side (edges) connected each loop to get triplets. As good I'm not 100% parquet is the champion prime here. Also would beryllium absorbing to effort to fuse join+agg: each Pregel iterarion is for illustration edges <-[join] nodes-state -> group by + agg -> [join] -> nodes-state -> update nodes-state. If I tin fuse together the first 2 stages it tin beryllium a immense triumph from the capacity constituent of view. Meanwhile I do not cognize yet really to do it successful DataFusion: a batch of point to learn.

WCC

The hardest part. 2B edges twitter chart is already immense (its edges are 30 GB successful CSV !!!). But for WCC we request to symmetrize edges (or do a national betwixt src, dst and dst AS src, src AS dst + chopped connected top), truthful astatine the highest we are crunching almost 4 cardinal of edges utilizing only 8GB DataFusion pool. After the travel survives the first fewer iteration, contraction process trim the magnitude of edges dramtically and algorithm ends successful 10 minutes pinch debased representation pressure.

sem@fedora:~/github/graphframes-rs$ systemd-run --user --scope \ -p MemoryMax=10G -p MemorySwapMax=0 \ -p AllowedCPUs=0-1 \ --setenv=RUST_LOG=graphframes_rs=info,datafusion=warn \ ./target/release/run-algorithm twitter_mpi-v.parquet twitter_mpi-e.parquet wcc 42 file:///var/home/sem/Downloads/gf_wcc_out 8G 2 Running arsenic unit: run-p316509-i284528.scope; invocation ID: 742f9296d31d426580b7ec8213422cf1 [2026-07-05T05:37:21Z INFO graphframes_rs::algorithm::connectivity::connected_components] commencement WCC pinch run-id 017c0a23-2b20-4ffa-ac6b-6e2cb8d7203e [2026-07-05T05:52:21Z INFO graphframes_rs::algorithm::connectivity::connected_components] aft mentation chart has 3228212374 edges [2026-07-05T06:13:21Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc guardant loop 1, edges remaining: 840238268 [2026-07-05T06:17:39Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc guardant loop 2, edges remaining: 77322906 [2026-07-05T06:17:57Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc guardant loop 3, edges remaining: 5624128 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc guardant loop 4, edges remaining: 1075998 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc guardant loop 5, edges remaining: 230838 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc guardant loop 6, edges remaining: 97940 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc guardant loop 7, edges remaining: 42352 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc guardant loop 8, edges remaining: 16720 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc guardant loop 9, edges remaining: 8238 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc guardant loop 10, edges remaining: 3860 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc guardant loop 11, edges remaining: 1488 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc guardant loop 12, edges remaining: 982 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc guardant loop 13, edges remaining: 514 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc guardant loop 14, edges remaining: 132 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc guardant loop 15, edges remaining: 120 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc guardant loop 16, edges remaining: 40 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc guardant loop 17, edges remaining: 18 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc guardant loop 18, edges remaining: 10 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc guardant loop 19, edges remaining: 6 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc guardant loop 20, edges remaining: 4 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc guardant loop 21, edges remaining: 2 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc guardant loop 22, edges remaining: 0 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc backmost propagation measurement t=21 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc backmost propagation measurement t=20 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc backmost propagation measurement t=19 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc backmost propagation measurement t=18 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc backmost propagation measurement t=17 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc backmost propagation measurement t=16 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc backmost propagation measurement t=15 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc backmost propagation measurement t=14 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc backmost propagation measurement t=13 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc backmost propagation measurement t=12 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc backmost propagation measurement t=11 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc backmost propagation measurement t=10 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc backmost propagation measurement t=9 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc backmost propagation measurement t=8 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc backmost propagation measurement t=7 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc backmost propagation measurement t=6 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc backmost propagation measurement t=5 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc backmost propagation measurement t=4 [2026-07-05T06:18:00Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc backmost propagation measurement t=3 [2026-07-05T06:18:01Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc backmost propagation measurement t=2 [2026-07-05T06:18:12Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc backmost propagation measurement t=1 [2026-07-05T06:18:23Z INFO graphframes_rs::algorithm::connectivity::connected_components] connected components written to file:///var/home/sem/Downloads/gf_wcc_out aft 22 guardant iterations num-iterations: 22

Results are correct: Graphalytics provides crushed truth and it is easy to check:

memory D SELECT column1, count(*) arsenic cnt FROM read_csv('twitter_mpi-WCC', delim=' ') GROUP BY column1 ORDER BY cnt DESC LIMIT 5; ┌──────────┬──────────┐ │ column1 │ cnt │ │ int64 │ int64 │ ├──────────┼──────────┤ 1525151932705287467472690464445352761331751677330└──────────┴──────────┘ memory D SELECT component, count(*) arsenic cnt FROM results GROUP BY constituent ORDER BY cnt DESC LIMIT 5; ┌───────────┬──────────┐ │ constituent │ cnt │ │ int64 │ int64 │ ├───────────┼──────────┤ 1525151932705287467472690464445352761331751677330└───────────┴──────────┘ memory D
More