合計で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マシン上のリソースを確認しました(メモリ不足になるため)。
また、DBに次の変更を加えました。
work_mem
20MBから2GBへ、改善なしまた、古いクエリと新しいクエリを比較したところ、次の結果が得られました。
だから私が推測する質問は:
(通常、約〜10の同時リクエストが実行されます)
... RAMの16GB。 work_memは2GBに設定され、
あなたのwork_mem設定は私には骨の折れるようです。 10個のクエリが同時に実行され、それぞれがwork_memの複数のインスタンスを使用する場合があります(特にパーティション分割で一般的です-タイトルの質問に答えるために、はい、あります)、メモリ不足になっても当然です。
クエリ中に、DBマシン上のリソースを確認しました(メモリ不足になるため)。
どうやってやったの?さまざまなツールからのデータは、さまざまな方法で解釈する必要があります。
また、DBに次の変更を加えました。
work_memを20MBから2GBに増やし、改善なし
使用するメモリの量を増やしても、メモリ不足の問題が解決することはほとんどありません。 「暴力は士気が向上するまで続くだろう。」
しかし、work_memが20MBのみに設定されていたときに、まったく同じ問題が発生していたと言っていますか?データベースのログファイルを調べて、問題について直接言ったことを確認しましたか(pythonについて渡されたものとは対照的ですか?)
あなたの説明から、パーティション分割はまったく必要ないかもしれません。 (player_id, ts)
のマルチカラムインデックスだけで、パーティショニングの負担とオーバーヘッドを負わずに問題を解決できる可能性があります。
古いデータを削除/アーカイブする予定はありますか、それとも無期限に蓄積されますか?