HN 日本語サマリー

← 一覧へ戻る
インフラ・DevOps

Postgresの障害の背後にある4つの原因

The four horsemen behind Postgres outages (malisper.me)

29 pointsby craigkerstiens12 コメント

要約

Postgresは優れたデータベースですが、本番環境での障害につながる一般的な問題がいくつか存在します。特にデータベース担当者がいないチームでは、VACUUMとトランザクションIDラップアラウンド、接続制限とクエリ並列処理、不適切なクエリプラン、JSONの扱いの4つが主な原因として挙げられます。これらの問題に対処するために、RustでPostgresを再実装するpgrustプロジェクトが進められています。

全文翻訳

Postgresは素晴らしいですが、人々が抱える非常に一般的な問題がいくつかあり、それらはPostgresで容易に障害につながる可能性があります。これらは単なる理論的な問題ではありません。多くのスタートアップと話をしてきた経験から、これらは本番環境で実際に障害を引き起こすものであり、特に専任のデータベース担当者がいないチームでは顕著です。これらが、私のPostgresの再実装であるpgrustで改善したいと考えている4つの主要な領域です。 VACUUMとトランザクションIDラップアラウンド PostgresのVACUUM処理は、おそらくPostgresに関連する障害の最も一般的な原因です。Postgresでは、行を削除しても実際には削除されません。Postgresは行を保持し、削除済みとしてマークします。これは、必要に応じてトランザクションを簡単にロールバックできるようにするなど、多くの理由で行われます。実際に行を削除してスペースを解放するために、PostgresにはVACUUMというバックグラウンドジョブがあり、削除済みとしてマークされた行を物理的に削除します。これがいくつかの問題を引き起こします。 まず、VACUUM自体です。VACUUMはすべてのデータを読み取る必要があるため、大量のI/Oを消費する可能性があります。このI/Oは、クエリが必要とするI/Oと競合し、クエリを遅くする可能性があります。VACUUMの設定を、I/Oの使用量を抑えるように調整しようとすると、遅れが生じ、削除された行がVACUUMがクリーンアップできる速度よりも速く蓄積される可能性があります。VACUUMは、更新および削除されるデータの速度に追いつくのに十分な積極性を持たせつつ、クエリのI/Oと競合するほど速すぎないようにするという、非常に厄介なゲームをプレイする必要があります。 2つ目の問題はトランザクションラップアラウンドです。どの行がライブでどれがデッドかを追跡するために、Postgresはそれらの行にトランザクションIDを付けます。PostgresはトランザクションIDを表すために32ビットIDを使用します。これは、PostgresがトランザクションIDの再利用を開始する前に、最大40億トランザクションしか持てないことを意味します。VACUUMのジョブの1つは、古いトランザクションIDを回収して再利用できるようにすることです。さて、VACUUMがトランザクションIDの回収に遅れすぎると、PostgresはライブトランザクションIDの再利用を防ぐためにデータベースをシャットダウンします。これは馬鹿げているように聞こえるかもしれませんが、そうでなければデータが破損する可能性があるため、必要です。これが原因で発生するPostgresの障害は、数千から数万件に及ぶと私は確信しています。 pgrustがやっていること: 私はこれに対処するために2つの異なる方法を検討しています。Postgresに64ビットトランザクションIDを追加するパッチが存在します。いくつかの理由でマージがためらわれています。以前のPostgresバージョンとの互換性が壊れ、すべての行に32ビットのオーバーヘッドが追加されます。個人的には、これが引き起こした障害の数を考えると、この変更を行う価値は十分にあると思います。私は、VACUUMをまったく必要としない代替アーキテクチャを検討しています。Oracleは、VACUUMを必要としない行の削除マーク付けに、UNDOログとして参照されるまったく異なるアプローチを採用しています。Postgresにこの種のサポートを追加することを検討しているプロジェクトがあります。 接続制限とクエリ並列処理 Postgresインスタンスを起動する際、Postgresが作成できる最大接続数を設定する構成変数があります。Postgresに接続しようとするものが接続制限を超えると、接続できなくなり、データベースはダウンします。接続数を変更するにはデータベースの再起動が必要なため、データベースを最初に作成する際にはこの設定について非常に慎重に考える必要があります。事前にこのことを知らないと、接続制限が低すぎることになる可能性が高いです。多くの人がPgBouncerのようなソフトウェアを実行しており、これが問題の一部を解決しますが、それでも大きな問題です。 そもそもPostgresに接続制限があるのはなぜでしょうか?その答えは、主にPostgresがクエリをどのように並列処理するかにかかっています。Postgres接続を作成するたびに、Postgresは新しいプロセスを作成します。他の並列処理方法と比較して、プロセスはCPUリソースを消費するだけでなく、新しいプロセスを起動するのにかかる時間という点でも非常にコストがかかります。プロセスは、Postgresがクエリ間で並列処理する能力を制限するだけでなく、クエリ内での並列処理能力も制限します。Postgresが単一クエリを並列処理したい場合、Postgresは複数のプロセスを起動し、それぞれにクエリを処理させます。新しいプロセスを起動するのはコストがかかるため、Postgresはこれを長時間実行されるクエリにのみ行います。 Postgresをプロセスモデルからスレッドモデルに切り替えることについて、何年も議論されていますが、具体的な進展はありません。大きな課題の1つは、それが非常に大規模なリファクタリングであり、安全に行うことが非常に難しいことです。 pgrustのやっていること: pgrustは最初からスレッドベースのモデルで設計されました。Postgresがプロセスモデルを使用する大きな理由の1つは安全性です。プロセスはスレッドよりも互いに分離されているため、プロセスで何か問題が発生しても、他のプロセスには影響しません。pgrustはRustで書かれており、Rustはコンパイル時に安全性の保証を提供するため、これはpgrustにとってリスクではありません。 不適切なクエリプラン Postgresのクエリプランナーは非常に賢いですが、扱いにくい場合があります。ご存知ないかもしれませんが、Postgresはクエリを実行するために数十種類のアルゴリズムを使用できます。クエリを実行すると、Postgresはそれを調べ、テーブル内のデータに関する統計情報を調べ、クエリを最も速く実行できるアルゴリズムを特定しようとします。多くの場合、Postgresは正しく判断します。問題は、Postgresが間違った判断をした場合、それは非常にひどいものになる可能性があることです。良いクエリプランと悪いクエリプランの違いは、クエリが10ミリ秒かかるか10分かかるかの違いになり得ます。 さらに悪いことに、Postgresはクエリプランナーに対して意味のある制御を提供しません。つまり、悪いクエリプランが得られた場合、ほとんどお手上げ状態です。悪いクエリプランが得られた場合、唯一の現実的な選択肢は、Postgresのアルゴリズムの一部を完全に無効にすることです。MySQLのような他のデータベースは、「プランナーヒント」を許可しており、特定のクエリの実行方法に影響を与えることができます。Postgresには同様のものはありません。私が以前働いていた会社では、Postgresが常にネストループ結合を使用しようとして、すべてのクエリを信じられないほど遅くしていたため、ネストループ結合をグローバルに無効にする必要がありました。これは、JSONの使用に大きく起因しており、Postgresがデータを理解できなくなったためですが、それについては後ほど説明します。 pgrustが目指していること: これはpgrustのより理論的な部分の1つですが、適応型クエリプランナーを組み込みたいと考えています。アイデアはこうです。通常10ミリ秒かかるクエリが突然10秒かかるようになった場合、pgrustはそのリグレッションを検出し、プランが変更されたかどうか、基になる統計情報がドリフトしたかどうかを確認し、DBAがそれに気づいて手動で介入するのを待つのではなく、修正を行います。これはまだpgrustに組み込まれていませんが、これが存在できない理由は見当たりません。 JSON PostgresはJSONに対して愛憎関係を持っています。非構造化データで作業している場合、PostgresのJSONはデータの保存を非常に容易にし、データのクエリを非常に容易にします。残念ながら、JSONで問題が発生することも非常に簡単です。Heapでは、私たちはJSONを多用しており、それは大きな頭痛の種でした。私のマネージャーはそれについてブログ記事全体を書きました。 最大の課題は、PostgresがJSONの統計情報をキャプチャしないことです。ほとんどの型について、Postgresは大量の統計情報(ヒストグラム、最も一般的な値など)を収集します。JSONについては、Postgresは意味のある統計情報を収集しません。代わりに、PostgresはJSON列でフィルタリングする際に、フィルタが0.1%の行に一致すると推定します。この0.1%は、データに基づいて計算されたものではなく、魔法の数字です。正しい数字は80%であるか、0.0001%である可能性があります。これらすべてが組み合わさって...