PostgreSQL图邻居查询速度慢

时间:2018-09-22 04:24:24

标签: postgresql performance query-optimization graph-databases

编辑在我最初的问题中,我注意到使用JOIN和使用WHERE .. IN子句搜索邻居之间的区别,@ LukaszSzozda正确地指出这是半加入。原来,我的节点列表有重复项,这说明了JOIN花费更长的时间才能运行的原因。谢谢,@ LukaszSzozda。但是,我的问题的更重要的方面仍然是,下面是什么。 更新我在底部添加了相关的配置选项,并使用ANALYZE更新了统计信息(感谢@joop)。另外,我测试了三种不同的索引(B树,哈希,BRIN)。最后,我注意到使用不同的查询可能会由于顺序不同而将不同数量的行返回到tmp_nodes中,因此我将其固定为一组恒定的相当随机的8,000个节点。

在PostgreSQL中,我的查询在〜200 * 10 6 个节点(在〜1.3 * 10 9 边内)中搜索8,000个节点的邻居很慢(〜30使用哈希索引的秒数;请参阅下面的索引基准测试。

鉴于我在下面描述的设置,我的服务器软件,数据库,表或查询是否有进一步的优化,以使邻居搜索更快?考虑到how well PostgreSQL did on the ArangoDB NoSQL benchmark的速度,我尤其感到惊讶。

更具体地说:

  1. 我知道AgnesGraph,但不希望转向图形数据库解决方案,特别是因为我无法从AgnesGraph得知它与PostgreSQL保持最新关系的程度。有人可以解释一下AgnesGraph与PostgreSQL中查询实际发生方式的性能优势,以便我可以决定是否进行迁移吗?
  2. 是否有任何配置调整(无论是在服务器中还是在操作系统中)都会根据计划影响我的查询,从而导致其运行时间超过所需时间?

设置

我在PostgreSQL(PostgreSQL 10.1,我不得不从{中拉出)一个大型图形数据库(〜10 9 边,〜200 * 10 6 节点) {1}} PPA)存储在云(DigitalOcean,6核,16GB RAM计算机,Ubuntu 17.10,Intel®Xeon®CPU E5-2650 v4 @ 2.20GHz)上,并使用{ {3}}(请参阅底部)。我正在服务器上查询。

我已经创建了前缘和后缘表(请参见PGTune

zesty

并通过各自的键聚类(以防万一):

CREATE TABLE edges_fwd (src BIGINT, dest BIGINT, PRIMARY KEY (src, dest));
CREATE TABLE edges_back (src BIGINT, dest BIGINT, PRIMARY KEY (dest, src));

出于测试我的查询的目的,我关闭了CLUSTER edges_fwd USING edges_fwd_pkey; CLUSTER edges_back USING edges_back_pkey; (请参见下面的旁注)。

我想加载8,000个节点(根据用户查询可以更改的8,000个节点)的所有边缘,其标识符在表enabled_seqscan中列出(单列{{ 1}})。我最初在查询中写了这个版本(已经按照this question的行在后面打了自己):

tmp_nodes

我也尝试过:

nid

它们都很慢,并且最多需要30秒才能运行(使用哈希指标)。 SELECT e.* FROM tmp_nodes JOIN edges AS e ON e.src = tmp_nodes.nid; 的输出显示在下面。

我希望事情通常会快得多。为了在集群表中查找8,000个键(是的,我知道the graph talk from PGCon11),因为服务器知道行是有序的,所以我期望的页面读取数少于总行数回到。因此,虽然获取了243,708行,这不少,但它们与8,000个不同的键相关联,读取次数不应大于此数目:每个键平均30行,每个键约1,400字节读取(表大小为56GB,具有1.3B行,因此每行大约46个字节;顺便说一下,这对于16个字节的数据来说是个很大的膨胀)。这远远低于系统的页面大小(4K)。我认为阅读8,000页甚至随机访问的内容都不需要花费这么长时间。

这使我回到了我的问题(上)。

强制使用索引

我从it's not really a clustered index那里获得了建议,至少用于测试(尽管由于我的数据库是只读的,所以我可能会在生产中使用它),请将SELECT * FROM edges_fwd AS e WHERE e.src IN (SELECT nid FROM tmp_nodes); 设置为{{1} },以强制使用索引。 我每5次跑一次-时间在这里和那里相差几秒钟。

EXPLAIN ANALYZE输出

小心地刷新OS缓存的OS磁盘并重新启动服务器以反映正确的随机查找时间,我在两个查询中都使用了enable_seqscan。我使用了两种类型的索引-B树和哈希。我还为B off选项(2、8、32和128)使用了不同的值来尝试BRIN,但是它们的速度(数量级或数量级)都比上面提到的慢。我在下面提供结果供参考。

B树索引,EXPLAIN ANALYZE查询:

EXPLAIN ANALYZE

B树索引,pages_per_range查询(半联接):

JOIN

哈希索引,Nested Loop (cost=10000000000.58..10025160709.50 rows=15783833 width=16) (actual time=4.546..39152.408 rows=243708 loops=1) -> Seq Scan on tmp_nodes (cost=10000000000.00..10000000116.00 rows=8000 width=8) (actual time=0.712..15.721 rows=8000 loops=1) -> Index Only Scan using edges_fwd_pkey on edges_fwd e (cost=0.58..3125.34 rows=1973 width=16) (actual time=4.565..4.879 rows=30 loops=8000) Index Cond: (src = tmp_nodes.nid) Heap Fetches: 243708 Planning time: 20.962 ms Execution time: 39175.454 ms 查询:

WHERE .. IN

loops = 8000)     执行时间:34155.511 ms

哈希索引,Nested Loop (cost=10000000136.58..10025160809.50 rows=15783833 width=16) (actual time=9.578..42605.783 rows=243708 loops=1) -> HashAggregate (cost=10000000136.00..10000000216.00 rows=8000 width=8) (actual time=5.903..35.750 rows=8000 loops=1) Group Key: tmp_nodes.nid -> Seq Scan on tmp_nodes (cost=10000000000.00..10000000116.00 rows=8000 width=8) (actual time=0.722..2.695 rows=8000 loops=1 ) -> Index Only Scan using edges_fwd_pkey on edged_fwd e (cost=0.58..3125.34 rows=1973 width=16) (actual time=4.924..5.309 rows=30 loops=8000) Index Cond: (src = tmp_nodes.nid) Heap Fetches: 243708 Planning time: 19.126 ms Execution time: 42629.084 ms 查询(半联接):

JOIN

)       -> edges_fwd e上的位图堆扫描(成本= 51.08..6986.79行= 1973宽度= 16)(实际时间= 3.768..3.958行= 30循环= 8000)             堆块:准确= 8094             ->在ix_edges_fwd_src_hash上进行位图索引扫描(成本= 0.00..50.58行= 1973宽度= 0)(实际时间= 2.340..2.340行= 31 循环= 8000)     执行时间:31857.692 ms

Nested Loop (cost=10000000051.08..10056052287.01 rows=15783833 width=16) (actual time=3.710..34131.371 rows=243708 loops=1) -> Seq Scan on tmp_nodes (cost=10000000000.00..10000000116.00 rows=8000 width=8) (actual time=0.960..13.338 rows=8000 loops=1) -> Bitmap Heap Scan on edges_fwd e (cost=51.08..6986.79 rows=1973 width=16) (actual time=4.086..4.250 rows=30 loops=8000) Heap Blocks: exact=8094 -> Bitmap Index Scan on ix_edges_fwd_src_hash (cost=0.00..50.58 rows=1973 width=0) (actual time=2.563..2.563 rows=31 设置

我按照answers to another question的建议设置了以下配置选项:

WHERE .. IN

2 个答案:

答案 0 :(得分:0)

您还需要索引以相反的方向搜索


CREATE TABLE edges_fwd 
        (src BIGINT
        , dest BIGINT
        , PRIMARY KEY (src, dest)
        );
CREATE UNIQUE INDEX ON edges_fwd(dest, src);

CREATE TABLE edges_back
        (src BIGINT
        , dest BIGINT
        , PRIMARY KEY (dest, src)
        );
CREATE UNIQUE INDEX ON edges_back(src, dest);

SELECT fwd.*
  FROM edges_back AS bck
  JOIN edges_fwd AS fwd
    ON fwd.src = bck.src        -- bck.src does not have a usable index
 WHERE bck.dest = root_id;

缺少此索引会导致哈希联接(或:tablescan)

此外,您也许可以合并两个表。


此外,您可以强制将srcdest列设置为NOT NULL (在Edges表中,null毫无意义) ,并使其成为您nodes表中的外键:


CREATE TABLE nodes
        (nid BIGINT NOT NULL PRIMARY KEY
        -- ... more stuff...
        );

CREATE TABLE edges_fwd
        (src BIGINT NOT NULL REFERENCES nodes(nid)
        , dest BIGINT NOT NULL REFERENCES nodes(nid)
        , PRIMARY KEY (src, dest)
        );

CREATE TABLE edges_back
        (src BIGINT NOT NULL REFERENCES nodes(nid)
        , dest BIGINT NOT NULL REFERENCES nodes(nid)
        , PRIMARY KEY (dest, src)
        );

INSERT INTO nodes(nid)
SELECT a
FROM generate_series(1,1000) a -- 1000 rows
        ;

INSERT INTO edges_fwd(src, dest)
SELECT a.nid, b.nid
FROM nodes a
JOIN nodes b ON random()< 0.1 --100K rows
        ;

INSERT INTO edges_back(src, dest)
SELECT a.nid, b.nid
FROM nodes a
JOIN nodes b ON random()< 0.1 --100K rows
        ;

这将导致该计划:


DROP SCHEMA
CREATE SCHEMA
SET
CREATE TABLE
CREATE TABLE
CREATE TABLE
INSERT 0 1000
INSERT 0 99298
INSERT 0 99671
ANALYZE
ANALYZE
                                                                  QUERY PLAN                                                                   
-----------------------------------------------------------------------------------------------------------------------------------------------
 Nested Loop  (cost=0.50..677.62 rows=9620 width=16) (actual time=0.086..5.299 rows=9630 loops=1)
   ->  Index Only Scan using edges_back_pkey on edges_back bck  (cost=0.25..100.07 rows=97 width=8) (actual time=0.053..0.194 rows=96 loops=1)
         Index Cond: (dest = 11)
         Heap Fetches: 96
   ->  Index Only Scan using edges_fwd_pkey on edges_fwd fwd  (cost=0.25..5.46 rows=99 width=16) (actual time=0.008..0.037 rows=100 loops=96)
         Index Cond: (src = bck.src)
         Heap Fetches: 9630
 Planning time: 0.480 ms
 Execution time: 5.836 ms
(9 rows)

答案 1 :(得分:0)

似乎这种设置的随机访问速度很慢。在大文件中运行a script to check random-access of 8,000 different, random 4K blocks大约需要30秒。使用Linux time和链接脚本,我平均得到大约24秒的时间:

File size: 8586524825 Read size: 4096
32768000 bytes read

real    0m24.076s

因此,似乎认为应该更快地进行随机访问是错误的。加上读取实际索引所花费的时间,这意味着在不更改硬件的情况下性能达到了顶峰。为了提高性能,我可能需要使用RAID设置或群集。如果RAID设置可以以接近线性的方式提高性能,我将接受自己的答案。