在10GB内存上运行十亿级图算法:我爱DataFusion

Hacker News Top 新闻

摘要

一位开发者展示,Apache DataFusion 可以通过将数据卸载到磁盘,在配备5–10GB内存的笔记本电脑上执行 PageRank 和弱连通分量等十亿级图分析,从而挑战对 Spark/GraphFrames 的需求。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/07/31 16:59

# 使用10GB内存处理十亿级图算法:我爱 DataFusion! 来源:https://semyonsinchenko.github.io/ssinchenko/post/datafusion-graphs-cc-2/ ## TLDR; 我使用 Apache DataFusion 实现了一个图 map-reduce。在可能的情况下,我将所有内容卸载到磁盘,并将算法设计为依赖批量扫描而非随机访问。DataFusion 处理溢出、排序合并连接、聚合、规划和执行,所以我的代码非常轻量。我通过 `systemd-run` 以硬内存限制的严格模式进行了测试。它确实可行。当然,我也遇到了一些问题:例如,在极端场景下我经常遇到 `FairSpillPool` 的死锁,而且我还没有找到方法让 SMJ 利用磁盘上已排序的数据。但它确实能工作。我可以在 5GB 内存下计算具有十亿条边的有向图(Graphalytics 数据集中的 `graph500-26`)的 PageRank。或者,我可以在 10GB 内存下识别具有二十亿条边的图(同一数据集集合中的 `twitter_mpi`)中的所有弱连通分量。NetworkX 和 Igraph 都做不到这一点;大多数现有图算法要求图能够装入内存。以前,我认为十亿级图分析需要 Apache Spark 和 GraphFrames。但现在,我认为你只需要一台笔记本电脑。我完全改变了我之前关于使用 Apache DataFusion 进行图分析的观点(https://semyonsinchenko.github.io/ssinchenko/post/datafusion-graphs-cc/#principal-limitations)。 ## 环境配置 我测试了两个任务。 ### PageRank 什么是 PageRank?(https://en.wikipedia.org/wiki/PageRank) 任务是在 Graphalytics 数据集 (https://ldbcouncil.org/benchmarks/graphalytics/datasets/) 的 `graph500-26` 上计算 PageRank: | 指标 | 值 | |---|---| | 节点数 | 32,804,978 | | 边数 | 1,051,922,853 | | 有向 | False | | 内存限制 | 5 GB | | DataFusion 池大小 | 4 GB | PageRank 是最流行的图中心性算法之一,应用范围从搜索结果排名到反欺诈评分。我的 DataFusion 实现是经典的 Pregel:批量同步并行算法(即 Map-Reduce),我通过连接和聚合来表达它。这与 Spark 的 GraphFrames 库核心中的实现非常相似。 ### 弱连通分量 什么是弱连通分量?(https://en.wikipedia.org/wiki/Weak_component) 任务是在同一数据集的 `twitter_mpi` 上识别所有弱连通分量: | 指标 | 值 | |---|---| | 节点数 | 52,579,682 | | 边数 | 1,963,263,821 | | 有向 | True | | 内存限制 | 10 GB | | DataFusion 池大小 | 8 GB | WCC 是任何身份(实体)解析问题的核心部分。例如,当你需要通过传递性 ID 对不同系统中的数据进行去重时,最终会归结为 WCC 问题。我的 DataFusion 实现基于 Bögeholz 等人的 "In-database connected component analysis",arXiv 1802.09478 (https://arxiv.org/abs/1802.09478)。我已经为 Spark 的 GraphFrames 实现了相同的算法,所以这是一个显而易见的选择。 ## 结果 ### PageRank 简单部分。我使用 SMJ 只是为了证明可扩展性,但也可以使用 HJ,因为顶点数量很小(32M),而且 PageRank 状态非常简单:一列 `rank`(`f64`)、一列 `out-degree`(`i64`)、一个参与标志(`bool`)。使用 HJ 会更快。PageRank 作用于有向边,因此不需要对图进行对称化。只需将边卸载到磁盘,并通过更新状态(同时将状态卸载到磁盘以打破血缘关系)进行迭代,直到收敛。 计算时间较长:15 次完整迭代大约需要 30 分钟。但这里的重点是内存,而不是速度。给它一些更现实的十亿级图分析数据,它会运行得足够快(我测试过)。我将结果与基准答案进行了核对:100% 匹配(容差为 `0.0001`)。这里还可以做很多优化:理论上可以按范围对边进行分桶,或做某种范围分区,这样 SMJ 就不需要在每次迭代中重新对最大的连接侧(边)进行排序以获得三元组。此外,我也不能 100% 确定 parquet 是这里的最佳选择。尝试融合 join+agg 也会很有趣:每次 Pregel 迭代就像是 `edges <-[join] nodes-state` -> `group by + agg` -> `[join] -> nodes-state` -> `update nodes-state`。如果我能将前两个阶段融合在一起,从性能角度来看将是一个巨大的胜利。但与此同时,我还不知道如何在 DataFusion 中实现这一点:还有很多东西要学。 ### WCC 最困难的部分。20 亿条边的 twitter 图已经非常巨大(其边在 CSV 中占 30 GB!!!)。但对于 WCC,我们需要对边进行对称化(或者在 `src, dst` 和 `dst AS src, src AS dst` 之间做 union,并在其上做 `distinct`),所以我们在峰值时仅用 8GB 的 DataFusion 池处理近 40 亿条边。在流程挺过最初的几次迭代后,收缩过程会急剧减少边的数量,算法在 10 分钟内结束,内存压力很低。 ``` 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 as unit: run-p316509-i284528.scope; invocation ID: 742f9296d31d426580b7ec8213422cf1 [2026-07-05T05:37:21Z INFO graphframes_rs::algorithm::connectivity::connected_components] start WCC with run-id 017c0a23-2b20-4ffa-ac6b-6e2cb8d7203e [2026-07-05T05:52:21Z INFO graphframes_rs::algorithm::connectivity::connected_components] after preparation graph has 3228212374 edges [2026-07-05T06:13:21Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc forward iteration 1, edges remaining: 840238268 [2026-07-05T06:17:39Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc forward iteration 2, edges remaining: 77322906 [2026-07-05T06:17:57Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc forward iteration 3, edges remaining: 5624128 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc forward iteration 4, edges remaining: 1075998 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc forward iteration 5, edges remaining: 230838 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc forward iteration 6, edges remaining: 97940 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc forward iteration 7, edges remaining: 42352 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc forward iteration 8, edges remaining: 16720 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc forward iteration 9, edges remaining: 8238 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc forward iteration 10, edges remaining: 3860 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc forward iteration 11, edges remaining: 1488 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc forward iteration 12, edges remaining: 982 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc forward iteration 13, edges remaining: 514 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc forward iteration 14, edges remaining: 132 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc forward iteration 15, edges remaining: 120 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc forward iteration 16, edges remaining: 40 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc forward iteration 17, edges remaining: 18 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc forward iteration 18, edges remaining: 10 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc forward iteration 19, edges remaining: 6 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc forward iteration 20, edges remaining: 4 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc forward iteration 21, edges remaining: 2 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc forward iteration 22, edges remaining: 0 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc back propagation step t=21 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc back propagation step t=20 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc back propagation step t=19 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc back propagation step t=18 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc back propagation step t=17 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc back propagation step t=16 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc back propagation step t=15 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc back propagation step t=14 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc back propagation step t=13 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc back propagation step t=12 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc back propagation step t=11 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc back propagation step t=10 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc back propagation step t=9 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc back propagation step t=8 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc back propagation step t=7 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc back propagation step t=6 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc back propagation step t=5 [2026-07-05T06:17:59Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc back propagation step t=4 [2026-07-05T06:18:00Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc back propagation step t=3 [2026-07-05T06:18:01Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc back propagation step t=2 [2026-07-05T06:18:12Z INFO graphframes_rs::algorithm::connectivity::connected_components] cc back propagation step 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 after 22 forward iterations num-iterations: 22 ``` 结果是正确的:Graphalytics 提供了基准答案,很容易验证: ``` memory D SELECT column1, count(*) as cnt FROM read_csv('twitter_mpi-WCC', delim=' ') GROUP BY column1 ORDER BY cnt DESC LIMIT 5; ┌──────────┬──────────┐ │ column1 │ cnt │ │ int64 │ int64 │ ├──────────┼──────────┤ │ 1 │ 52515193 │ │ 27052874 │ 67 │ │ 47269046 │ 44 │ │ 45352761 │ 33 │ │ 17516773 │ 30 │ └──────────┴──────────┘ memory D SELECT component, count(*) as cnt FROM results GROUP BY component ORDER BY cnt DESC LIMIT 5; ┌───────────┬──────────┐ │ component │ cnt │ │ int64 │ int64 │ ├───────────┼──────────┤ │ 1 │ 52515193 │ │ 27052874 │ 67 │ │ 47269046 │ 44 │ │ 45352761 │ 33 │ │ 17516773 │ 30 │ └───────────┴──────────┘ memory D ```

相似文章

Slater – 专为读密集型图设计的低内存图数据库

Hacker News Top

Slater 是一款低内存图数据库,专为读密集型工作负载设计。它使用固定的缓存预算从磁盘提供大型图服务,仅需几百 MB 的 RAM 即可查询数亿节点和数十亿边,同时兼容标准 Bolt 协议并支持实时写入。

我如何仅用24GB内存运行193B参数模型

Reddit r/ArtificialInteligence

介绍 Iris Ai,一个在消费级硬件上将查询路由至8个专用LLM的系统,通过每次仅激活一个模型和动态模型交换,以低内存实现大模型性能。