19

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

合理的な理由から、適度に機能する独自のData Mapperのようなレイヤーを作成しましたが、1 つの大きなボトルネックである更新パフォーマンスがあります。もちろん、更新/置換のシナリオが「空のテーブルへの挿入」のシナリオほど高速になるとは思っていませんが、もう少し近づくといいでしょう。

このシステムには同時更新がないことに注意してください

更新時に各行のすべてのフィールドを常に設定します。これは、テストで「置換」という用語を使用する用語に見られます。私はこれまで、更新の問題に対して 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

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

  • すべてのテストは、データベースが存在するのと同じコンピューターで実行されます。ローカルホストに接続しています。
  • 挿入と更新は、500 アイテムのバッチでデータベースに適用され、それぞれが独自のトランザクション ( UPDATED ) で送信されます。
  • すべての更新/置換テストは、データベースに既に存在していたものと同じ値を使用しました。
  • すべてのデータは、psycopg2 adapt() 関数を使用してエスケープされました。
  • すべてのテーブルは切り捨てられ、使用前にバキュームされます ( ADDED、以前の実行では切り捨てのみが発生しました)
  • テーブルは次のようになります。

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

したがって、本当の問題は次のとおりです。更新/置換操作をもう少しスピードアップするにはどうすればよいですか? (これらの調査結果は「十分」かもしれないと思いますが、SOの群衆をタップせずにあきらめたくありません:)

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

再現したい場合は、ここでテスト スクリプトを入手できます。ただし、最初に確認することを忘れないでください... WorksForMeですが...

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

編集

#postgresql @ freenode の 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 および jug/jwp の merlin83 のおかげで、挿入から一時への/削除/挿入アプローチ (上記の「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 は、insert-to-temp/update バリアント (上記の「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

編集

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

4

6 に答える 6

4

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

これらの状況に特化した merge_by_key 関数を作成しました。

http://mbk.projects.postgresql.org/

ドキュメントはそれほどフレンドリーではありませんが、よく見てみることをお勧めします。

于 2009-06-07T20:01:59.393 に答える
2

私は数ヶ月前に同様の状況にあり、調整されたチャンク/トランザクションサイズから最大の速度ブーストを得ることになりました。また、テスト中にチェックポイントの警告がないかログを確認し、適切に調整することもできます。

于 2009-06-07T18:55:14.100 に答える
2

UPS で WAL (Write Ahead Logging) を使用して、ディスク書き込み間の更新をキャッシュすることでメリットが得られるようです。

wal_buffers この設定は、WAL (先行書き込みログ) が持つことができるバッファの数を決定します。データベースに多数の書き込みトランザクションがある場合、この値をデフォルトよりも少し高く設定すると、ディスク領域の使用率が向上する可能性があります。実験して決める。適切な開始点は、256 ~ 512K のメモリに対応する 32 ~ 64 前後です。

http://www.varlena.com/GeneralBits/Tidbits/perf.html

于 2009-06-07T19:59:32.723 に答える
1

Oracleでは、テーブルをロックすると間違いなく役立ちます。PostgreSQLでも試してみてください。

于 2009-06-07T18:05:02.067 に答える
1

更新の場合、テーブルとインデックスのフィルファクターを下げることができ、それが役立つ場合があります

http://www.postgresql.org/docs/current/static/sql-createtable.html

http://www.postgresql.org/docs/current/static/sql-createindex.html

于 2010-08-19T10:34:02.307 に答える
1

あなたのinsert_or_replace。これを試して:

WHERE EXISTS(SELECT 1 FROM item WHERE key=NEW.key LIMIT 1)

それ以外の

WHERE EXISTS(SELECT 1 FROM item WHERE key=NEW.key)

コメントで述べたように、それはおそらく何もしません。追加する必要があるのは、インデックスを削除することで、いつでも INSERT/UPDATE のパフォーマンスを高速化できるということです。これは、テーブルが過剰にインデックス付けされていることが判明しない限り、やりたいことではない可能性がありますが、少なくともチェックアウトする必要があります。

于 2009-06-07T17:39:09.380 に答える