0

PHP と postgres バックエンド (9.1) で動作する Web アプリケーションがあります。

重い DB リフティング作業のほとんどは、postgres ストアド プロシージャを介して行われます。

アプリ内のプロセスの 1 つは、データのインポート ルーチンです。ストアド プロシージャはインポートに非常に集中していますが、開発中にテスト シート (約 20 行のデータ) を約 15 秒でインポートできます。

これは、ローカル デスクトップの 4 コアの ubuntu VM で、デフォルトの postgres 構成 (VM に 1GB の RAM が割り当てられている) で実行されます。私のCPUはIntel i7です。

ローカル マシンで pg_top を使用しましたが、SELECT プロセスは約 60% の CPU 使用率で急増し、15 秒以内に終了します。

そのため、アプリを 1and1 のプロフェッショナル サーバーであるライブ環境にデプロイしました。32 コア、64 GB の RAM、2 TB のハード ドライブ。非常に高価で非常に大きな数です!

現在、ライブ サーバーで同じインポート ルーチンを実行すると 6 分以上かかり、postgres SELECT ステートメントを 100% CPU で実行すると約 6 分かかります。

私はpostgres conf設定の多くを経験し、より高性能なボックスに一致するようにメモリ番号を増やしましたが、何を変更しても、非常に低いパフォーマンスに影響を与えることはないようです.

クエリのパフォーマンスが大幅に低下する理由を知っている人はいますか?

1GB RAM の 4 コア VM でわずか 15 秒

ただし、64 GB の RAM を搭載した 32 コアの専用サーバーでは 6 分

線に沿って何かが明らかに台無しになっていますが、それが何であるかはわかりません:(

編集:

わかりました、これは私が問題を特定したと思うクエリです(小さなデータセットでは10ミリ秒ではなく、大きなデータセットでは50/60ミリ秒かかります)

EXPLAIN UPDATE artwork_entity SET "updated_on"=NOW(), "category"='blah', "category:oid"=47425 
WHERE artwork_entity."id" IN (
SELECT n."id" FROM (
SELECT e."id" FROM artwork_entity e
WHERE e."id"=47425 OR e."id" IN
(SELECT l."descendant_id" FROM artwork_relation l
LEFT JOIN artwork_entity e1 ON l."descendant_id"=e1."id"
WHERE l."depth">0 AND l."ancestor_id"=47425
AND (e1."category:oid"=(SELECT e2."category:oid" FROM artwork_entity e2 WHERE e2."id"=l."ancestor_id") OR e1."category:oid" IS NULL))
) AS n);




Update on artwork_entity  (cost=3864.35..7743.46 rows=21118 width=451)"
  ->  Hash Semi Join  (cost=3864.35..7743.46 rows=21118 width=451)"
        Hash Cond: (artwork_entity.id = e.id)"
        ->  Seq Scan on artwork_entity  (cost=0.00..3364.36 rows=42236 width=445)"
        ->  Hash  (cost=3600.38..3600.38 rows=21118 width=10)"
              ->  Seq Scan on artwork_entity e  (cost=24.84..3600.38 rows=21118 width=10)"
                    Filter: ((id = 47425) OR (hashed SubPlan 2))"
                    SubPlan 2"
                      ->  Nested Loop Left Join  (cost=0.00..24.83 rows=1 width=4)"
                            Filter: ((e1."category:oid" = (SubPlan 1)) OR (e1."category:oid" IS NULL))"
                            ->  Index Scan using artwork_relation_ancestor_id_descendant_id_key on artwork_relation l  (cost=0.00..8.28 rows=1 width=8)"
                                  Index Cond: (ancestor_id = 47425)"
                                  Filter: (depth > 0)"
                            ->  Index Scan using artwork_entity_pkey on artwork_entity e1  (cost=0.00..8.27 rows=1 width=8)"
                                  Index Cond: (l.descendant_id = id)"
                            SubPlan 1"
                              ->  Index Scan using artwork_entity_pkey on artwork_entity e2  (cost=0.00..8.27 rows=1 width=4)"
                                    Index Cond: (id = l.ancestor_id)"

また、このクエリは、どの列にもインデックスを追加せずに実行されました。

また、内部の select ステートメントは、大規模なデータセットで実行するのに約 10/20 ミリ秒しかかからないことに注意してください (したがって、更新に違いありませんか?) 利用可能な大量の行のうち 2 行のみを更新しています。

編集2:

EXPLAIN SELECT e."id" FROM artwork_entity e
WHERE e."id"=47425 OR e."id" IN
(
SELECT l."descendant_id" FROM artwork_relation l
LEFT JOIN artwork_entity e1 ON l."descendant_id"=e1."id"
WHERE l."depth">0 AND l."ancestor_id"=47425
AND (e1."category:oid"=(SELECT e2."category:oid" FROM artwork_entity e2 WHERE e2."id"=l."ancestor_id") OR e1."category:oid" IS NULL)
)

次に、シーケンス スキャン用に 21k 行の取得を試みます。

しかし、それを次のように 2 つの個別のクエリに分割すると、次のようになります。

EXPLAIN SELECT e."id" FROM artwork_entity e
WHERE e."id"=47425

これは1行のみを取得し、次にクエリの他の部分を取得します

EXPLAIN SELECT l."descendant_id" FROM artwork_relation l
LEFT JOIN artwork_entity e1 ON l."descendant_id"=e1."id"
WHERE l."depth">0 AND l."ancestor_id"=47425
AND (e1."category:oid"=(SELECT e2."category:oid" FROM artwork_entity e2 WHERE e2."id"=l."ancestor_id") OR e1."category:oid" IS NULL)

また、1 行しか取得しませんが、2 番目のクエリが in の一部である場合、21k 行すべてを取得しようとします。

どうして?

編集3:

初期スキャンで 21,000 行を返すステートメントを次のように簡素化しました。

EXPLAIN SELECT e."id" FROM artwork_entity e
WHERE e."id"=47425 OR e."id" IN
(
SELECT l."descendant_id" FROM artwork_relation l
WHERE l."depth">0 AND l."ancestor_id"=47425
)

別々に実行すると、どちらも単一の行が返されますが、一緒に追加すると、データセット全体がクエリされます。

4

1 に答える 1

1

わかりました、私はそれを行うより速い方法を見つけました:

こうするよりも

WHERE e."id"=47425 OR e."id" IN (...)

OR ステートメントを削除して、次の IN ステートメントを実行するだけです。

SELECT e."id" FROM artwork_entity e
WHERE e."id" IN
(
SELECT l."descendant_id" FROM artwork_relation l
LEFT JOIN artwork_entity e1 ON l."descendant_id"=e1."id"
WHERE l."depth">=0 AND l."ancestor_id"=47425
AND (e1."category:oid"=(SELECT e2."category:oid" FROM artwork_entity e2 WHERE e2."id"=l."ancestor_id") OR e1."category:oid" IS NULL)
)

違いは、IN ステートメントが depth>=0 であり、depth>0 ではないことです。その理由は、実際にはエンティティへの自己参照関係を深さ 0 で格納しているためです。このストアド プロシージャを作成した後にこの方法を追加したと思うので、当時は利用できませんでした。

とにかく、それを行うと、正しい行のみが検索され、結果ははるかに高速なクエリになります (60 ミリ秒ではなく 12 ミリ秒)。

ただし、この回答はおそらく他の人には役に立ちません。

編集:

これが私の解決策だと言いましたが、全体としてははるかに優れています。

ただし、現在、ライブ サーバーではインポートに 50 秒かかります (6 分ではなく) が、ローカル VM ではまだはるかに高速です (同じデータセットで 12 秒)。

postgres は (ライブ サーバーまたはローカル サーバーで) CPU 使用率が 30% を超えることはありませんが、何らかの理由で、低電力のローカル VM ではより高速です。

不足しているもの、または構成に関して注意すべきことはありますか?

于 2013-02-23T23:17:47.133 に答える