Apache AGE 压测实录:PostgreSQL 里的图数据库扛不扛得住
故事从几个 Apache AGE 的 segfault 开始。年轻扩展出段错误不算稀奇,但它带出一个问题:稳定性尚摇摇欲坠时,性能如何? 而真正的障碍是——没有现成的 AGE 基准:没有现成 benchmark、没有已发布的结果。于是作者基于 LDBC SNB 数据模型自己造了压测。
为什么要把图塞进 PostgreSQL
关系模型答"Alice 住纽约"要 JOIN 四张表;图模型就是一个 Alice 顶点、一个 New York 顶点、一条 lives-in 边。Apache AGE(A Graph Extension)让你直接在 PostgreSQL 里用 openCypher(Neo4j Cypher 的开源版本)查图,不用部署独立图数据库、不用跨系统同步、团队不用学新栈。代价在后面:openCypher 是开放版本而非 Cypher 完整拷贝。
深度 2 层的"朋友的朋友",SQL 开始长递归 CTE 和多个 JOIN;Cypher 只是把 [:KNOWS] 改成 [:KNOWS*1..2]——三个字符。三层深度后,可读性差距像"诗 vs 洗衣机说明书"。
内部机制
连接是标准扩展流程(CREATE EXTENSION age + LOAD + search_path 指向 ag_catalog)。create_graph 自动建同名 schema 和两张父表 ag_label_vertex/ag_label_edge,Cypher 引入类型时自动建继承子表,无需手动 CREATE TABLE。顶点属性是 agtype(JSONB 的超集),边额外带 start_id/end_id。顶点属性几乎总比边重——边通常一两个字段,顶点可以带十几个属性。
自建基准
数据模型和查询集取自 LDBC SNB,内部工具 pg_microbench 直接对接 AGE,19 个测试场景分三组:短读、重读、写。社交网络 schema(Person/Post/Comment/Forum/Tag…),数据按 SF 缩放:SF=1(约 2 万对象)、SF=10(约 20 万)、SF=100(约 200 万)。索引专门建了两种:id 上的 B-tree(仅当查询用 WHERE n.id = 'value' 时才需要,否则冗余)、properties 上的 GIN。
关键数字
| 负载 | TPS | 状态 |
|---|---|---|
| 短读 | ~40000 | 稳定 |
| 路径查找 | ~7 | 补丁进行中 |
| 边插入 | 30000 | 稳定 |
| 顶点插入(patch 前) | 1000 → 退化 | 已向社区提交补丁 |
| 顶点插入(patch 后) | 15000 | 稳定 |
作者发现真正的瓶颈有时不是图遍历算法或查询语言复杂度,而是一次插入时顶点存在性检查里的单次 SeqScan——本不该出现在那里。
实践建议
- id 建 B-tree 索引、properties 建 GIN 索引——必做;
- 查询永远指定 label,否则全表 SeqScan;
- 从选择性更强的一端起步,尽早过滤;
- 路径查找锁死深度、偏好单向查询;
- 重型遍历把 work_mem 提到 120MB+;
- 小心 OPTIONAL MATCH。
结论:PostgreSQL 之上的图模型可行,短查询和中复杂负载下 AGE 完全可用;但与 Neo4j 相比,大图和路径查找仍是 Neo4j 更快——AGE 开发者自己的回答是"Neo4j 是为图从零建的,AGE 叠在关系库之上,差距靠补丁收窄"。
来源:Apache AGE under load: how PostgreSQL graphs behave under stress - DEV Community