PostgreSQL で更新/置換操作を高速化するにはどうすればよいですか? 質問する

PostgreSQL で更新/置換操作を高速化するにはどうすればよいですか? 質問する

私たちは、PostgreSQL 8.3 をストレージ バックエンドとして使用する特殊なアプリケーションを持っています (Python と psycopg2 を使用)。重要なテーブルに対して実行する操作は、ほとんどの場合、挿入または更新です (削除や選択はほとんどありません)。

正気を保つために、私たちは独自のデータマッパーのようなレイヤーは、かなりうまく機能しますが、更新のパフォーマンスという大きなボトルネックがあります。もちろん、更新/置換シナリオが「空のテーブルへの挿入」シナリオほど高速になるとは期待していませんが、もう少し近づくと良いでしょう。

このシステムは同時更新が不要であることに注意してください

更新時には常に各行のすべてのフィールドを設定します。これは、テストで「置換」という用語を使用していることからもわかります。これまで、更新の問題に対して 2 つのアプローチを試しました。

  1. replace()更新する行の配列を受け取るプロシージャを作成します。

    CREATE OR REPLACE FUNCTION replace_item(data item[]) RETURNS VOID AS $$
    BEGIN
        FOR i IN COALESCE(array_lower(data,1),0) .. COALESCE(array_upper(data,1),-1) LOOP
           UPDATE item SET a0=data[i].a0,a1=data[i].a1,a2=data[i].a2 WHERE key=data[i].key;
        END LOOP;
    END;
    $$ LANGUAGE plpgsql
    
  2. insert_or_replace時々の削除以外はすべて複数行の挿入になるようにルールを作成します

    CREATE RULE "insert_or_replace" AS
        ON INSERT TO "item"
        WHERE EXISTS(SELECT 1 FROM item WHERE key=NEW.key)
        DO INSTEAD
            (UPDATE item SET a0=NEW.a0,a1=NEW.a1,a2=NEW.a2 WHERE key=NEW.key);
    

どちらも更新をかなり高速化しますが、後者は挿入を少し遅くします。

Multi-row insert           : 50000 items inserted in  1.32 seconds averaging 37807.84 items/s
executemany() update       : 50000 items updated  in 26.67 seconds averaging  1874.57 items/s
update_andres              : 50000 items updated  in  3.84 seconds averaging 13028.51 items/s
update_merlin83 (i/d/i)    : 50000 items updated  in  1.29 seconds averaging 38780.46 items/s
update_merlin83 (i/u)      : 50000 items updated  in  1.24 seconds averaging 40313.28 items/s
replace_item() procedure   : 50000 items replaced in  3.10 seconds averaging 16151.42 items/s
Multi-row insert_or_replace: 50000 items inserted in  2.73 seconds averaging 18296.30 items/s
Multi-row insert_or_replace: 50000 items replaced in  2.02 seconds averaging 24729.94 items/s

テスト実行に関するランダムなメモ:

  • すべてのテストは、データベースが存在するのと同じコンピューター上で実行され、localhost に接続されます。
  • 挿入と更新は500項目のバッチでデータベースに適用され、それぞれが独自のトランザクションで送信されます(更新しました)。
  • すべての更新/置換テストでは、データベースにすでに存在する値と同じ値が使用されました。
  • すべてのデータは、psycopg2 の adapt() 関数を使用してエスケープされました。
  • すべてのテーブルは使用前に切り捨てられ、真空にされます(追加した、以前の実行では切り捨てのみが行われました)
  • 表は次のようになります。

    CREATE TABLE item (
        key MACADDR PRIMARY KEY,
        a0 VARCHAR,
        a1 VARCHAR,
        a2 VARCHAR
    )
    

では、本当の質問は、どうすれば更新/置換操作をもう少し高速化できるかということです。(これらの結果は「十分」だと思いますが、SO の仲間に頼らずに諦めたくはありません :)

また、よりエレガントな replace_item() に関するヒントや、私のテストが完全に壊れているという証拠があれば、大歓迎です。

テストスクリプトは利用可能ですここ再現を試みる場合。ただし、最初に確認することを忘れないでください... WorksForMe ですが...

設定に合わせて db.connect() 行を編集する必要があります。

編集

freenode の #postgresql の andres のおかげで、単一クエリ更新を使用した別のテストができました。これは、複数行の挿入 (上記の update_andres としてリストされています) とよく似ています。

UPDATE item
SET a0=i.a0, a1=i.a1, a2=i.a2 
FROM (VALUES ('00:00:00:00:00:01', 'v0', 'v1', 'v2'), 
             ('00:00:00:00:00:02', 'v3', 'v4', 'v5'),
             ...
      ) AS i(key, a0, a1, a2)
WHERE item.key=i.key::macaddr

編集

#postgresql @ freenode の merlin83 と下記の jug/jwp のおかげで、挿入から一時ファイルへの挿入/削除/挿入アプローチを使用した別のテストができました (上記で「update_merlin83 (i/d/i)」としてリストされています)。

INSERT INTO temp_item (key, a0, a1, a2)
    VALUES (
        ('00:00:00:00:00:01', 'v0', 'v1', 'v2'),
        ('00:00:00:00:00:02', 'v3', 'v4', 'v5'),
        ...);

DELETE FROM item
USING temp_item
WHERE item.key=temp_item.key;

INSERT INTO item (key, a0, a1, a2)
    SELECT key, a0, a1, a2
    FROM temp_item;

私の直感では、これらのテストは現実のシナリオでのパフォーマンスをあまり代表していないと思いますが、違いは十分に大きいので、さらに調査を進める上で最も有望なアプローチを示すことができると思います。perftest.py スクリプトには、確認したい方のためにすべての更新も含まれています。ただし、かなり見苦しいので、ゴーグルを忘れないでください :)

編集

#postgresql @ freenode の andres は、挿入から一時ファイル/更新へのバリアント (上記で「update_merlin83 (i/u)」としてリストされている) を使用してテストする必要があることを指摘しました。

INSERT INTO temp_item (key, a0, a1, a2)
    VALUES (
        ('00:00:00:00:00:01', 'v0', 'v1', 'v2'),
        ('00:00:00:00:00:02', 'v3', 'v4', 'v5'),
        ...);

UPDATE item
SET a0=temp_item.a0, a1=temp_item.a1, a2=temp_item.a2
FROM temp_item
WHERE item.key=temp_item.key

編集

おそらく最終編集: 負荷シナリオによりよく一致するようにスクリプトを変更しました。少しスケールアップしてランダム性を追加しても、数値は保持されるようです。他のシナリオで非常に異なる数値が得られた方がいらっしゃいましたら、ぜひお知らせください。

ベストアンサー1

私が PG でこれらのことを実行する通常の方法は、コピー、マージ (楽しい部分)、利益を使用して、ターゲット テーブルに一致する生データを一時テーブル (制約なし) にロードすることです。

私は特にこのような状況のために merge_by_key 関数を作成しました:

詳しくはこちら

ドキュメントはあまり親切ではありませんが、試してみることをお勧めします良い見て。

おすすめ記事