PostgreSQL 8.3 をストレージ バックエンドとして使用するかなり特殊なアプリケーションがあります (Python と psycopg2 を使用)。重要なテーブルに対して実行する操作は、ほとんどの場合、挿入または更新です (削除または選択はほとんどありません)。
合理的な理由から、適度に機能する独自のData Mapperのようなレイヤーを作成しましたが、1 つの大きなボトルネックである更新パフォーマンスがあります。もちろん、更新/置換のシナリオが「空のテーブルへの挿入」のシナリオほど高速になるとは思っていませんが、もう少し近づくといいでしょう。
このシステムには同時更新がないことに注意してください
更新時に各行のすべてのフィールドを常に設定します。これは、テストで「置換」という用語を使用する用語に見られます。私はこれまで、更新の問題に対して 2 つのアプローチを試みてきました。
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
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
編集
おそらく最終編集:負荷シナリオによりよく一致するようにスクリプトを変更しました。少しスケールアップしてランダム性を追加しても、数値は保持されるようです。誰かが他のシナリオとは非常に異なる数値を取得した場合、私はそれについて知りたいと思います.