Postgres 19 图查询生产实测

我们的Neo4j集群每月的费用比我的车贷还多,而它只回答四个查询。

四个。我在另一个标签页中打开账单,数了数,感受到那种当你意识到一直在为一个几乎不住的东西付房租时特有的恐惧。

四个Cypher语句,由三年前离开的承包商编写,都在做某种形式的"向我展示与这个人连接的人"。

所以当Postgres 19 Beta 2带着SQL/PGQ发布时,我做了件小事。我把一份生产数据的副本拉到了备用机器上,并将我们的遍历指向了它。

我原本打算写一篇自鸣得意的文章,说明图数据库仍然会获胜。结果那不是我最终写的文章。

1、实际发布的内容

Peter Eisentraut在三月份提交了SQL/PGQ。这是SQL:2023标准的属性图,而它最重要的地方在于它拒绝成为的东西。

没有图存储引擎。没有邻接表。没有行的第二份副本。

属性图是覆盖在已有表上的元数据,其行为很像视图。

你只需声明一次。

CREATE PROPERTY GRAPH social
  VERTEX TABLES (
    users KEY (id) LABEL person PROPERTIES (id, name)
  )
  EDGE TABLES (
    follows KEY (id)
      SOURCE KEY (src) REFERENCES users (id)
      DESTINATION KEY (dst) REFERENCES users (id)
      LABEL knows
  );

然后你可以用几乎和Cypher一模一样的模式匹配来查询它。

SELECT * FROM GRAPH_TABLE (social
  MATCH (a IS person WHERE a.id = 42)-[IS knows]->(b IS person)
  COLUMNS (b.id, b.name)
);

2、无人警告我的重写

GRAPH_TABLE是一个重写器。你的箭头语法在规划器看到之前就被展平为普通的连接。

   你写的                          实际运行的
   ----                          --------
GRAPH_TABLE (                          users a
     MATCH (a)-[k]->(b)     ---->    JOIN follows k ON k.src = a.id
     COLUMNS (b.name)                JOIN users   b ON b.id  = k.dst
   )
        |                                       |
        |                                       v
        +----- 没有图引擎 ----->   同样的规划器,同样的统计信息,
                                        同样的索引,同样的EXPLAIN

一跳变成三方连接。两跳变成五方。三跳变成七方。形状机械地增长,而常规的代价模型选择连接顺序。

这听起来很无聊,直到你意识到你的索引、你的MVCC、你的行级安全和你的备份都继续工作,而没有任何新的移动部件。

3、我的设置,因为你会问

十六核,64GB RAM,本地NVMe。匿名化的生产副本,包含410万用户和3800万关注边。

pg 19beta2   shared_buffers 16GB   work_mem 128MB   effective_cache_size 48GB
neo4j 5      heap 16GB             pagecache 24GB
两者都预热,500次运行的中位数,客户端在同一主机上

同一台机器,同一份数据,客户端和两个数据库之间没有网络。

4、数据

  查询                        neo4j        pg19        胜出
  ----------------------------------------------------------------
  一跳,按id种子              1.8 ms      1.1 ms      postgres
  两跳,去重                  14 ms       9 ms        postgres
  两跳,过滤计数              22 ms       12 ms       postgres
  三跳,去重                  61 ms       148 ms      neo4j

以下是规划器在两跳情况下物有所值的证明。

Nested Loop  (actual time=0.021..8.744 rows=3117)
  ->  Index Scan using users_pkey on users a  (rows=1)
  ->  Nested Loop  (actual time=0.014..7.902 rows=3117)
        ->  Index Scan using follows_src_idx on follows k1  (rows=284)
        ->  Index Scan using follows_src_idx on follows k2  (rows=11)
Buffers: shared hit=9412

所有内容都来自索引,没有来自磁盘。我读了三遍那张表。

5、我看起来很傻的部分

我的第一次反向遍历运行了340毫秒,我差点关闭终端并宣布Postgres不适合。

然后我查看了计划,发现了一个跨越所有3800万条边的顺序扫描。我们在源列上有索引,但在目标列上没有,因为Neo4j可以免费双向遍历,而我们从未需要考虑过这一点。

CREATE INDEX ON follows (dst);

之后六毫秒。图数据库多年来一直在悄悄地弥补我们模式中的一个漏洞,而我却一直称它为魔法。

6、它崩溃的地方

然后是第四个查询,Postgres礼貌地拒绝了。

MATCH (a:Person {id: 42})-[:KNOWS*1..4]->(b:Person)
RETURN DISTINCT b.name

Postgres 19没有量化路径模式,没有最短路径,没有多模式MATCH。如果你的深度是一个变量而不是你手动输入的数字,GRAPH_TABLE无法表达它。

变通方法是我们都在图数据库存在之前写过的东西。

WITH RECURSIVE walk AS (
  SELECT dst AS id, 1 AS d FROM follows WHERE src = 42
  UNION
  SELECT f.dst, w.d + 1
  FROM walk w JOIN follows f ON f.src = w.id
  WHERE w.d < 4
)
SELECT DISTINCT u.name
FROM walk w JOIN users u ON u.id = w.id;

九十六毫秒。和Neo4j足够接近,以至于我不再在意,而且丑陋到我终于理解为什么有人会购买图数据库而不是编写这个。


原文链接:Postgres 19 Adds Graph Queries. I Ran Our Neo4j Workload Against It

汇智网翻译整理,转载请标明出处