PostgreSQLの挿入パフォーマンスを高速化する方法


215

Postgresの挿入パフォーマンスをテストしています。データ型が数値の1列のテーブルがあります。それにインデックスもあります。次のクエリを使用してデータベースをいっぱいにしました。

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

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

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


5
質問でもあなたのPgバージョンについて言及する価値があります。この場合、大きな違いはありませんが、多くの質問に対しては違います。
クレイグリンガー、

1
テーブルのインデックスを削除し、もしあればトリガーして、挿入スクリプトを実行します。一括読み込みが完了したら、インデックスを再作成できます。
Sandeep

回答:


481

参照データベースを作成 PostgreSQLのマニュアルでは、depeszの優れた-として、通常の記事で話題にし、このSOの質問

(この回答は、既存のDBへのデータの一括読み込みまたは新しいDBの作成に関するものであることに注意してください。出力でのDB復元パフォーマンスpg_restoreまたは出力のpsql実行に関心がある場合pg_dump、これの多くは適用されずpg_dumppg_restoreすでに作成などのことを行っていますスキーマとデータの復元が完了した後のトリガーとインデックス)

やらなければならないことがたくさんあります。理想的なソリューションはUNLOGGED、インデックスのないテーブルにインポートしてから、ログに変更してインデックスを追加することです。残念ながらPostgreSQL 9.4では、テーブルUNLOGGEDをログからログに変更することはサポートされていません。9.5ではALTER TABLE ... SET LOGGED、これを許可しています。

一括インポートのためにデータベースをオフラインにできる場合は、を使用してくださいpg_bulkload

さもないと:

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

  • インポートを開始する前にインデックスを削除し、後で再作成してください。(同じデータを段階的に追加するよりも、1つのパスでインデックスを構築する方がはるかに短い時間で済み、結果のインデックスははるかにコンパクトになります)。

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

  • 可能な場合は、使用COPYするのではなく、INSERTS

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

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

  • fsync()のコストを削減するために使用synchronous_commit=offcommit_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を最適化することにも興味があるかもしれません。


1
良質のSSDが使用されている場合、RAID 5/6からの書き込みペナルティが多少緩和されることに同意しますか?明らかにペナルティはまだありますが、違いはHDDの場合よりもはるかに少ないと思います。

1
私はそれをテストしていません。私はそれはおそらくそれほど悪くないと思います-厄介な書き込み増幅効果と(小さな書き込みの場合)読み取り-変更-書き込みサイクルの必要性は依然として存在しますが、過度のシークに対する深刻なペナルティは問題ではないはずです。
クレイグリンガー

たとえば、indisvalidpostgresql.org/docs/8.3/static/catalog-pg-index.html)をfalseに設定して、インデックスを削除する代わりに無効にしてから、データをロードしてからインデックスをオンラインにすることはできますREINDEXか?
Vladislav Rastrusny 2014

1
@CraigRinger Perc H730でSSDを使用してRAID-5とRAID-10をテストしました。RAID-5は実際には高速です。また、大きなbyteaと組み合わせた挿入/トランザクションは、コピーよりも高速であるように見えることに注意する価値があるかもしれません。しかし、全体的に良いアドバイスです。
atlaste

2
誰かが何か大きな速度の向上を見ていUNLOGGEDますか?簡単なテストでは、10〜20%の改善が見られます。
serg

15

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

以下は、バイナリ入力でpsycopg2を使用したPythonのレシピです。


1
バイナリモードは、タイムスタンプなど、それらの解析が重要な一部の入力を大幅に節約できます。多くのデータ型では、あまりメリットがありません。または、帯域幅が増加するため(たとえば、小さい整数)、わずかに遅くなることさえあります。それを育てる良い点。
クレイグリンガー、

11

優れたCraig Ringerの投稿とdepeszのブログ投稿に加えて、ODBC(psqlodbc)インターフェースを介して、トランザクション内でPrepared -Statement挿入を使用して挿入を高速化したい場合は、それを作成するために行う必要があるいくつかの追加事項があります。速く働く:

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

残念ながら、psqlodbc SQLBulkOperationsは準備されていない一連の挿入ステートメントを発行して「実装」するため、最速の挿入を実現するには、上記の手順を手動でコーディングする必要があります。


A8=30000000挿入を高速化するために、接続文字列で大きなソケットバッファーサイズを使用する必要があります。
Andrus

9

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

うまくいかなかったすべてのものをリストアップして、肉に直接切り込むことはしません。

ターゲットテーブル(GUID)に主キードロップすると、30MIまたは行が100Kあたり3秒未満の一定の速度で目的の場所に問題なく流れました。


6

UUID(正確にはあなたのケースではありません)で列を挿入して、@ Dennisの回答に追加した場合(まだコメントできません)、gen_random_uuid()を使用するよりもアドバイスしてください(PG 9.4とpgcryptoモジュールが必要です)は(aロット)uuid_generate_v4()より速い

=# explain analyze select uuid_generate_v4(),* from generate_series(1,10000);
                                                        QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series  (cost=0.00..12.50 rows=1000 width=4) (actual time=11.674..10304.959 rows=10000 loops=1)
 Planning time: 0.157 ms
 Execution time: 13353.098 ms
(3 filas)


=# explain analyze select gen_random_uuid(),* from generate_series(1,10000);
                                                        QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series  (cost=0.00..12.50 rows=1000 width=4) (actual time=252.274..418.137 rows=10000 loops=1)
 Planning time: 0.064 ms
 Execution time: 503.818 ms
(3 filas)

また、それはそれを行うための推奨される公式の方法です

注意

ランダムに生成された(バージョン4)UUIDのみが必要な場合は、代わりにpgcryptoモジュールのgen_random_uuid()関数の使用を検討してください。

これにより、370万行の挿入時間が約2時間から約10分に短縮されました。


1

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


-1

この挿入パフォーマンスの問題も発生しました。私の解決策は、挿入作業を完了するためにいくつかのgoルーチンを生成することです。その間、SetMaxOpenConns適切な番号を指定する必要があります。そうしないと、オープン接続エラーが多すぎて警告されます。

db, _ := sql.open() 
db.SetMaxOpenConns(SOME CONFIG INTEGER NUMBER) 
var wg sync.WaitGroup
for _, query := range queries {
    wg.Add(1)
    go func(msg string) {
        defer wg.Done()
        _, err := db.Exec(msg)
        if err != nil {
            fmt.Println(err)
        }
    }(query)
}
wg.Wait()

プロジェクトの読み込み速度ははるかに高速です。このコードスニペットは、それがどのように機能するかを示しています。読者はそれを簡単に変更できるはずです。


まあ、あなたはそれを言うことができます。しかし、私の場合、数百万行の実行時間は数時間から数分に短縮されます。:)
Patrick
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.