web-dev-qa-db-ja.com

Postgres 9.2でwork_memとshared_buffersを増やすとクエリが大幅に遅くなる

16GBのRAMを搭載したRHEL 6.3、8コアマシンで実行されているPostgreSQL 9.2インスタンスがあります。サーバーはこのデータベース専用です。デフォルトのpostgresql.confはメモリ設定に関してかなり保守的であることを考えると、Postgresがより多くのメモリを使用できるようにすることは良い考えかもしれないと思いました。驚いたことに、 wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server に関するアドバイスに従うと、実行するすべてのクエリが実質的に遅くなりますが、より複雑なクエリでは明らかに顕著になります。

また、より多くのパラメーターを調整して以下の推奨事項を提供するpgtuneを実行してみましたが、何も変更されませんでした。 RAMサイズの1/4のshared_buffersが推奨されます。これは他の場所(特にPGウィキ)でのアドバイスに沿っているようです)。

_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の共有メモリが(shmmaxおよびshmall)に設定されている場合の割り当ては、非常に大きな値に設定されているため、問題にはなりません。 Postgresログにもエラーが表示されません。私はデフォルトの設定でautovacuumを実行していますが、それが何かと関係があるとは思いません。すべてのクエリは、数秒間隔で同じマシン上で実行され、構成を変更した(そしてPGを再起動した)だけでした。

編集:1つの特に興味深い事実を見つけました。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のフォローアップディスカッション も参照してください。

44
Petr Praus

まず最初に、work_memは操作ごとにであり、かなり早く過剰になる可能性があることに注意してください。一般に、並べ替えが遅い問題が発生しない場合は、必要になるまでwork_memをそのままにしておきます。

クエリプランを見ると、2つのプランを見るとバッファヒットが非常に異なっており、シーケンシャルスキャンでも速度が遅いことがわかります。この問題は先読みキャッシングと関係があり、そのためのスペースが少ないと思います。つまり、インデックスを再利用したり、ディスク上のテーブルを読み取ったりしないようにメモリにバイアスをかけています。


私の理解は、OSキャッシュにそのページが含まれるかどうか実際にはわからないため、ディスクから読み取る前にPostgreSQLがページのキャッシュを参照することです。その後、ページはキャッシュ内にとどまり、そのキャッシュはOSキャッシュよりも低速であるため、これにより、クエリの並べ替えが速くなりますが、遅い並べ替えに変わります。実際、work_memの問題を除いて、プランを読み取ると、すべてのクエリ情報がキャッシュから取得されているように見えますが、それはどのキャッシュの問題です。

work_mem:ソートまたは関連する結合操作に割り当てることができるメモリの量。これは操作ごとであり、ステートメントごとやバックエンドごとではないため、1つの複雑なクエリでこの量のメモリを何倍も使用できます。この制限に達していることは明らかではありませんが、注意し、認識しておく価値はあります。これを大きくしすぎると、読み取りキャッシュと共有バッファーで使用できるメモリが失われます。

shared_buffers:実際のPostgreSQLページキューに割り当てるメモリの量。ここで、理想的には、データベースの興味深いセットは、ここにキャッシュされたメモリと読み取りバッファーにとどまります。ただし、これにより、すべてのバックエンドで最も頻繁に使用される情報がキャッシュされ、ディスクにフラッシュされなくなります。 Linuxでは、このキャッシュはOSのディスクキャッシュよりもかなり低速ですが、OSのディスクキャッシュが動作しないこと、およびPostgreSQLに対して透過的であることを保証します。これは明らかに問題の場所です。

したがって、何が起こるかというと、リクエストがあると、PostgreSQLがこのキャッシュについて深い知識を持っているので、最初に共有バッファをチェックし、ページを探します。それらがそこにない場合、OSにファイルから開くように依頼し、OSが結果をキャッシュしている場合は、キャッシュされたコピーを返します(これは共有バッファーよりも高速ですが、Pgはそれがキャッシュされているかどうかを判断できません) disk、およびdiskはmuch遅いので、PostgreSQLは通常そのような機会を逃します)。これはランダムページアクセスとシーケンシャルページアクセスにも影響することに注意してください。そのため、shared_buffers設定を低くすると、パフォーマンスが向上する可能性があります。

私の直感では、shared_buffer設定を大きくすると、並行性の高い環境でパフォーマンスが向上するか、少なくとも一貫性が向上するということです。また、PostgreSQLはこのメモリを取得して保持するため、システムで他のものが実行されている場合、読み取りバッファは他のプロセスによって読み取られたファイルを保持します。これは非常に大きくて複雑なトピックです。共有バッファーの設定を大きくすると、パフォーマンスが保証されますが、パフォーマンスが低下する場合があります。

31
Chris Travers

work_memを増やすとパフォーマンスが低下するという一見逆説的な効果( @ Chris で説明があるかもしれません)とは別に、少なくとも2つの方法で関数を改善できます。

  • 2つの偽の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%')
  • 実際の検索パターンがより選択的であると想定して、pi.firstnameおよびpi.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);

または、1つのマルチカラムインデックス:

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を消費することを防ぎ、おそらく互いに飢えさせます。

12