我在使用Postgrtes 10数据库时遇到了一个奇怪的性能问题。
我有这样一张桌子:
CREATE TABLE articles
(
article_id bigint NOT NULL,
content jsonb NOT NULL,
-- few other fields...
CONSTRAINT articles_pkey PRIMARY KEY (article_id)
)
我们有一个Python库(使用psycopg2驱动程序)来访问此表。它执行对表的简单查询,其中之一用于批量下载内容,如下所示:
select content from articles where id between $1 and $2
我注意到,使用某些ID时,它的运行速度非常快,而使用其他ID时,速度要慢4-5倍。值得注意的是,此表中有两个主要的ID“类别”:第一个是范围270000000-500000000,第二个是范围10000000000-100030000000。对于第一个范围,它是“快速”,而对于第二个范围,它是“慢”。除了将较大的绝对数从int
撤回到bigint
之外,第二个范围内的值更“稀疏”,这意味着在第一个范围内的值几乎是结果(丢失值的“空洞”很少) ),而在第二个范围内,“空洞”更多(平均填充率为30%)。
为了消除其他库代码的可能影响,我使用以下查询进行了综合测试:
select count(*), sum(length(content::text))
from storage.articles where article_id between %s and %s
我调整了请求卡盘的长度,以平均平均每个请求的JSONB摘要长度。
另外,我计算返回的count(*)
的处理速率除以执行时间。第一个范围是800-1050行/秒,第二个范围是120-250行/秒。另一个指标是每次处理的JSON的长度。由于查询中的主要困难似乎是处理JSONB数据,因此我认为在两种情况下它必须平均相同。但是没有:它是7-10 MB / s,而1.5-2.5 MB / s。
因此问题重现了。尽管它的绝对值变得更快(由于没有网络传输),但性能上的显着差异仍然存在。我比较了两个范围内查询的执行计划,它们看起来是等效的(EXPLAIN ANALYZE
):
Aggregate (cost=8046.71..8046.72 rows=1 width=8) (actual time=2924.410..2924.411 rows=1 loops=1)
-> Index Scan using articles_pkey on articles (cost=0.57..8000.02 rows=4669 width=107) (actual time=11.482..61.258 rows=5000 loops=1)
Index Cond: ((article_id >= 461995000) AND (article_id <= 461999999))
Planning time: 1.028 ms
Execution time: 2924.650 ms
Aggregate (cost=6216.75..6216.76 rows=1 width=8) (actual time=17704.635..17704.636 rows=1 loops=1)
-> Index Scan using articles_pkey on articles (cost=0.57..6180.69 rows=3606 width=107) (actual time=0.154..563.578 rows=3059 loops=1)
Index Cond: ((article_id >= '100017010000'::bigint) AND (article_id <= '100017019999'::bigint))
Planning time: 0.404 ms
Execution time: 17704.746 ms
我看到的唯一区别是转换为bigint
应该是一次。
我想念什么吗?如何调查(和修复)它?
更新1:为EXPLAIN
添加了BUFFERS
:
Aggregate (cost=8635.91..8635.92 rows=1 width=16) (actual time=6625.993..6625.995 rows=1 loops=1)
Buffers: shared hit=26847 read=3914
-> Index Scan using articles_pkey on articles (cost=0.57..8573.35 rows=5005 width=107) (actual time=21.649..1128.004 rows=5000 loops=1)
Index Cond: ((article_id >= 438000000) AND (article_id <= 438005000))
Buffers: shared hit=4342 read=671
Planning time: 0.393 ms
Execution time: 6626.136 ms
Aggregate (cost=5533.02..5533.03 rows=1 width=16) (actual time=33219.100..33219.102 rows=1 loops=1)
Buffers: shared hit=6568 read=7104
-> Index Scan using articles_pkey on articles (cost=0.57..5492.96 rows=3205 width=107) (actual time=22.167..12082.624 rows=2416 loops=1)
Index Cond: ((article_id >= '100021000000'::bigint) AND (article_id <= '100021010000'::bigint))
Buffers: shared hit=50 read=2378
Planning time: 0.517 ms
Execution time: 33219.218 ms
更新2:更多实验:
我进行了几次其他测试,以对表中的所有数据使用上述方法。使用一个简单的程序,我迭代了段的边界以最终处理两个范围内的所有数据。在此期间,我解析EXPLAIN ANALYZE的结果并收集以下一系列指标:
Index Scan...
)基于上述指标,我计算了继承指标:
由于ID的“密度”在“小”和“大”范围内不同,因此我调整了块的大小,以便在两种情况下每次迭代都获得约5000行,尽管我的实验表明,块的大小并不是真正的很重要。
上面发布的结果在整个第一和第二个范围内得到了确认(因此,这不仅是由随机缓存的数据引起的)。
为消除缓存影响,我使用刷新缓冲区重新启动了Postgres服务器:
postgresql stop; sync; echo 3 > /proc/sys/vm/drop_caches; postgresql start
此后,我重复了测试并得到了下一张相同的图片。
我将上次测试的系列渲染为图表,并将其发布在此处:
这是“小”范围的图片。在整个测试过程中,磁盘读取速率大约为10-11 MB / s。
问题:为什么我刷新了内存缓冲区后命中率仍然很高(读取率很低)?
这是“大”范围的图片。对我来说看起来很疯狂。在大多数情况下,磁盘读取速度约为2 MB / s,但有时会跳至26-30 MB / s。阅读率也相差很大。
在测试期间,我使用iotop
验证了磁盘读取速度,并发现其指示非常接近于我计算得出的指示。我不能说一直都在监视它,但是当第二个范围分别为2 MB / s和22 MB / s以及第一个范围为10 MB / s时,我确认了它。我还用htop
来检查CPU不是瓶颈,在测试过程中CPU大约是3%。
该问题在主服务器和从属服务器上均可重现。我的测试是在从属服务器上进行的,而DBMS上没有任何其他负载,或者主机上与DBMS无关的磁盘活动。
我唯一的假设是,由于虚拟化或其他原因,不同的数据片段将以不同的速度读取,但是...为什么要严格限制在这些范围内?为什么在两台不同的机器上都一样?
有什么想法吗?