在Postgres 9.2上增加work_mem和shared_buffers会大大减慢查询速度


39

我有一个运行在具有16GB RAM的8核RHEL 6.3计算机上的PostgreSQL 9.2实例。服务器专用于此数据库。鉴于默认的postgresql.conf在内存设置方面相当保守,我认为允许Postgres使用更多内存可能是一个好主意。令我惊讶的是,遵循wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server上的建议,实际上使我运行的每个查询速度显着降低,但是在更复杂的查询上显然更明显。

我还尝试运行pgtune,它给出了以下建议,并带有更多已调整的参数,但没有任何改变。它建议使用RAM大小的1/4的shared_buffers,这似乎与其他地方(尤其是PG Wiki)上的建议相符。

default_statistics_target = 50
maintenance_work_mem = 960MB
constraint_exclusion = on
checkpoint_completion_target = 0.9
effective_cache_size = 11GB
work_mem = 96MB
wal_buffers = 8MB
checkpoint_segments = 16
shared_buffers = 3840MB
max_connections = 80

我尝试在更改设置(使用reindex database)后为整个数据库重新编制索引,但这也无济于事。我玩过shared_buffers和work_mem。从非常保守的默认值(128k / 1MB)逐渐更改它们会逐渐降低性能。

我遇到EXPLAIN (ANALYZE,BUFFERS)了一些疑问,罪魁祸首似乎是哈希联接的速度明显慢。我不清楚为什么。

举一些具体的例子,我有以下查询。在默认配置下,它在〜2100ms内运行,在配置增加缓冲区的情况下,在〜3300ms内运行:

select count(*) from contest c
left outer join contestparticipant cp on c.id=cp.contestId
left outer join teammember tm on tm.contestparticipantid=cp.id
left outer join staffmember sm on cp.id=sm.contestparticipantid
left outer join person p on p.id=cp.personid
left outer join personinfo pi on pi.id=cp.personinfoid
where pi.lastname like '%b%' or pi.firstname like '%a%';

EXPLAIN (ANALYZE,BUFFERS) 对于上面的查询:

问题是,为什么增加缓冲区大小时会观察到性能下降?机器肯定没有内存不足。如果将OS中的共享内存(shmmaxshmall)设置为非常大的值,那应该没有问题。我在Postgres日志中也没有收到任何错误。我在默认配置下运行autovacuum,但我不希望这与它有任何关系。所有查询都在相隔几秒钟的同一台计算机上运行,​​只是配置有所更改(并重新启动了PG)。

编辑:我发现一个特别有趣的事实:当我在2010年中的iMac(OSX 10.7.5)上也使用Postgres 9.2.1和16GB RAM进行相同的测试时,我没有遇到速度变慢的情况。特别:

set work_mem='1MB';
select ...; // running time is ~1800 ms
set work_mem='96MB';
select ...' // running time is ~1500 ms

当我对服务器上的数据进行完全相同的查询(上述查询)时,work_mem = 1MB时为2100毫秒,而96 MB时为3200毫秒。

Mac具有SSD,因此可以理解得更快,但是却表现出我所期望的行为。

另请参阅有关pgsql-performance后续讨论


1
看起来在较慢的情况下,每个步骤通常都比较慢。其他设置是否保持不变?
dezso 2012年

1
在一个更专业的论坛上而不是在一般的论坛上提出这个问题可能是值得的。在这种情况下,我建议使用pgsql-general邮件列表archives.postgresql.org/pgsql-general
Colin't Hart

1
哦,然后举报,如果找到答案,请回答您自己的问题!(这是允许的,甚至被鼓励)。
科林·哈特

1
我确实想知道Postgres在这方面与Oracle有何相似:我记得乔纳森·刘易斯(Oracle专家)所做的一门课程,他在课程中演示了分配更多的内存有时会使它们变慢。我忘了具体细节,但这与Oracle进行部分排序然后将它们写到临时存储中,然后再将它们组合在一起有关。以某种方式更多的内存使此过程变慢。
科林·哈特

2
该问题现在发布在pgsql-performance上:archives.postgresql.org/pgsql-performance/2012-11/msg00004.php
Petr Praus 2012年

Answers:


28

首先,请记住work_mem是针对每个操作的,因此它很快就会变得过多。通常,如果您没有遇到速度慢的麻烦,我会不理会work_mem,直到您需要它为止。

查看您的查询计划时,令我印象深刻的一件事是,查看这两个计划时,缓冲区命中率有很大不同,甚至顺序扫描都比较慢。我怀疑该问题与预读缓存有关,并且其空间较小。这意味着您要使内存偏重于索引的重复使用,并禁止读取磁盘上的表。


我的理解是PostgreSQL在从磁盘读取页面之前会先查找页面的缓存,因为它并不真正知道OS缓存是否将包含该页面。因为页面然后保留在缓存中,并且因为该缓存比OS缓存慢,所以这改变了快速查询与慢速查询的排序。实际上,除了work_mem问题外,阅读计划还似乎所有查询信息都来自缓存,但这是哪个缓存的问题。

work_mem:我们可以为排序或相关的联接操作分配多少内存。这是针对每个操作,而不是针对每个语句或后端,因此单个复杂查询可以使用此内存量的很多倍。尚不清楚您是否达到了这个极限,但是值得注意和意识到。如果将其增加得太多,则会丢失可能用于读取缓存和共享缓冲区的内存。

shared_buffers:分配给实际PostgreSQL页面队列的内存量。现在,理想情况下,数据库的有趣集合将保留在此处缓存的内存和读取缓冲区中。但是,这样做是为了确保缓存所有后端中最常用的信息,而不将其刷新到磁盘上。在Linux上,此缓存比OS磁盘缓存要慢得多,但是它可以保证OS磁盘缓存不存在并且对PostgreSQL透明。这很明显是您的问题所在。

所以发生的事情是,当我们有一个请求时,我们首先检查共享缓冲区,因为PostgreSQL对此缓存有很深的了解,然后查找页面。如果它们不存在,我们会要求操作系统从文件中打开它们,并且如果操作系统已经缓存了结果,它将返回缓存的副本(这比共享缓冲区快,但是Pg无法确定是缓存还是打开)磁盘,而磁盘慢得多,因此PostgreSQL通常不会遇到这种情况。请记住,这也会影响随机和顺序页面访问。因此,使用较低的shared_buffers设置可能会获得更好的性能。

我的直觉是,在具有较大shared_buffer设置的高并发环境中,您可能会获得更好的性能,或者至少更加一致。还请记住,PostgreSQL会获取并保留该内存,因此,如果系统上正在运行其他内容,则读取缓冲区将保存其他进程读取的文件。这是一个非常大而复杂的话题。较大的共享缓冲区设置可提供更好的性能保证,但在某些情况下可能会降低性能。


10

除了看似矛盾的效果,增加work_mem性能会降低性能(@Chris可能有一个解释),您可以至少通过两种方式改善功能。

  • 用重写两个伪造LEFT JOINJOIN。这可能会使查询计划者感到困惑,并导致劣等的计划。

SELECT count(*) AS ct
FROM   contest            c
JOIN   contestparticipant cp ON cp.contestId = c.id
JOIN   personinfo         pi ON pi.id = cp.personinfoid
LEFT   JOIN teammember    tm ON tm.contestparticipantid = cp.id
LEFT   JOIN staffmember   sm ON sm.contestparticipantid = cp.id
LEFT   JOIN person        p  ON p.id = cp.personid
WHERE (pi.firstname LIKE '%a%'
OR     pi.lastname  LIKE '%b%')
  • 假设您的实际搜索模式更具选择性,请在上使用Trigram索引pi.firstnamepi.lastname支持非锚定LIKE搜索。('%a%'也支持类似的更短模式,但是索引不太可能对非选择谓词有所帮助。):

CREATE INDEX personinfo_firstname_gin_idx ON personinfo USING gin (firstname gin_trgm_ops);
CREATE INDEX personinfo_lastname_gin_idx  ON personinfo USING gin (lastname gin_trgm_ops);

或一个多列索引:

CREATE INDEX personinfo_name_gin_idx ON personinfo USING gin (firstname gin_trgm_ops, lastname gin_trgm_ops);

应该使您的查询快得多。您需要为此安装附加模块pg_trgm。这些相关问题下的详细信息:


另外,您是否尝试过work_mem 在本地设置-仅针对当前交易

SET LOCAL work_mem = '96MB';

这样可以防止并发事务占用更多的RAM,甚至可能使彼此饿死。


3
我想支持Erwin的本地work_mem建议。由于work_mem更改了更快的查询种类,因此您可能需要为某些查询更改它。即,低work_mem级别最适合以复杂方式(即大量联接)对少量记录进行排序/联接的查询,而高work_mem级别最适合于具有少数种类但一次对多个行进行排序或联接的查询。
克里斯·特拉弗斯

同时,我改进了查询(问题是从去年10月开始的),但谢谢:)这个问题更多的是关于意外的影响,而不是特定的查询。该查询主要用于演示效果。感谢您提供索引提示,我将尽力尝试!
Petr Praus 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.