第 21章定常的なデータベース保守作業

目次
21.1. 定常的なバキューム作業
21.1.1. ディスク容量の復旧
21.1.2. プランナ用の統計情報の更新
21.1.3. トランザクション ID の周回エラーの防止
21.2. 定常的なインデックスの再作成
21.3. ログファイルの保守

PostgreSQL サーバの円滑な稼働を維持するために定期的に行なわなければならない、定常的な保守作業があります。 ここで説明する作業は繰返し行なうべきものであり、また、cron スクリプトなどの標準的な Unix ツールを使用して簡単に自動化することができます。 しかし、適切なスクリプトを設定し、その実行の成功を点検することは、データベース管理者の責任です。

明らかに必要な保守作業の1つに、定期的なデータのバックアップコピーの作成があります。 最近のバックアップがなければ、(ディスクの破損、火災、重要なテーブルの間違った削除などの)破滅の後、復旧することができません。 PostgreSQL で可能なバックアップとリカバリ機構については、第22章 にて詳細に説明します。

他の保守作業の主なカテゴリには、定期的なデータベースの "バキューム" があります。 この作業については 項21.1 で説明します。

他にも、ログファイルの管理も定期的に注意しなければなりません。 これについては 項21.3 で説明します。

PostgreSQL は他のデータベース管理システムに比べ、保守作業は少ないといえます。 それでもなお、これらの作業に適切に注意することは、システムに対する快適かつ充実した経験を確実に得るのに効果があります。

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

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

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

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

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

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

PostgreSQL 7.2 から、標準形式の VACUUM は、普通のデータベース操作(選択、挿入、更新、削除、ただし、テーブルスキーマの変更は除きます。 )と並行して実行できるようになりました。 そのため、定常的なバキューム処理は以前のリリースのような邪魔なものではなく、1 日のうちで使用頻度が低い時間にスケジューリングすることは重要なことではなくなりました。

21.1.1. ディスク容量の復旧

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

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

標準形式の VACUUM は、ディスク容量を安定状態の使用量のレベルで維持することを目的に最もよく使用されます。 標準形式では、古いバージョンの行を探し、その領域をそのテーブル内で再利用できるように変更します。 しかし、テーブルファイルを縮小させ、オペレーティングシステムにディスク容量を返却するといった難しいことは行ないません。 ディスク容量をオペレーティングシステムに返却する必要がある場合は、VACUUM FULL コマンドを使用して下さい。 しかし、すぐに再度割り当てる必要があるディスク容量を解放することにどんな意味があるでしょうか? 更新頻度の激しいテーブルの保守においては、不定期の VACUUM FULL よりも適切な頻度で標準の VACUUM の方がよりよい方法です。

ほとんどのサイトで推奨できる方法は、データベース全体の VACUUM を 1 日 1 回使用頻度が低い時間帯にスケジュールすることです。 必要ならば、更新頻度の激しいテーブルのバキューム処理をより頻繁に行なうよう追加して下さい。 (1 つのクラスタで複数のデータベースがある場合、それぞれをバキュームすることを忘れないで下さい。 vacuumdb プログラムが役に立つかもしれません。) 容量の復旧のための定常的なバキューム処理には、VACUUM FULL ではなく、普通の VACUUM を使用して下さい。

VACUUM FULL は、テーブル内のほとんどのタプルを削除したことが判明している場合に推奨します。 その安定状態のテーブルサイズをVACUUM FULL のより積極的な方式によって大いに縮小できるからです。

テーブルの内容が度々完全に削除される場合、DELETE の後にVACUUM を使用するよりも、TRUNCATE を使用する方が良いでしょう。

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

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

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

特定のテーブルに対して ANALYZE を実行することができます。 また、テーブルの特定の列のみに対してさえも実行することができます。 ですので、アプリケーションの要求に応じて、他よりも頻繁に一部の統計情報を更新できるような柔軟性があります。 しかし、実際は、この機能は有用ではないかもしれません。 PostgreSQL 7.2 から、ANALYZE は、全ての行を読むのではなく、テーブルからランダムに行を抽出して統計処理を行なうようになったため、巨大なテーブルに対してもかなり高速に処理するようになりました。 ですので、頻繁にデータベース全体に対して実行する方が、おそらくかなり単純になります。

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

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

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

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

PostgreSQL 7.2 の前まででは、XID の周回から保護する唯一の方法は最低でも 40 億トランザクション毎に initdb を再度行なうことでした。 当然これは、トラフィックが多いサイトを十分に満足させることはありませんでしたので、より良い解決方法が案出されました。 この新しい方法では、サーバを、initdb や再起動などなく、限界なく稼働状態とすることができます。 この保守要求の代価は、データベースの各テーブルは、最低でも 10 億トランザクション毎にバキュームされなければならない、ということです。

実際、これは面倒な要求ではありませんが、失敗の結果は(ディスク容量の浪費や性能の低下ではなく)完全なデータの損失となりますので、データベース管理者が、直前の VACUUM からの経過時間を保持できるような少し特別な準備を行ないました。 この節の残りで詳細を説明します。

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

VACUUM の通常のポリシーは、過去の 10 億トランザクションより古い通常の XID を持つバージョンの行を全て FrozenXID に再割り当てすることです。 このポリシーは元々の挿入時の XID をどこからも参照されることがなくなるまで、保存します。 (実際は、ほとんどのバージョンの行はおそらく "凍結" になることなく、生成、削除されるでしょう。) このポリシーでは、任意のテーブルの VACUUM の最大の安全な間隔は、正確に 10 億トランザクションです。 この値以上の間行なわなかったとすると、前回は再割り当てするほど古くなかったバージョンの行が 20 億トランザクション以上の古さとなってしまい、未来のものとして循環され、失われてしまいます。 (もちろん、その後の 20 億トランザクション後に再度出現しますが、これは何の助けにもなりません。)

周期的な VACUUM は、これまで説明してきた理由により、とにかく必要とされます。 10 億トランザクションの間バキュームされないテーブルがあるとは考えられません。 しかし、管理者がこの制約に合っていることを確実にすることができるように、VACUUMpg_database システムテーブルにトランザクション ID 統計情報を保存します。 特に、データベースの pg_database 行の datfrozenxid フィールドは、データベース全体に対するバキューム操作 (つまり、特定テーブルの指定のない VACUUM) が完了した時に更新されます。 このフィールドに保存された値は、VACUUM コマンドで使用された、凍結用の切捨て XID です。 この切捨て XID よりも古い、全ての通常の XID はそのデータベースの FrozenXID によって置換されていることが保証されています。 この情報を検査する簡便な方法は、以下の問い合わせを実行することです。

SELECT datname, age(datfrozenxid) FROM pg_database;

age 列は切捨て XID から現在のトランザクション XID までのトランザクション数を測ります。

標準の凍結ポリシーでは、よくバキュームされたデータベースでのage 列は 10 億から始まります。 age が 20 億に近い場合、そのデータベースは、周回問題の危険性を回避するために、再度バキュームされなければなりません。 推奨する方式は、十分安全なマージンを確保するために、各データベースを少なくとも 5 億 (50000万) トランザクション毎にバキュームすることです。 この規則に合わせることを補助するために、各データベース全体に対する VACUUM は、15 億トランザクション以上の age を示す pg_database のエントリがあった場合に自動的に警告を発します。

play=# VACUUM;
WARNING:  some databases have not been vacuumed in 1613770184 transactions
HINT:  Better vacuum them within 533713463 transactions, or you may have a wraparound failure.
VACUUM

FREEZE オプション付きの VACUUM は、全ての開いているトランザクションによって適切とみなすことができるほど古いバージョンの行を凍結するという、より積極的な凍結ポリシーを使用します。 特に、VACUUM FREEZE がどちらかというと待ち状態のデータベースで行なわれた場合、そのデータベース中の 全ての バージョンの行は凍結されます。 従って、データベースが全く変更されない限り、トランザクション ID の周回を防ぐことを目的としたバキューム処理を今後行なう必要はなくなります。 この技術は、initdb において template0 を準備するために使用されています。 また、これは、pg_database にて datallowconn = false と記録されたユーザ作成のデータベースの準備の時にも使用しなければなりません。 このデータベースに接続することができませんので、バキュームする方法が存在しないからです。 バキュームされないデータベースに関する、VACUUM の自動警告メッセージはこの種のデータベースに対する間違った警告を防ぐために datallowconn = false の付いた pg_database エントリを無視することに注意して下さい。 従って、この種のデータベースを正確に凍結させておくことは、ユーザの責任となります。