インデックスを使用してpostgresでのソートを高速化する方法


10

私はpostgres 9.4を使用しています。

messagesスキーマは次のとおりです。メッセージはfeed_idに属し、posted_atを持っています。また、メッセージには親メッセージを含めることができます(返信の場合)。

                    Table "public.messages"
            Column            |            Type             | Modifiers
------------------------------+-----------------------------+-----------
 message_id                   | character varying(255)      | not null
 feed_id                      | integer                     |
 parent_id                    | character varying(255)      |
 posted_at                    | timestamp without time zone |
 share_count                  | integer                     |
Indexes:
    "messages_pkey" PRIMARY KEY, btree (message_id)
    "index_messages_on_feed_id_posted_at" btree (feed_id, posted_at DESC NULLS LAST)

で注文したすべてのメッセージを返したいのですshare_countが、ごとparent_idに1つのメッセージだけを返したいのです。つまり、複数のメッセージに同じがある場合parent_id、最新のメッセージ(posted_at)のみが返されます。parent_idnullにすることができ、ヌルを持つメッセージはparent_idすべて返す必要があります。

私が使用したクエリは次のとおりです。

WITH filtered_messages AS (SELECT * 
                           FROM messages
                           WHERE feed_id IN (7) 
                           AND (posted_at >= '2015-01-01 04:00:00.000000') 
                           AND (posted_at < '2015-04-28 04:00:00.000000'))
    SELECT *
    FROM (SELECT DISTINCT ON(COALESCE(parent_id, message_id)) parent_id,
                          message_id, 
                          posted_at, 
                          share_count
          FROM filtered_messages
          ORDER BY COALESCE(parent_id, message_id), posted_at DESC NULLS LAST
         ) messages
    ORDER BY share_count DESC NULLS LAST, posted_at DESC NULLS LAST;

これがhttp://sqlfiddle.com/#!15/588e5/1/0です。SQLFiddleで、スキーマ、正確なクエリ、および期待される結果を定義しました。

ただし、メッセージテーブルが大きくなると、クエリのパフォーマンスが低下します。複数のソートインデックスを追加しようとしましたが、インデックスを使用していないようです。これが説明です:http : //explain.depesz.com/s/Sv2

正しいインデックスを作成するにはどうすればよいですか?


一見するORDER BYと、サブクエリ内のはまったく役に立ちません。さらに、リンクされたプランを投稿されたクエリの結果にすることはできませんmetadata。たとえば、についての言及はありません。
dezso 2015

あなたの説明はの役割をカバーしていないfeed_idposted_at、あなたは言及しなかったmetadataJSONタイプであるように思われ、すべての?一貫性を保つために質問を修正してください。CTEで500,000を超える行を選択します...テーブルには何行ありますか?CTEでは通常、何パーセントの行を選択しますか?行の何パーセントparent_id IS NULLですか?パフォーマンスの質問については、[postgresql-performance]タグの情報を考慮してください。
Erwin Brandstetter 2015

また重要:各行の行数はparent_id?(最小/平均/最大)
Erwin Brandstetter 2015

申し訳ありませんが、いくつかの列を減らすことで質問をより明確にするために、Share_countは実際にはhstoreにありましたmetadata。現在、メッセージテーブルには10 milのデータがありますが、急速に増加しています。各feed_idごとにパーティションテーブルに分けて考えています。フィードIDごとにフェッチしているだけなので。null以外のparent_id nullの割合は、約60%/ 40%です。一般的なフェッチは、テーブルの約1〜2%です。(約100Kメッセージ)100Kのパフォーマンスは約1秒ですが、500K +に達すると、ビットマップインデックスを使用し、通常は10秒かかります。
Zhaohan Weng 2015

回答:


9

クエリ

このクエリは、どの場合でもかなり高速になるはずです。

SELECT parent_id, message_id, posted_at, share_count
FROM   messages
WHERE  feed_id = 7
AND    posted_at >= '2015-01-01 4:0:0'
AND    posted_at <  '2015-04-28 4:0:0'
AND    parent_id IS NULL  -- match index condition
UNION ALL
(
SELECT DISTINCT ON(parent_id)
       parent_id, message_id, posted_at, share_count
FROM   messages
WHERE  feed_id = 7
AND    posted_at >= '2015-01-01 4:0:0'
AND    posted_at <  '2015-04-28 4:0:0'
AND    parent_id IS NOT NULL  -- match index condition
ORDER  BY parent_id, posted_at DESC NULLS LAST
)
ORDER  BY share_count DESC NULLS LAST, posted_at DESC NULLS LAST;
  • ここでは、CTEは何もしませんが、プレーンなサブクエリでも配信できませんでした。また、CTEは個別に実行され、その結果が具体化されるため、最適化バリアが導入されます。

  • 実際に必要なサブクエリレベルがもう1つあります。

  • (COALESCE(parent_id, message_id)はプレーンインデックスと互換性がありません。その式のインデックスが必要です。ただし、データの分布によっては、あまり役に立たない場合もあります。詳細については、以下のリンクをクリックしてください。

  • の単純なケースをparent_id IS NULL個別に分割SELECTすると、最適な結果が得られる場合と得られない場合があります。特にそれがまれなケースである場合は特に、インデックスがオンの組み合わせクエリの(COALESCE(parent_id, message_id)方がパフォーマンスが向上する可能性があります。その他の考慮事項が適用されます...

指数

特にこれらのインデックスでサポートされている場合:

CREATE INDEX messages_idx_null ON messages (
  feed_id
, posted_at DESC NULLS LAST
, share_count DESC NULLS LAST
, parent_id, message_id
)
WHERE parent_id IS NULL;

CREATE INDEX messages_idx_notnull ON messages (
  feed_id
, posted_at DESC NULLS LAST
, share_count DESC NULLS LAST
, parent_id, message_id
)
WHERE parent_id IS NOT NULL;

二つの部分インデックスは、テーブル全体を覆う一緒にし、単一の総インデックスと一緒に同じサイズです。

最後の2つの列parent_id, message_idは、インデックスのみのスキャンを実行する場合にのみ意味があります。そうでない場合は、両方のインデックスからそれらを削除します。

SQL Fiddle。

欠落している詳細に応じてDISTINCT ON、目的に最適なクエリ手法である場合とそうでない場合があります。ここで詳細な説明を読んでください:

そしておそらくここでより速い代替案:

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.