web-dev-qa-db-ja.com

PostgreSQLの挿入パフォーマンスをスピードアップする方法

Postgresの挿入性能をテストしています。そのデータ型として数値を持つ1つの列を持つテーブルがあります。それにもインデックスがあります。このクエリを使用してデータベースをいっぱいにしました。

insert into aNumber (id) values (564),(43536),(34560) ...

上記のクエリーを使用して、一度に一度に10,000個の行をすばやく400万行挿入しました。データベースが600万行に達すると、パフォーマンスは15分ごとに100万行に劇的に低下しました。挿入パフォーマンスを向上させるためのトリックはありますか?このプロジェクトでは最適な挿入パフォーマンスが必要です。

5 GBのRAMを搭載したマシンでWindows 7 Proを使用する。

173
Luke101

PostgreSQLマニュアルの データベースへのデータ投入 、トピックについては dependentszの優れた記事 、および this SO question を参照してください。 =。

(この答えは、既存のDBにデータを一括ロードするか、新しいDBを作成することです。pg_restoreまたはpsqlpg_dump出力の実行によるDBリストアのパフォーマンスに関心がある場合は、ほとんど当てはまりません。 pg_dumppg_restoreは、スキーマとデータの復元が終了した後で、既にトリガーやインデックスを作成するようなことをしているので)

やるべきことはたくさんあります。理想的な解決策は、インデックスなしでUNLOGGEDテーブルにインポートし、それをログに変更してインデックスを追加することです。残念ながらPostgreSQL 9.4ではテーブルをUNLOGGEDからloggingに変更することはできません。これを可能にするために9.5ではALTER TABLE ... SET LOGGEDが追加されています。

データベースを一括インポートのためにオフラインにできる場合は、 pg_bulkload を使用します。

さもないと:

  • テーブルのトリガを無効にする

  • インポートを開始する前にインデックスを削除し、後でそれらを再作成します。 (同じデータを徐々に追加するよりも、1回のパスで索引を作成するのにはるか時間がかかりません。その結果得られる索引ははるかにコンパクトになります)。

  • 単一のトランザクション内でインポートを実行する場合は、外部キー制約を削除してインポートを実行し、コミットする前に制約を再作成しても安全です。無効なデータが導入される可能性があるため、インポートが複数のトランザクションに分割されている場合は、これを行わないでください。

  • 可能であれば、COPYsの代わりにINSERTを使用してください。

  • COPYを使用できない場合は、実用的であれば多値INSERTsを使用することを検討してください。あなたはもうこれをやっているようです。リストしようとしないでください一つのVALUESにたくさんの値をこれらの値は2、3回メモリに収まる必要があるため、1ステートメントあたり数百にしてください。

  • 挿入を明示的なトランザクションにまとめて、トランザクションごとに数十万または数百万の挿入を行います。実用的な制限はありませんが、バッチ処理では入力データの各バッチの開始をマークすることでエラーから回復できます。繰り返しますが、あなたはすでにこれをやっているようです。

  • Fsync()のコストを削減するには、synchronous_commit=offと巨大なcommit_delayを使用してください。ただし、作業を大きなトランザクションにまとめた場合、これはあまり役に立ちません。

  • 複数の接続からのINSERTまたはCOPY。いくつがハードウェアのディスクサブシステムに依存します。経験則として、直接接続ストレージを使用する場合は、物理ハードドライブごとに1つの接続が必要です。

  • 高いcheckpoint_segments値を設定して、log_checkpointsを有効にします。 PostgreSQLのログを調べて、チェックポイントが頻繁に発生することについて不満がないことを確認してください。

  • インポート中にシステムがクラッシュした場合にPostgreSQLクラスタ全体(あなたのデータベースと同じクラスタ上の他のすべてのもの)を壊滅的な破損に失うことを気にしないのであれば、Pgを止め、fsync=offを設定し、インポートをしてください。その後、(致命的に)Pgを停止してfsync=onをもう一度設定します。 WAL設定 を参照してください。 あなたのPostgreSQLインストール上のどのデータベースにも気になるデータが既にある場合はこれをしないでください。fsync=offを設定すればfull_page_writes=offも設定できます。繰り返しになりますが、データベースの破損やデータの損失を防ぐために、インポート後に再度有効にしてください。 Pgマニュアルの 非永続的設定 を参照してください。

システムの調整も検討する必要があります。

  • 可能な限り--- 高品質ストレージ用のSSDを使用してください。信頼性の高い、電源保護されたライトバックキャッシュを備えた優れたSSDは、コミットレートを非常に速くします。上記のアドバイスに従うと、ディスクフラッシュ/ fsync()sの数を減らすことができますが、これらはあまり役に立ちませんが、それでも大きな助けになる可能性があります。データを保持することを気にしない限り、適切な停電保護なしで安価なSSDを使用しないでください。

  • 直接接続ストレージにRAID 5またはRAID 6を使用している場合は、ここでやめてください。データをバックアップし、RAIDアレイをRAID 10に再構成して、やり直してください。 RAID 5/6はバルク書き込みパフォーマンスには向いていません - 大きなキャッシュを備えた優れたRAIDコントローラが役に立ちますが。

  • 大容量のバッテリバックアップ式ライトバックキャッシュを備えたハードウェアRAIDコントローラを使用するオプションがある場合、これにより、コミットが多いワークロードの書き込みパフォーマンスを大幅に向上させることができます。 commit_delayで非同期コミットを使用している場合、またはバルクロード中の大きなトランザクションの数が少ない場合は、それほど役に立ちません。

  • 可能であれば、WAL(pg_xlog)を別のディスク/ディスクアレイに保存してください。同じディスク上で別のファイルシステムを使用することにはほとんど意味がありません。人々はしばしばWALにRAID1ペアを使うことを選びます。繰り返しになりますが、これはコミット率が高いシステムではより大きな効果があり、ログを取らないテーブルをデータロードのターゲットとして使用している場合はほとんど効果がありません。

また、に興味があるかもしれません 速いテストのためのPostgreSQLの最適化

426
Craig Ringer

ドキュメントによれば、COPY table TO ... WITH BINARYを使用してください。 " はテキストとCSVのフォーマット よりやや速いです。"何百万もの行を挿入する必要があり、バイナリデータに慣れている場合にのみこれを行ってください。

これは、Pythonで にレシピの例を示したもので、psycopg2とバイナリ入力 を使用しています。

11
Mike T

トランザクション内でプリペアドステートメントの挿入を使用してODBC( psqlodbc )インターフェイスを介して挿入を高速化したい場合は、優れたCraig Ringerの投稿およびdependentszのブログ投稿に加えて、それが速く動くようにするためにあなたがする必要があるいくつかの余分なことがあります:

  1. 接続文字列にProtocol=-1を指定して、エラーのロールバック時のレベルを "Transaction"に設定します。デフォルトでは、psqlodbcは "Statement"レベルを使用します。これはトランザクション全体ではなく各ステートメントに対してSAVEPOINTを作成し、挿入を遅くします。
  2. 接続文字列にUseServerSidePrepare=1を指定して、サーバーサイドの準備済みステートメントを使用します。このオプションを指定しないと、クライアントは挿入される各行とともにinsert文全体を送信します。
  3. SQLSetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>(SQL_AUTOCOMMIT_OFF), 0);を使用して各ステートメントの自動コミットを無効にします
  4. すべての行が挿入されたら、SQLEndTran(SQL_HANDLE_DBC, conn, SQL_COMMIT);を使用してトランザクションをコミットします。トランザクションを明示的に開く必要はありません。

残念ながら、psqlodbcは一連の準備されていないinsert文を発行することによってSQLBulkOperationsを "実装"しているので、最速のinsertを達成するためには上記のステップを手動でコーディングする必要があります。

10

私は今日同じ問題に約6時間を費やしました。インサートは最大5MI(合計30MIのうち)行まで「通常の」速度(100Kあたり3秒未満)で進み、その後パフォーマンスは劇的に低下します(100Kあたり1分まで)。

うまくいかなかったことをすべてリストアップして、肉にまっすぐにカットすることはしません。

ターゲットテーブル上のI 主キーを削除(これはGUIDでした)そして私の30MIか行は100Kあたり3秒以下の一定の速度でそれらの目的地に楽しく流れました。

6
Dennis

最適な挿入パフォーマンスを得るには、それが選択肢である場合はインデックスを無効にします。それ以外に、より良いハードウェア(ディスク、メモリ)も役に立ちます。

1
Icarus