22.1. 定常的なバキューム作業

PostgreSQLVACUUMコマンドは以下の理由により定期的に実行させる必要があります。

  1. 更新、あるいは削除された行によって占められたディスク領域の復旧または再利用。

  2. PostgreSQL問い合わせプランナによって使用されるデータ統計情報の更新。

  3. トランザクションIDの周回による非常に古いデータの損失を防止。

上述の理由それぞれを目的として実行されるVACUUMの頻度や適用範囲は各サイトの必要性によって変わります。 したがって、データベース管理者はこれらの問題を理解し、適切な保守計画を構築しなければなりません。 本節は、高度な問題を説明することに専念していますので、コマンドの構文などの詳細については、VACUUMのリファレンスページを参照してください。

VACUUMの標準構文は、実運用データベースに対する操作と並行して実行させることができます。 SELECTINSERTUPDATEDELETEなどのコマンドは通常通りに動作し続けます。 しかし、バキューム処理中はALTER TABLE ADD COLUMNなどのコマンドを使用してテーブル定義を変更することはできません。 また、VACUUMには、かなりの量のI/Oトラフィックスが必要です。 このため、他の実行中のセッションの性能を劣化させる場合があります。 バックグラウンドで実行されるバキューム処理による性能への影響を軽減させることを調整できるような設定パラメータがあります。 項17.4.4を参照してください。

自動的に必要なVACUUM操作を実行する機構がPostgreSQL 8.1で追加されました。 項22.1.4を参照してください。

22.1.1. ディスク容量の復旧

通常のPostgreSQLの操作では、行のUPDATEもしくはDELETEは古い行を即座に削除しません。 この方法は、多版同時性制御(第12章を参照してください)の恩恵を受けるために必要なものです。 あるバージョンの行は他のトランザクションから参照される可能性がある場合は削除されてはなりません。 しかし、結局は、更新される前の行や削除された行を参照するトランザクションはなくなります。 必要なディスク容量が無限大にならないように、これらが占める領域は、新しい行で再利用できるように回収されなければなりません。 これはVACUUMを実行することで行われます。

はっきり言って、頻繁に更新、削除されるテーブルは、滅多に更新されないテーブルよりもより頻繁にバキュームを行う必要があります。 変更頻度がないことがわかっているテーブルを除く、限定したテーブルに対してVACUUMを行う定期的なcron処理を設定することは有益なものになるかもしれません。 これは、巨大な、更新頻度が高いテーブルと巨大な更新頻度が低いテーブルの両方が存在する場合にのみ有益です。 小さなテーブルのバキューム処理のコストは考慮する必要はありません。

VACUUMコマンドには2種類あります。 1つ目の形式は"怠惰なバキューム"や単なるVACUUMと呼ばれるもので、テーブルやインデックス内の不要なデータに対して将来再利用できるように印を付けます。 不要データによって使用されている領域は、その領域がテーブルの最後にある場合、または、排他的テーブルロックが簡単に入手できる場合を除き、回収されません。 ファイルの先頭や中間の不要な領域によるファイルの縮小は行われず、ファイル内の未使用領域はオペレーティングシステムに返却されません。 この種のVACUUMは、普通のデータベース操作と同時に実行することができます。

2つ目の形式はVACUUM FULLコマンドです。 これは、より積極的なアルゴリズムを使用して不要になったバージョンの行が占める領域の回収を行います。 VACUUM FULLで解放された領域は全て即座にオペレーティングシステムに返却されます。 残念ながら、この種のVACUUMコマンドでは、VACUUM FULLが処理中のテーブルごとに明示的なロックが獲得されます。 したがって、VACUUM FULLを頻繁に使用すると、同時に実行されるデータベース問い合わせの性能をかなり下げてしまいます。

標準形式のVACUUMは、ディスク容量を安定状態の使用量のレベルで維持することを目的に最もよく使用されます。 ディスク容量をオペレーティングシステムに返却する必要がある場合は、VACUUM FULLコマンドを使用してください。 しかし、すぐに再度割り当てる必要があるディスク容量を解放することにどんな意味があるでしょうか? 更新頻度の激しいテーブルの保守においては、不定期のVACUUM FULLよりも適切な頻度で標準のVACUUMの方がより良い方法です。

ほとんどのサイトで推奨できる方法は、データベース全体のVACUUMを 1日1回使用頻度が低い時間帯にスケジュールすることです。 必要ならば、更新頻度の激しいテーブルのバキューム処理をより頻繁に行うよう追加してください (非常に高い頻度でデータの更新を行うインストレーションの中では、分間隔位という頻度で高負荷なテーブルのVACUUMを行うこともあります)。 1つのクラスタで複数のデータベースがある場合、それぞれをバキュームすることを忘れないでください。 vacuumdbプログラムが役に立つかもしれません。

VACUUM FULLは、テーブル内のほとんどの行を削除したことが判明している場合に推奨します。 その安定状態のテーブルサイズをVACUUM FULLのより積極的な方式によって大いに縮小できるからです。 容量の復旧のための定常的なバキューム処理には、VACUUM FULLではなく、普通のVACUUMを使用してください。

テーブルの内容が定期的に完全に削除される場合、DELETEの後にVACUUMを使用するよりも、TRUNCATEを使用する方が良いでしょう。 TRUNCATEはテーブルの全ての内容を即座に削除します。 また、その後に不要となったディスク容量を回収するためにVACUUMVACUUM FULLを行う必要がありません。

22.1.2. プランナ用の統計情報の更新

PostgreSQL問い合わせプランナは、優れた問い合わせ計画を作成するのに、テーブルの内容に関する統計情報に依存しています。 この統計情報はANALYZEによって収集されます。 このコマンドはそのものを呼び出す以外にも、VACUUMのオプション処理としても呼び出すことができます。 合理的な精度の統計情報を持つことは重要です。 さもないと非効率的な計画を選択してしまい、データベース性能を悪化させてしまいます。

領域復旧のためのバキューム処理と同様、頻繁な統計情報の更新は、滅多に更新されないテーブルよりも更新の激しいテーブルにとってより有益です。 しかし、頻繁に更新されるテーブルであっても、データの統計的な分布が大きく変更されなければ、統計情報を更新する必要はありません。 単純な鉄則は、テーブル内の列の最小値、最大値にどのくらいの変化があったかを考えることです。 例えば、行の更新時刻を保持するtimestamp列の場合、最大値は行が追加、更新されるにつれて、単純に増加します。 こういった列は、おそらく、例えば、あるWebサイト上のアクセスされたページのURLを保持する列よりも頻繁に統計情報を更新する必要があるでしょう。 このURL列の更新頻度も高いものかもしれませんが、その値の統計的な分布の変更は相対的に見ておそらく低いものです。

特定のテーブルに対してANALYZEを実行することができます。 また、テーブルの特定の列のみに対してさえも実行することができます。 ですので、アプリケーションの要求に応じて、他よりも頻繁に一部の統計情報を更新できるような柔軟性があります。 しかし、実際には、操作が高速であるため、単にデータベース全体を解析することが最善です。 解析では、すべての行を読むのではなく、テーブルからランダムに行を抽出して統計処理に使用します。

ティップ: 列単位でのANALYZE実行頻度の調整は非常に実用的とは言えるものではありませんが、ANALYZEで集計される統計情報の詳細レベルの調整を列単位で行うことは価値がある場合があります。 WHERE句でよく使用され、データ分布の規則性がほとんどない列は、他の列よりもより細かいデータの度数分布が必要になるでしょう。 ALTER TABLE SET STATISTICSを参照してください。

ほとんどのサイトで推奨できる方法は、1日1回使用頻度の低い時間帯に、データベース全体に対してANALYZEをスケジュールすることです。 通常は、毎晩のVACUUMと組み合わせることができます。 しかし、テーブルの統計情報の変更が相対的に遅いサイトでは、過剰であるかもしれません。 より低い頻度でANALYZEを実行することで十分です。

22.1.3. トランザクションIDの周回エラーの防止

PostgreSQLのMVCCトランザクションのセマンティックは、トランザクションID(XID)番号の比較が可能であることに依存しています。 現在のトランザクションのXIDよりも新しい挿入時のXIDを持ったバージョンの行は、"未来のもの"であり、現在のトランザクションから可視であってはなりません。 しかし、トランザクションIDのサイズには制限(執筆時点では32ビット)があり、長時間(40億トランザクション)稼働しているクラスタはトランザクションの周回を経験します。 XIDのカウンタが一周して0に戻り、そして、突然に、過去になされたトランザクションが将来のものと見えるように、つまり、その出力が不可視になります。 端的に言うと、破滅的なデータの損失です (実際はデータは保持されていますが、それを入手することができなければ、慰めにならないでしょう)。 これを防ぐためには、すべてのデータベースにあるすべてのテーブルを少なくとも20億トランザクションごとにバキュームする必要があります。

定期的なバキューム処理によりこの問題が解決する理由は、PostgreSQLが特別なXID、FrozenXIDを区別しているためです。 このXIDは常に全ての通常のXIDよりも古いものとみなされます。 通常のXID(2以上の値)はmodulo-231という数式を使用して比較されます。 これは、全ての通常のXIDでは、20億の"より古い"XIDと20億の"より新しい"XIDが存在することを意味します。 言い換えると、通常のXID空間は終わることなく循環されているということです。 そのため、ある特定のXIDであるバージョンの行を作成すると、そのバージョンの行は、以降の20億トランザクションからはどの通常のXIDについて比較しているのかには関係なく、 "過去のもの"と認識されます。 そのバージョンの行が20億トランザクション以上後にも存在していた場合、それは突然に未来のものとして認識されます。 このデータ損失を防ぐために、20億トランザクションより古いとみなされるより、少し前に古いバージョンの行のXIDをFrozenXIDに再割り当てする必要があります。 この特殊なXIDに割り当てられた後は、周回問題に関係なく、全ての通常のトランザクションから"過去のもの"として認識され、また、そのバージョンの行はどれだけ古いものであろうと、削除されるまで好ましい状態となります。 この古いXIDの再割り当てはVACUUMで扱われます。

VACUUMの動作は、設定パラメータvacuum_freeze_min_ageにより制御されます。 vacuum_freeze_min_ageトランザクションより古いXIDはすべて、FrozenXIDで置換されます。 より大きなvacuum_freeze_min_ageによりトランザクション情報が長期間保持されます。 一方で、この値を小さくすることで、テーブルを次にバキュームする必要が起こるまで継続できるトランザクション数を増加させることができます。

テーブルをバキュームすることなく処理できる最大の時間は、20億トランザクションから前回のバキュームの際に使用されたvacuum_freeze_min_ageを差し引いたものです。 この時間よりも長期間バキュームを行わないと、データ損失が発生します。 これを確実に防止するために、項22.1.4で説明する自動バキューム機能が、autovacuum_freeze_max_age設定パラメータで指定された時代より古いXIDを持つ可能性がある任意のテーブルに対して呼び出されます。 (これは自動バキュームが無効であっても起こります。)

これは、 あるテーブルがバキュームされていなかったとしても、自動バキュームがおよそautovacuum_freeze_max_age - vacuum_freeze_min_ageトランザクション毎に呼び出されることを意味します。 領域確保のために定常的にバキューム処理を行うテーブルでは、これは重要ではありません。 しかし、(挿入のみで更新や削除が行われないテーブルを含む)静的なテーブルでは、領域確保のためのバキューム処理を行う必要がなくなりますので、非常に長期間静的なテーブルでは、強制的な自動バキューム間の間隔を最大まで延ばすことができます。 記載するまでもありませんが、autovacuum_freeze_max_ageを増やすことでもvacuum_freeze_min_ageを減らすことでも、これを行うことができます。

autovacuum_freeze_max_ageを増やすことには1つ欠点があります。 それは、データベースクラスタのサブディレクトリpg_clogがより大きな容量となることです。 autovacuum_freeze_max_ageの範囲まですべてのトランザクションのコミット状況を格納しなければならないためです。 コミット状況は1トランザクション当たり2ビット使用しますので、もしautovacuum_freeze_max_ageがその最大許容値である2億よりすこし少ない値に設定している場合、pg_clogはおよそ0.5ギガバイトまで膨らむものと考えられます。 これがデータベースサイズ全体に対してとるに足らないものであれば、autovacuum_freeze_max_ageを最大許容値に設定することを勧めます。 さもなければ、pg_clogの容量として許容できる値に応じて設定してください。 (デフォルトは2000万トランザクションです。換算するとpg_clogはおよそ50メガバイトの容量となります。)

vacuum_freeze_min_age を減らすことにも1つ欠点があります。 これによりVACUUMが大して役に立たなくなるかもしれません。 テーブル行がすぐに変更される場合(新しいXIDを獲得することになります)、テーブル行のXIDをFrozenXIDに変更することは時間の無駄です。 そのため、この設定は、行の変更が起こらなくなるまで凍結されない程度に大きくすべきです。 この設定を減らすことによる他の欠点として、行を挿入または変更したトランザクションに関する情報がすぐに失われることです。 この情報は、特に、データベース障害の後、何がうまくいかなかったのかを解析する場合、便利になることがあります。 この2つの理由により、この設定を減らすことは完全に静的なテーブルでない限りお勧めしません。

データベース内のもっとも古いXIDの年代を追跡するために、VACUUMはシステムテーブルpg_classpg_databaseにXID統計情報を保持します。 特に、テーブルpg_class行のrelfrozenxid列には、テーブルに対する最後のVACUUMで使用された凍結切捨てXIDが含まれます。 この切り捨てXIDよりも古い、全ての通常のXIDはそのテーブルのFrozenXIDによって置換されていることが保証されています。 同様に、データベースのpg_database行のdatfrozenxid列は、データベース内で現れる通常のXIDの下限値です。 これは、そのデータベース内のテーブル当たりのrelfrozenxid値の最小値です。 この情報を検査する簡便な方法は、以下の問い合わせを実行することです。

SELECT relname, age(relfrozenxid) FROM pg_class WHERE relkind = 'r';
SELECT datname, age(datfrozenxid) FROM pg_database;

age列は切り捨てXIDから現在のトランザクションXIDまでのトランザクション数を測ります。 VACUUM直後、age(relfrozenxid)は、使用されたvacuum_freeze_min_age設定より若干大きくなるはずです(VACUUMを起動してから始まったトランザクションの数分大きくなります)。 age(relfrozenxid)autovacuum_freeze_max_ageを越える場合、そのテーブルに対する自動バキュームがすぐに強制されます。

何らかの理由により自動バキュームがテーブルの古いXIDの整理に失敗した場合、システムはデータベースの最古のXIDが周回ポイントから1000万トランザクションに達した場合と似たような警告メッセージを発行し始めます。

WARNING:  database "mydb" must be vacuumed within 177009986 transactions
HINT:  To avoid a database shutdown, execute a full-database VACUUM in "mydb".

こうした警告も無視し続け、周回するまでのトランザクションが100万より少なくなると、システムは停止し、新しいトランザクションの実行を拒絶します。

ERROR:  database is shut down to avoid wraparound data loss in database "mydb"
HINT:  Stop the postmaster and use a standalone backend to VACUUM in "mydb".

この100万トランザクションという安全マージンは、管理者が必要なVACUUMコマンドを手作業で実行することで、データを失うことなくリカバリすることができるようにするために存在します。 しかし、システムがこの安全のための停止モードになると、コマンドを実行しませんので、 実行するためには、サーバを停止し、シングルユーザモードのバックエンドを使用してVACUUMを行う他ありません。 停止モードはシングルユーザモードのバックエンドでは強制されません。 シングルユーザモードのバックエンドの使用に関する詳細はpostgresマニュアルページを参照してください。

22.1.4. 自動バキュームデーモン

PostgreSQL 8.1から、autovacuum デーモンという独立した、省略可能なサーバプロセスが存在します。 このデーモンは、VACUUMANALYZE コマンドの実行を自動化することを目的としたものです。 有効にすると、autovacuumデーモンは周期的に実行され、大量のタプルの挿入、更新、削除があったテーブルを検査します。 この検査は、行レベルの統計情報収集機能を使用します。 したがって、stats_start_collectorおよびstats_row_leveltrueに設定されていないと、 autovacuumデーモンを使用することができません。 また、superuser_reserved_connectionsの値を検討する時に、autovacuumプロセス用のスロットを許可することも重要です。

autovacuumデーモンが有効の場合、autovacuumデーモンはautovacuum_naptime秒毎に実行されます。 実行する度に、処理するデータベースを1つ選択し、そのデータベース内の各テーブルを検査します。 VACUUMまたはANALYZEコマンドが必要に応じて呼び出されます。

テーブルのrelfrozenxid値がautovacuum_freeze_max_ageトランザクションよりも古い場合、そのテーブルは常にバキュームされます。 さもなければ、どの操作を行うかを決める時に、2つの条件が使用されます。 直前のVACUUMの後に不要となったタプル数が"バキューム閾値"を超えると、テーブルはバキュームされます。 このバキューム閾値は以下のように定義されます。

バキューム閾値 = バキューム基礎閾値 + バキューム規模係数 * タプル数

ここで、バキューム基礎閾値はautovacuum_vacuum_threshold、バキューム規模係数はautovacuum_vacuum_scale_factor、タプル数はpg_class.reltuplesです。 不要となったタプル数は、統計情報コレクタから取り出されます。 これは、各UPDATEDELETE操作で更新される、ほぼ正確な数です。 (負荷が高いと一部の情報が失われる可能性があることから、これはほぼ正確な数でしかありません。) 解析の場合も似たような条件が使用されます。 以下で定義される閾値が、直前のANALYZEの後に挿入、更新、削除されたタプル数と比較されます。

解析閾値 = 解析基礎閾値 + 解析規模係数 * タプル数

デフォルトの閾値と規模係数は、postgresql.confから取られますが、pg_autovacuumシステムカタログに項目を用意することで、テーブル毎に上書きすることができます。 特定のテーブルに対してpg_autovacuum行が存在する場合、そこで指定した設定が適用されます。 さもなければ、全体的な設定が使用されます。 全体的な設定に関する詳細は項17.9を参照してください。

基礎閾値と規模係数の他に、pg_autovacuumで5つのパラメータを各テーブルに対して設定することができます。 1つ目はpg_autovacuum.enabledで、これをfalseに設定することでautovacuumデーモンはこのテーブル全体を対象からはずします。 この場合autovacuumは、トランザクションIDの周回を防ぐためにバキュームする必要がある時のみ、このテーブルを対象とします。 次の2つのパラメータ、バキュームコスト遅延(pg_autovacuum.vac_cost_delay)とバキュームコスト上限(pg_autovacuum.vac_cost_limit)です。 これらは、コストに基づくVacuum遅延機能用のテーブル固有の値を設定するために使用されます。 最後の2つのパラメータ(pg_autovacuum.freeze_min_age)およびpg_autovacuum.freeze_max_age)は、 それぞれテーブル指定のvacuum_freeze_min_ageautovacuum_freeze_max_age用の値です。

pg_autovacuum内の値をいずれかの負が設定された場合、あるいは、特定のテーブル用のpg_autovacuumが存在しない場合、postgresql.conf内の対応する値が使用されます。

現時点では、pg_autovacuumカタログに項目を作成するには、手作業でカタログにINSERTするしかありません。 この機能は今後のリリースで改良される予定です。 また、カタログの定義も変更される予定です。

注意

現在、pg_autovacuumシステムカタログの内容は、pg_dumpツールやpg_dumpallツールで作成されたデータベースダンプに保存されません。 ダンプ/リロードの過程でこれらを保持したい場合、このカタログを手作業で確実にダンプしてください。