私は「リンク」と呼ばれるアプリを持っています。1)ユーザーはグループに集まり、他のユーザーを追加します。2)上記のグループでお互いにコンテンツを投稿します。グループは、postgresql 9.6.5 DBの_links_group
_テーブルで定義されていますが、グループに投稿される返信は_links_reply
_テーブルで定義されています。全体的に、DBのパフォーマンスは優れています。
ただし、_links_reply
_テーブルに対する1つのSELECT
クエリが、slow_logに常に表示されます。 500ミリ秒以上かかり、他のほとんどのpostgresql操作で発生しているものよりも約10倍遅くなります。
Django ORMを使用してクエリを生成しました。ORM呼び出しは次のとおりです:replies = Reply.objects.select_related('writer__userprofile').filter(which_group=group).order_by('-submitted_on')[:25]
。基本的に、これは特定のグループオブジェクトの最新の25件の返信を選択しています。また、関連するuser
およびuserprofile
オブジェクトも同様です。
これは私の遅いログからの対応するSQLの例です:LOG:duration:8476.309 ms statement:
_SELECT
"links_reply"."id", "links_reply"."text",
"links_reply"."which_group_id", "links_reply"."writer_id",
"links_reply"."submitted_on", "links_reply"."image",
"links_reply"."device", "links_reply"."category",
"auth_user"."id", "auth_user"."username",
"links_userprofile"."id", "links_userprofile"."user_id",
"links_userprofile"."score", "links_userprofile"."avatar"
FROM
"links_reply"
INNER JOIN "auth_user"
ON ("links_reply"."writer_id" = "auth_user"."id")
LEFT OUTER JOIN "links_userprofile"
ON ("auth_user"."id" = "links_userprofile"."user_id")
WHERE "links_reply"."which_group_id" = 124479
ORDER BY "links_reply"."submitted_on" DESC
LIMIT 25
_
ここで説明の分析結果を見てください:https://explain.depesz.com/s/G4Xインデックススキャン(後方)いつも食べているようです。
これが_\d links_reply
_の出力です。
_Table "public.links_reply"
Column | Type | Modifiers
----------------+--------------------------+----------------------------------------------------------
id | integer | not null default nextval('links_reply_id_seq'::regclass)
text | text | not null
which_group_id | integer | not null
writer_id | integer | not null
submitted_on | timestamp with time zone | not null
image | character varying(100) |
category | character varying(15) | not null
device | character varying(10) | default '1'::character varying
Indexes:
"links_reply_pkey" PRIMARY KEY, btree (id)
"category_index" btree (category)
"links_reply_submitted_on" btree (submitted_on)
"links_reply_which_group_id" btree (which_group_id)
"links_reply_writer_id" btree (writer_id)
"text_index" btree (text)
Foreign-key constraints:
"links_reply_which_group_id_fkey" FOREIGN KEY (which_group_id) REFERENCES links_group(id) DEFERRABLE INITIALLY DEFERRED
"links_reply_writer_id_fkey" FOREIGN KEY (writer_id) REFERENCES auth_user(id) DEFERRABLE INITIALLY DEFERRED
Referenced by:
TABLE "links_groupseen" CONSTRAINT "links_groupseen_which_reply_id_fkey" FOREIGN KEY (which_reply_id) REFERENCES links_reply(id) DEFERRABLE INITIALLY DEFERRED
TABLE "links_report" CONSTRAINT "links_report_which_reply_id_fkey" FOREIGN KEY (which_reply_id) REFERENCES links_reply(id) DEFERRABLE INITIALLY DEFERRED
_
これは大きなテーブル(2500万行)です。それが動作しているハードウェアは、16コアと60 GBのメモリを備えています。このマシンをpythonアプリと共有しています。しかし、私はサーバーのパフォーマンスを監視しており、そこにボトルネックはありません。
このクエリのパフォーマンスを向上させる方法はありますか?ここにあるすべてのオプション(ある場合)についてアドバイスしてください。
このクエリは例外的に先週までうまくを実行したことに注意してください。それから何が変わったのですか? (別のVMで)DBの_pg_dump
_、次に_pg_restore
_を実行し、Postgresql 9.3.10から9.6.5にアップグレードしました。また、以前にpgbouncer
という接続プーラーを使用しましたが、これは新しいVMにまだ移行していません。これで完了です。
最後に、(ユーザーエクスペリエンスから)先週までに作成されたすべてのグループオブジェクトについて、クエリの実行が高速であることにも気付きました。しかし、現在作成されているすべての新しいオブジェクトは遅いログを生成しています。これは、特に_links_reply_submitted_on
_インデックスを使用した場合のインデックスの問題のようなものですか?
更新:規定された最適化により、状況は一変しました。見てください:
_pg_upgrade
_でメジャーバージョンをアップグレードした後、ANALYZE
を実行する必要があります。テーブル統計はコピーされません。おそらくautovacuum設定も調整してください。
_(which_group_id, submitted_on DESC)
_の複数列インデックスは、このクエリmuchを提供する必要があります。
ノイズをなくし、読みやすくするためのテーブルエイリアスを使用したフォーマット済みクエリ:
_SELECT lr.id, lr.text, lr.which_group_id, lr.writer_id
, lr.submitted_on, lr.image, lr.device, lr.category
, au.id, au.username
, lu.id, lu.user_id, lu.score, lu.avatar
FROM links_reply lr
JOIN auth_user au ON au.id = lr.writer_id
LEFT JOIN links_userprofile lu ON lu.user_id = au.id
WHERE lr.which_group_id = 119287
ORDER BY lr.submitted_on DESC
LIMIT 25;
_
クエリ自体に問題はありません。
(私はそうは思いません。)
これはインデックス作成の問題のようなものですか?
破損が疑われる場合は、REINDEX
を実行します。 マニュアルは次のことを示唆しています:
ユーザーテーブルのインデックスの破損が疑われる場合は、_
REINDEX INDEX
_または_REINDEX TABLE
_を使用して、そのインデックスまたはテーブルのすべてのインデックスを再構築できます。
同時アクセスの場合:ロックは、いくつかの点で、インデックスを最初から削除および再作成することとは異なります。 マニュアル:
REINDEX
は、インデックスの内容が最初から再構築されるという点で、インデックスのドロップと再作成に似ています。ただし、ロックに関する考慮事項はかなり異なります。REINDEX
は、インデックスの親テーブルの書き込みをロックアウトしますが、読み取りはロックアウトしません。また、処理中の特定のインデックスを排他的にロックし、そのインデックスを使用しようとする読み取りをブロックします。対照的に、_DROP INDEX
_は一時的に親テーブルを排他的にロックし、書き込みと読み取りの両方をブロックします。後続の_CREATE INDEX
_は、書き込みをロックアウトしますが、読み取りはロックしません。インデックスが存在しないため、読み取りはそれを使用しようとしません。つまり、ブロックはありませんが、読み取りは高価な順次スキャンに強制される可能性があります。
それでも並行操作の問題が解決しない場合は、 _CREATE INDEX CONCURRENTLY
_ を検討して新しい重複インデックスを作成し、別のトランザクションで古いインデックスを削除してください。
ただし、テーブル統計が実際の問題であるように見えます。クエリプランからの引用:
Links_reply のlinks_reply_submitted_onを使用した後方スキャンインデックス(コスト= 0.44..1,664,030.07 行= 2,001 幅= 50) (実際の時間= 522.811..716.414 rows = 25 loops = 1) フィルター:(which_group_id = 119287) フィルターによって削除された行:1721320
大胆な強調鉱山。 Postgresはこのクエリプランを誤解を招く統計に基づいているように見えます。さらに多くのヒットが予想され、おそらく述語_which_group_id = 119287
_の選択性も過小評価されます。最終的に1.7M行をフィルタリングします。これreeksの不正確なテーブル統計。そして、ありそうな説明もあります:
メジャーバージョンをアップグレードする場合 _pg_upgrade
_ は、既存の統計を新しいバージョンのDBにコピーしません。 _VACUUM ANALYZE
_の後に_pg_upgrade
_または少なくともANALYZE
を実行することをお勧めします。ツールはあなたに思い出させるように促しさえします。 マニュアル:
オプティマイザ統計は_
pg_upgrade
_によって転送されないため、アップグレードの最後にその情報を再生成するコマンドを実行するように指示されます。新しいクラスターに一致するように接続パラメーターを設定する必要がある場合があります。
そうしないと、テーブルへの十分な書き込み(または_CREATE INDEX
_や_ALTER TABLE
_などの他のユーティリティコマンドが統計情報をその場で更新する)によって自動バキュームがトリガーされるまで、テーブルは現在の統計情報なしで移動します。
同じことがダンプ/復元サイクルにも当てはまります(あなたのケースでは_pg_dump
_&_pg_restore
_を使用)。テーブル統計はダンプに含まれません。
テーブルが非常に大きい_(~25M rows)
_。 autovacuumのデフォルト設定では、row_countと固定オフセットの割合としてしきい値を定義します。大きなテーブルではこれがうまく機能しない場合があります。次の自動分析までにかなり時間がかかります。
テーブルまたはDB全体で手動ANALYZE
を実行します。
関連:
...インデックスの問題、特に_
links_reply_submitted_on
_インデックスの場合?
はい、それも。インデックス"links_reply_submitted_on" btree (submitted_on)
は、クエリ内のパターンに対して最適化されていません:
_SELECT ...
FROM links_reply lr
JOIN ...
WHERE lr.which_group_id = 119287
ORDER BY lr.submitted_on DESC
LIMIT 25
_
上記のクエリプランで見たように、Postgresはインデックススキャンを使用して、下からインデックスを読み取り、一致しないものをフィルタリングします。このアプローチは、選択されたすべての(数少ない)_which_group_id
_が最近25行ある場合、かなり高速になる可能性があります。しかし、_which_group_id
_の行の不均一な分布や多くの異なる値に対しては、それほどうまく機能しません。
この複数列のインデックスの方が適しています。
_links_reply__which_group_id__submitted_on btree (which_group_id, submitted_on DESC)
_
これで、Postgresは、データの分布に関係なく、選択した_which_group_id
_の上位25行を選択することができます。
関連:
あなたの観察について:
最後に、(ユーザーエクスペリエンスから)先週までに作成されたすべてのグループオブジェクトについて、クエリの実行が高速であることにも気付きました。しかし、現在作成されているすべての新しいオブジェクトは遅いログを生成しています。
どうして?新しいオブジェクトはまだhave 25エントリではない可能性があるため、Postgresはさらに多くを見つけるために大きなインデックス全体をスキャンし続ける必要があります。これは、古いインデックスとクエリプランでは非常にコストがかかりますが、新しいインデックス(および更新されたテーブル統計)では非常に安価です。
また、正確なテーブル統計があれば、Postgresはおそらく他のインデックス"links_reply_which_group_id" btree (which_group_id)
を使用して、既存の数行をすばやく取得します(25を超える場合はソートします)。しかし、私の新しいインデックスは、いずれにしてもより信頼できるクエリプランを提供します。
テーブルレイアウトの最適化や自動バキューム設定の調整など、他にもさまざまな(マイナーな)ことができますが、この答えはすでに十分長いです。関連:
そして、あなたは後でコメントしました:
無関係なフィールドも削除しました...
実際に必要な列のみを取得することは確かに役立ちます。さらに、それを行います。しかし、ここでは主な問題ではありません。