21

クエリ:

SELECT "replays_game".*
FROM "replays_game"
INNER JOIN
 "replays_playeringame" ON "replays_game"."id" = "replays_playeringame"."game_id"
WHERE "replays_playeringame"."player_id" = 50027

を設定SET enable_seqscan = offすると、次のような高速処理が実行されます。

QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Nested Loop  (cost=0.00..27349.80 rows=3395 width=72) (actual time=28.726..65.056 rows=3398 loops=1)
   ->  Index Scan using replays_playeringame_player_id on replays_playeringame  (cost=0.00..8934.43 rows=3395 width=4) (actual time=0.019..2.412 rows=3398 loops=1)
         Index Cond: (player_id = 50027)
   ->  Index Scan using replays_game_pkey on replays_game  (cost=0.00..5.41 rows=1 width=72) (actual time=0.017..0.017 rows=1 loops=3398)
         Index Cond: (id = replays_playeringame.game_id)
 Total runtime: 65.437 ms

しかし、恐ろしい enable_seqscan がなければ、より遅いことを選択します。

QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Hash Join  (cost=7330.18..18145.24 rows=3395 width=72) (actual time=92.380..535.422 rows=3398 loops=1)
   Hash Cond: (replays_playeringame.game_id = replays_game.id)
   ->  Index Scan using replays_playeringame_player_id on replays_playeringame  (cost=0.00..8934.43 rows=3395 width=4) (actual time=0.020..2.899 rows=3398 loops=1)
         Index Cond: (player_id = 50027)
   ->  Hash  (cost=3668.08..3668.08 rows=151208 width=72) (actual time=90.842..90.842 rows=151208 loops=1)
         Buckets: 1024  Batches: 32 (originally 16)  Memory Usage: 1025kB
         ->  Seq Scan on replays_game  (cost=0.00..3668.08 rows=151208 width=72) (actual time=0.020..29.061 rows=151208 loops=1)
 Total runtime: 535.821 ms

関連するインデックスは次のとおりです。

Index "public.replays_game_pkey"
 Column |  Type   | Definition
--------+---------+------------
 id     | integer | id
primary key, btree, for table "public.replays_game"

Index "public.replays_playeringame_player_id"
  Column   |  Type   | Definition
-----------+---------+------------
 player_id | integer | player_id
btree, for table "public.replays_playeringame"

だから私の質問は、Postgres が参加する 2 つの方法の相対的なコストを誤って見積もっているということです。コストの見積もりで、ハッシュ結合が高速になると考えていることがわかります。そして、インデックス結合のコストの見積もりは、500 倍ずれています。

Postgresにもっと手がかりを与えるにはどうすればよいですか? 上記のすべてを実行するVACUUM ANALYZE直前に実行しました。

興味深いことに、ゲーム数が少ないプレイヤーに対してこのクエリを実行すると、Postgres はインデックス スキャン + ネスト ループを実行することを選択します。したがって、多数のゲームについての何かが、相対的な推定コストが実際の推定コストと一致しないこの望ましくない動作をくすぐります。

最後に、Postgres を使用する必要がありますか? 私はデータベース チューニングの専門家になりたいわけではないので、専任の DBA とは対照的に、良心的な開発者レベルの注意を払って適切に機能するデータベースを探しています。私が Postgres を使い続けると、このような問題が絶え間なく発生し、Postgres の専門家にならざるを得なくなるのではないかと心配しています。


Postgres の専門家 (RhodiumToad) が私の完全なデータベース設定 ( http://pastebin.com/77QuiQSp ) をレビューし、 set cpu_tuple_cost = 0.1. それは劇的なスピードアップをもたらしました: http://pastebin.com/nTHvSHVd

または、MySQL に切り替えることで、問題もかなりうまく解決しました。私の OS X ボックスには MySQL と Postgres のデフォルト インストールがあり、MySQL は 2 倍高速であり、クエリを繰り返し実行することによって「ウォームアップ」されたクエリを比較しています。「コールド」クエリ、つまり特定のクエリが初めて実行されるとき、MySQL は 5 ~ 150 倍高速です。コールド クエリのパフォーマンスは、特定のアプリケーションにとって非常に重要です。

私に関する限り、大きな問題はまだ未解決です。Postgres を適切に実行するには、MySQL よりも多くの調整と構成が必要ですか? たとえば、ここでコメンターが提供した提案がどれも機能しなかったと考えてください。

4

4 に答える 4

13

私の推測では、デフォルトを使用していますがrandom_page_cost = 4、これは高すぎるため、インデックス スキャンのコストがかかりすぎます。

このスクリプトを使用して 2 つのテーブルを再構築しようとしています。

CREATE TABLE replays_game (
    id integer NOT NULL,
    PRIMARY KEY (id)
);

CREATE TABLE replays_playeringame (
    player_id integer NOT NULL,
    game_id integer NOT NULL,
    PRIMARY KEY (player_id, game_id),
    CONSTRAINT replays_playeringame_game_fkey
        FOREIGN KEY (game_id) REFERENCES replays_game (id)
);

CREATE INDEX ix_replays_playeringame_game_id
    ON replays_playeringame (game_id);

-- 150k games
INSERT INTO replays_game
SELECT generate_series(1, 150000);

-- ~150k players, ~2 games each
INSERT INTO replays_playeringame
select trunc(random() * 149999 + 1), generate_series(1, 150000);

INSERT INTO replays_playeringame
SELECT *
FROM
    (
        SELECT
            trunc(random() * 149999 + 1) as player_id,
            generate_series(1, 150000) as game_id
    ) AS t
WHERE
    NOT EXISTS (
        SELECT 1
        FROM replays_playeringame
        WHERE
            t.player_id = replays_playeringame.player_id
            AND t.game_id = replays_playeringame.game_id
    )
;

-- the heavy player with 3000 games
INSERT INTO replays_playeringame
select 999999, generate_series(1, 3000);

デフォルト値 4 の場合:

game=# set random_page_cost = 4;
SET
game=# explain analyse SELECT "replays_game".*
FROM "replays_game"
INNER JOIN "replays_playeringame" ON "replays_game"."id" = "replays_playeringame"."game_id"
WHERE "replays_playeringame"."player_id" = 999999;
                                                                     QUERY PLAN                                                                      
-----------------------------------------------------------------------------------------------------------------------------------------------------
 Hash Join  (cost=1483.54..4802.54 rows=3000 width=4) (actual time=3.640..110.212 rows=3000 loops=1)
   Hash Cond: (replays_game.id = replays_playeringame.game_id)
   ->  Seq Scan on replays_game  (cost=0.00..2164.00 rows=150000 width=4) (actual time=0.012..34.261 rows=150000 loops=1)
   ->  Hash  (cost=1446.04..1446.04 rows=3000 width=4) (actual time=3.598..3.598 rows=3000 loops=1)
         Buckets: 1024  Batches: 1  Memory Usage: 106kB
         ->  Bitmap Heap Scan on replays_playeringame  (cost=67.54..1446.04 rows=3000 width=4) (actual time=0.586..2.041 rows=3000 loops=1)
               Recheck Cond: (player_id = 999999)
               ->  Bitmap Index Scan on replays_playeringame_pkey  (cost=0.00..66.79 rows=3000 width=0) (actual time=0.560..0.560 rows=3000 loops=1)
                     Index Cond: (player_id = 999999)
 Total runtime: 110.621 ms

2に下げた後:

game=# set random_page_cost = 2;
SET
game=# explain analyse SELECT "replays_game".*
FROM "replays_game"
INNER JOIN "replays_playeringame" ON "replays_game"."id" = "replays_playeringame"."game_id"
WHERE "replays_playeringame"."player_id" = 999999;
                                                                  QUERY PLAN                                                                   
-----------------------------------------------------------------------------------------------------------------------------------------------
 Nested Loop  (cost=45.52..4444.86 rows=3000 width=4) (actual time=0.418..27.741 rows=3000 loops=1)
   ->  Bitmap Heap Scan on replays_playeringame  (cost=45.52..1424.02 rows=3000 width=4) (actual time=0.406..1.502 rows=3000 loops=1)
         Recheck Cond: (player_id = 999999)
         ->  Bitmap Index Scan on replays_playeringame_pkey  (cost=0.00..44.77 rows=3000 width=0) (actual time=0.388..0.388 rows=3000 loops=1)
               Index Cond: (player_id = 999999)
   ->  Index Scan using replays_game_pkey on replays_game  (cost=0.00..0.99 rows=1 width=4) (actual time=0.006..0.006 rows=1 loops=3000)
         Index Cond: (id = replays_playeringame.game_id)
 Total runtime: 28.542 ms
(8 rows)

SSD を使用している場合は、さらに 1.1 に下げます。

最後の質問ですが、postgresql を使い続ける必要があると思います。私はpostgresqlとmssqlの経験があり、前者の半分のパフォーマンスを得るには、後者に3倍の労力をかける必要があります。

于 2012-05-17T22:29:39.137 に答える
10

次の変更を加えて、sayap の testbed-code (Thanks!) を実行しました。

  • random_page_cost を 8,4,2,1 に設定して、コードを 4 回実行します。その順序で。(cpc=8 は、ディスク バッファ キャッシュを準備することを目的としています)
  • ハードヒッターの割合を減らして (1/2、1/4、1/8) テストを繰り返します (それぞれ: 3K、1K5,750、および 375 ハードヒッター)。残りの記録は変更されません。
  • これらの 4*4 のテストは、work_mem の低い設定 (64K、最小) で繰り返されます。

この実行の後、同じ実行を行いましたが、10 倍にスケールアップしました: 1M5 レコード (30K ハードヒッター) で

現在、私は100倍のスケールアップで同じテストを実行していますが、初期化はかなり遅いです...

結果 セル内のエントリは、ミリ秒単位の合計時間と、選択したクエリプランを示す文字列です。(ほんの一握りの計画しか発生しません)

Original 3K / 150K  work_mem=16M

rpc     |       3K      |       1K5     |       750     |       375
--------+---------------+---------------+---------------+------------
8*      | 50.8  H.BBi.HS| 44.3  H.BBi.HS| 38.5  H.BBi.HS| 41.0  H.BBi.HS
4       | 43.6  H.BBi.HS| 48.6  H.BBi.HS| 4.34  NBBi    | 1.33  NBBi
2       | 6.92  NBBi    | 3.51  NBBi    | 4.61  NBBi    | 1.24  NBBi
1       | 6.43  NII     | 3.49  NII     | 4.19  NII     | 1.18  NII


Original 3K / 150K work_mem=64K

rpc     |       3K      |       1K5     |       750     |       375
--------+---------------+---------------+---------------+------------
8*      | 74.2  H.BBi.HS| 69.6  NBBi    | 62.4  H.BBi.HS| 66.9  H.BBi.HS
4       | 6.67  NBBi    | 8.53  NBBi    | 1.91  NBBi    | 2.32  NBBi
2       | 6.66  NBBi    | 3.6   NBBi    | 1.77  NBBi    | 0.93  NBBi
1       | 7.81  NII     | 3.26  NII     | 1.67  NII     | 0.86  NII


Scaled 10*: 30K / 1M5  work_mem=16M

rpc     |       30K     |       15K     |       7k5     |       3k75
--------+---------------+---------------+---------------+------------
8*      | 623   H.BBi.HS| 556   H.BBi.HS| 531   H.BBi.HS| 14.9  NBBi
4       | 56.4  M.I.sBBi| 54.3  NBBi    | 27.1  NBBi    | 19.1  NBBi
2       | 71.0  NBBi    | 18.9  NBBi    | 9.7   NBBi    | 9.7   NBBi
1       | 79.0  NII     | 35.7  NII     | 17.7  NII     | 9.3   NII


Scaled 10*: 30K / 1M5  work_mem=64K

rpc     |       30K     |       15K     |       7k5     |       3k75
--------+---------------+---------------+---------------+------------
8*      | 729   H.BBi.HS| 722   H.BBi.HS| 723   H.BBi.HS| 19.6  NBBi
4       | 55.5  M.I.sBBi| 41.5  NBBi    | 19.3  NBBi    | 13.3  NBBi
2       | 70.5  NBBi    | 41.0  NBBi    | 26.3  NBBi    | 10.7  NBBi
1       | 69.7  NII     | 38.5  NII     | 20.0  NII     | 9.0   NII

Scaled 100*: 300K / 15M  work_mem=16M

rpc     |       300k    |       150K    |       75k     |       37k5
--------+---------------+---------------+---------------+---------------
8*      |7314   H.BBi.HS|9422   H.BBi.HS|6175   H.BBi.HS| 122   N.BBi.I
4       | 569   M.I.sBBi| 199   M.I.sBBi| 142   M.I.sBBi| 105   N.BBi.I
2       | 527   M.I.sBBi| 372   N.BBi.I | 198   N.BBi.I | 110   N.BBi.I
1       | 694   NII     | 362   NII     | 190   NII     | 107   NII

Scaled 100*: 300K / 15M  work_mem=64K

rpc     |       300k    |       150k    |       75k     |       37k5
--------+---------------+---------------+---------------+------------
8*      |22800 H.BBi.HS |21920 H.BBi.HS | 20630 N.BBi.I |19669  H.BBi.HS
4       |22095 H.BBi.HS |  284 M.I.msBBi| 205   B.BBi.I |  116  N.BBi.I
2       |  528 M.I.msBBi|  399  N.BBi.I | 211   N.BBi.I |  110  N.BBi.I
1       |  718 NII      |  364  NII     | 200   NII     |  105  NII

[8*] Note: the RandomPageCost=8 runs were only intended as a prerun to prime the disk buffer cache; the results should be ignored.

Legend for node types:
N := Nested loop
M := Merge join
H := Hash (or Hash join)
B := Bitmap heap scan
Bi := Bitmap index scan
S := Seq scan
s := sort
m := materialise

暫定的な結論:

  • 元のクエリの「ワーキング セット」が小さすぎます。すべてがコアに収まるため、ページ フェッチのコストが大幅に過大評価されます。RPC を 2 (または 1) に設定すると、この問題は「解決」されますが、クエリがスケールアップされると、ページ コストが支配的になり、RPC=4 は同等またはそれ以上になります。

  • work_mem をより低い値に設定することは、オプティマイザを (ハッシュ + ビットマップ スキャンの代わりに) インデックス スキャンに移行させるもう 1 つの方法です。私が見つけた違いは、Sayap が報告したものよりも小さいものです。多分私はより effective_cache_size を持っていますか、それとも彼はキャッシュをプライミングするのを忘れていましたか?

  • オプティマイザーは、「歪んだ」分布 (および「歪んだ」または「ピーク」の多次元分布) に問題があることが知られています。最初の 3K/150K ハードヒッターの 1/4 と 1/8 を使用したテストランは、この効果が「ピーク」になると消えることを示しています。 "平らになります。
  • 2% 境界で何かが起こります: 3000/150000 は、2% 未満のハードヒッターの場合とは異なる (より悪い) 計画を生成します。これはヒストグラムの粒度でしょうか?
于 2012-05-18T18:28:33.273 に答える
2

(player_id, game_id)テーブルに複数の列のインデックスを使用すると、より適切な実行計画が得られる場合がありreplays_playeringameます。これにより、プレイヤー ID のゲーム ID を検索するためにランダムなページ シークを使用する必要がなくなります。

于 2012-05-17T23:02:51.123 に答える