web-dev-qa-db-ja.com

postgresのパーティションと単一のテーブルを使用した場合のメモリオーバーヘッドはありますか?

バックグラウンド

合計で1日あたり約1,000万行の時系列データを取り込みます。各行にはタイムスタンプ、プレーヤーID、その他の列があります。

次に、特定のプレーヤーと時間範囲(たとえば、プレーヤーxxxの過去90日間)のAPIを介してそのデータがクエリされます。ほとんどの場合、複数の同時リクエストが必要です(通常、約〜10の同時リクエストが実行されます)。

私はpostgres 9.6.17を実行しています。マシンには500 GBのHDDスペース(常に約15%の空きスペースがある)、8コア、16 GBのRAMがあります。 work_memは2GBに設定されています、cache_size〜12GB、最大接続数は100などに設定されています。

APIは20 python別のマシンでflaskおよびsqlalchemy + psycopg2を実行しているgunicornワーカーです。各ワーカーには、DBへの2つの接続のプールがあり、 5のオーバーフロー。以前はプール設定が高くなっていますが、プールを使用してもほとんどメリットがないため、数値が低いことがわかりました。

素朴なアプローチ

最初に、すべてのデータを単一のテーブルに入れ、タイムスタンプとプレーヤーの両方でインデックスを作成しました。このアプローチは、データ量が増えて徐々に遅くなるまで(明確な理由により)うまくいきました。これにより、APIワーカーはタイムアウトして500を返します。explainによって返される一般的なクエリのコスト(6か月間のシングルプレーヤーデータ)は、約100万になります(以下の例)

Bitmap Heap Scan on player_data  (cost=515553.98..585514.47 rows=80037 width=32)
   Recheck Cond: (((player_id)::text = 'xxx'::text) AND (ts >= 1572566400) AND (ts < 1574899199))
   ->  BitmapAnd  (cost=515553.98..515553.98 rows=62819 width=0)
         ->  Bitmap Index Scan on idx_player_id  (cost=0.00..12749.35 rows=962837 width=0)
               Index Cond: ((player_id)::text = 'xxx'::text)
         ->  Bitmap Index Scan on idx_ts  (cost=0.00..502778.48 rows=37691480 width=0)
               Index Cond: ((ts >= 1572566400) AND (ts < 1574899199))

パーティションでのより良いアプローチ

改善点として、パーティションにデータを1日1回保存し、代わりにすべてのパーティションにプレーヤーIDとタイムスタンプインデックスを作成しました。これによりクエリ時間が大幅に短縮され、explainでも改善が見られました。

Append  (cost=0.00..85192.02 rows=80037 width=32)
   ->  Seq Scan on player_data  (cost=0.00..0.00 rows=1 width=85)
         Filter: ((ts >= 1572566400) AND (ts < 1574899199) AND ((player_id)::text = 'xxx'::text)
   ->  Index Scan using idx_player_id_20191104 on player_data_20191104  (cost=0.43..317.61 rows=280 width=32)
         Index Cond: ((player_id)::text = 'xxx'::text)
         Filter: ((ts >= 1572566400) AND (ts < 1574899199)
........<continued for a while>................

コストはほぼ1桁低くなり、特に時間単位が小さいクエリの場合、すべてが順調に進んでいました。

問題

しばらくすると、ユーザーはクエリが機能しない、つまりAPIが500エラーを返し始めたと不平を言い始めました。ログを確認した後、次のエラーの多くに気づきました。

sqlalchemy.exc.OperationalError: (psycopg2.errors.OutOfMemory) out of memory
DETAIL:  Failed on request of size NNNN.

NNNNは1から10000までのほぼすべての整数にできます。エラーが発生すると、同時に実行されるすべてのクエリでエラーが発生し、しばらくの間(数分)DBが応答しなくなります。通常、APIサービスを再起動すると、通常の状態に戻ります。 DBが応答しない場合、マシンからのpsql接続試行など、あらゆる種類のクエリとアクセスに対して同じエラーが返されます。

並行して実行されるクエリは、ほとんどが同じインデックスとパーティションにヒットすることにも注意してください。同じ時間範囲の異なるプレーヤーID。クエリは、オフセット/制限値を除いて同一である場合があります。

別のメモ-API経由または同時psqlでの同時実行に関係なく、同じエラーが表示されます。

デバッグと診断

クエリ中に、DBマシン上のリソースを確認しました(メモリ不足になるため)。

  • 利用可能RAM 60%を下回ることはありません
  • スワップは使用されず、ディスクには何も書き込まれません(ただし、postgresが利用可能なメモリを使い果たした場合は、そうする必要があります)
  • テスト中のDBトップへの同時接続数は100で、アイドルは40です。
  • 単一パーティションのインデックスサイズは約5MBで、古いテーブルのインデックスサイズはほぼ1GBでした

また、DBに次の変更を加えました。

  • 増加する work_mem 20MBから2GBへ、改善なし
  • RAM 8GBから12GBに、最終的には16GBに増加、改善なし

また、古いクエリと新しいクエリを比較したところ、次の結果が得られました。

  • パーティション分割されたテーブルに対する10を超える同時クエリにより、エラーが発生した
  • 古い単一のテーブルに対する200の同時クエリは、まったく問題を引き起こしませんでした

質問

だから私が推測する質問は:

  • パーティションを使用すると、メモリのオーバーヘッドが発生し、メモリ割り当ての失敗によりクエリが失敗しますか?
  • インデックスですか?複数のインデックスを使用することは、1つの大きなインデックスを使用するよりも悪いですか?
  • この理論は理にかなっていますか、それとも私は何か明白なものを欠いていますか?
2
Ezekiel Rage

(通常、約〜10の同時リクエストが実行されます)

... RAMの16GB。 work_memは2GBに設定され、

あなたのwork_mem設定は私には骨の折れるようです。 10個のクエリが同時に実行され、それぞれがwork_memの複数のインスタンスを使用する場合があります(特にパーティション分割で一般的です-タイトルの質問に答えるために、はい、あります)、メモリ不足になっても当然です。

クエリ中に、DBマシン上のリソースを確認しました(メモリ不足になるため)。

どうやってやったの?さまざまなツールからのデータは、さまざまな方法で解釈する必要があります。

また、DBに次の変更を加えました。

work_memを20MBから2GBに増やし、改善なし

使用するメモリの量を増やしても、メモリ不足の問題が解決することはほとんどありません。 「暴力は士気が向上するまで続くだろう。」

しかし、work_memが20MBのみに設定されていたときに、まったく同じ問題が発生していたと言っていますか?データベースのログファイルを調べて、問題について直接言ったことを確認しましたか(pythonについて渡されたものとは対照的ですか?)

あなたの説明から、パーティション分割はまったく必要ないかもしれません。 (player_id, ts)のマルチカラムインデックスだけで、パーティショニングの負担とオーバーヘッドを負わずに問題を解決できる可能性があります。

古いデータを削除/アーカイブする予定はありますか、それとも無期限に蓄積されますか?

1
jjanes