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

PostgreSQL 8.0.0 文書
前のページ | 巻戻し |  | 早送り | 次のページ

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

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

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

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

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

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

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

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

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

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

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

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

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

PostgreSQL 8.0から、バックグランドで実行されるバキューム処理による性能への影響を軽減させることを調整できるように設定パラメータが追加されました。 項16.4.3.4を参照してください。

## 21.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`プログラムが役に立つかもしれません。

> ティップ: `contrib/pg_autovacuum`プログラムは、高頻度のバキューム操作を自動化する際に役に立ちます。

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

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

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

    SELECT datname, age(datfrozenxid) FROM pg_database;

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

標準の凍結ポリシーでは、よくバキュームされたデータベースでの`age`列は10億から始まります。 `age`が20億に近い場合、そのデータベースは、周回問題の危険性を回避するために、再度バキュームされなければなりません。 推奨する方式は、十分安全なマージンを確保するために、各データベースを少なくとも5億(50000万)トランザクション毎に`VACUUM`を実行することです。 この規則に合わせることを補助するために、各データベース全体に対する`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`を実行する方法が存在しないからです。 バキュームされないデータベースに関する、`VACUUM` の自動警告メッセージはこの種のデータベースに対する間違った警告を防ぐために`datallowconn` = `false`の付いた`pg_database`エントリを無視することに注意してください。 従って、この種のデータベースを正確に凍結させておくことは、ユーザの責任となります。

警告
トランザクションの周回に対して安全であることを確認してください。 少なくとも10億トランザクション毎に全てのデータベースにおいてシステムカタログを含む全てのテーブルをバキュームしなければなりません。 データベース全体にバキューム用のコマンドを実行せずに、使用中のユーザテーブルのみをバキュームする必要があるものと考えた結果、データが損失した事故がありました。 こうした状態でもしばらくの間はうまく動いているように見えます。

前のページ | ホーム | 次のページ
文字セットサポート | 上に戻る | 定常的なインデックスの再作成