RDS for PostgreSQLやAurora PostgreSQLでCloudWatchメトリクスとして用意されているMaximumUsedTransactionIDs
について記載致します。
MaximumUsedTransactionIDsについてのまとめ
MaximumUsedTransactionIDs
は、当該インスタンス上の全データベース中で、最も長くトランザクションIDが凍結されていないテーブルの経過トランザクション数を示します。
最も長くトランザクションIDが凍結されていないテーブルについて、下記のような条件が満たされると、当該メトリクスも低下します。
- バキューム時に古いトランザクションIDを含むページが読み込まれた場合
- 最後にトランザクションIDが凍結されてから
vacuum_freeze_table_age
の設定値を経過した後にバキュームされた場合 - 最後にトランザクションIDが凍結されてから
autovacuum_freeze_max_age
の設定値に達して強制的にバキュームされた場合 - 手動で
vacuum freeze
を実行した場合
最後にトランザクションIDが凍結されてからの経過トランザクション数をデータベースごとに確認するには下記のようなSQLを実行します。
select
datname,
age(datfrozenxid) as age
from
pg_database
order by
2 desc,
1
;
最後にトランザクションIDが凍結されてからの経過トランザクション数をテーブルごとに確認するには下記のようなSQLを実行します。
上記で経過トランザクション数が多かったデータベースに接続して実施します。
select
relname,
age(relfrozenxid) as age
from
pg_class
where
relkind = 'r'
order by
2 desc,
1
;
最終的にはMaximumUsedTransactionIDs
がautovacuum_freeze_max_age
の設定値に達した段階で強制的なバキュームにより増加傾向が横ばいとなることが予測されますが、これは、必ずしも未対応で問題ないということは意味しません。
autovacuum_freeze_max_age
に達するまで、何らかの理由で長い期間バキュームされていなかったということを暗示するためです。また、このタイミングの強制的なバキュームの負荷が非常に高くなる可能性があります。
したがって、MaximumUsedTransactionIDs
のメトリクスについて、少なくともvacuum_freeze_table_age
の設定値付近にアラームを設定しておき、autovacuum_freeze_max_age
に達する前にバキュームするのが望ましい対応です。
また、自動バキュームが適切に行われているか確認し、行われていない場合は、自動バキュームの閾値を再検討する形が望ましいです。
自動バキュームが行われているかは、rds.force_autovacuum_logging_level
およびlog_autovacuum_min_duration
を適切に設定することで、ログ上から確認することができます。例えば、前者をlog
とし、後者を0
とすることで、全ての自動バキュームに関する情報をログ出力させることができます。
主なログの確認観点としては、「自動バキュームが行われているか」という点と、「自動バキュームがスキップされていないか」という点になります。
rds.force_autovacuum_logging_level パラメータの値を log に設定し、log_autovacuum_min_duration パラメータを 1,000〜5,000 ミリ秒の値に設定することをお勧めします。この値を 5,000 に設定すると、Amazon RDS は、5 秒を超えるすべてのアクティビティをログに書き込みます。また、アプリケーションのロックによって autovacuum で意図的にテーブルがスキップされた場合は、「バキュームのスキップ」メッセージも表示されます。
PostgreSQL の一般的な DBA タスク
以下、MaximumUsedTransactionIDs
に深く関連するトランザクションIDに関する説明です。
トランザクションIDとは
- データのバージョン管理を行うための管理IDである
- トランザクションIDを比較することでどの時点のデータを見せるかを管理している
PostgreSQLはデータ追記型のアーキテクチャです。データに対して更新を行っても、実際は最新版のデータが追記されます。つまり、更新前のデータは最新版として扱われなくなるだけで、バキュームで回収されるまでは物理的には存在しています。
データ追記型アーキテクチャにおいて問題となるのが、データのバージョン管理です。物理的には複数データが存在するため、どれが最新のデータなのか、実行中のトランザクションからはどのデータを見れば良いのかという点を管理する必要があります。
このバージョン管理を実施するために利用されているのがトランザクションIDです。
イメージとしては、各トランザクションに昇順の数値であるトランザクションIDを割り当て、そのトランザクション内でデータが更新(実際は追記)された場合は、当該データに対して、更新を行ったトランザクションIDもメタデータとして保持させるような形です。
各トランザクションに割り当てられたIDと、各データが保持するIDを比較することにより、トランザクションから参照可能なデータのバージョンの絞り込みが行えるようになります。
このトランザクションIDの比較作業により、たとえ物理的には古いデータが存在している場合でも、論理的には新しいデータを見ることができるようになります。
また、そのトランザクションから見えてはいけない時点のデータを見せないといったバージョン管理も実現することができます。
PostgreSQLのMVCCトランザクションのセマンティクスは、トランザクションID(XID)番号の比較が可能であることに依存しています。 現在のトランザクションのXIDよりも新しい挿入時のXIDを持ったバージョンの行は、「未来のもの」であり、現在のトランザクションから可視であってはなりません。
24.1. 定常的なバキューム作業
トランザクションIDの周回とは
- 利用できるトランザクションIDを使い果たして最初から再利用し始める事象のこと
- 何も対策が取られないと再利用後のトランザクションからは全てのデータが論理的に見えなくなる
さて、上記でトランザクションIDについて記載しましたが、数値を扱う上で避けられないことがあります。それは扱える数値の上限です。
PostgreSQLではトランザクションIDを32ビットの符号付き整数で表しているため、-2,147,483,648
から2,147,483,647
まで扱えます。
つまり、トランザクションIDの最大値は2,147,483,647
ですが、PostgreSQLはこの約20億以上のトランザクションを超えても継続的に動作できるような仕組みを持っています。
どのような仕組みかと言うと、トランザクションIDを最初からまた再利用するのです。これをトランザクションIDの周回と呼びます。
しかしながら、再利用されて割り当てられたトランザクションIDは直前のトランザクションIDと比べると、圧倒的に若い値となります(極端に表現すると2,147,483,647
→0
)。
そうなると、再利用されたトランザクションIDからは、全てのデータが未来のものとして扱われるようになります。
したがって、物理的にはデータが存在する状態にも関わらず、論理的にはデータは存在しないものとして扱われてしまいます。
トランザクションIDのサイズには制限(32ビット)があり、長時間(40億トランザクション)稼働しているクラスタはトランザクションの周回を経験します。 XIDのカウンタが一周して0に戻り、そして、突然に、過去になされたトランザクションが将来のものと見えるように、つまり、その出力が不可視になります。 端的に言うと、破滅的なデータの損失です。 (実際はデータは保持されていますが、それを入手することができなければ、慰めにならないでしょう。)
24.1. 定常的なバキューム作業
トランザクションIDの凍結とは
- データをどのトランザクションIDから見ても過去のものとして見えるトランザクションIDに更新すること
- これによりトランザクションIDの周回による論理的なデータ損失を避けることができる
論理的にデータが損失するということは、実質的に約20億トランザクションまでしかPostgreSQLを利用できないのかと言うと、そうではありません。
上記のトランザクションIDの周回に伴う論理的なデータの損失を避けるための仕組みも持っています。
どのような仕組みかと言うと、データを「どのトランザクションIDから見ても過去のものとして見えるトランザクションID」として更新するのです。この特殊な更新をトランザクションIDの凍結と呼びます。
この仕組みにより、たとえトランザクションIDの周回が発生し、急激に若いトランザクションIDが割り当てられ、単純なトランザクションIDの比較だとデータが論理的に損失するような場合でも、「どのトランザクションIDから見ても過去のものとして見えるトランザクションID」に凍結されたデータを見ることができるようになります。
PostgreSQLは特別なXID、FrozenTransactionIdを確保します。 このXIDは通常のXIDの比較規則には従わず、常に全ての通常のXIDよりも古いものとみなされます。
~~中略~~
凍結された行バージョンは挿入XIDがFrozenTransactionIdであるかのように扱われ、それで、周回問題に関係なく、すべての通常のトランザクションから「過去のもの」として認識され、また、そのバージョンの行はどれだけ古いものであろうと、削除されるまで有効状態となります。
24.1. 定常的なバキューム作業
トランザクションIDの凍結タイミング
トランザクションIDの凍結は下記のタイミングにて実施されます。
- バキューム時に古いトランザクションIDを含むページが読み込まれた場合
- 最後にトランザクションIDが凍結されてから
vacuum_freeze_table_age
の設定値を経過した後にバキュームされた場合 - 最後にトランザクションIDが凍結されてから
autovacuum_freeze_max_age
の設定値に達して強制的にバキュームされた場合 - 手動で
vacuum freeze
を実行した場合
バキューム時に古いトランザクションIDを含むページが読み込まれた場合
通常、バキューム処理はパフォーマンス向上のため、全てのページを読み込むわけではありません。この際、読み飛ばされたページに古いトランザクションIDがあった場合、それは凍結されません。
したがって、普通のバキューム時には、古いトランザクションIDを含むページが読み込まれない限り、凍結は実施されません(必ず凍結されることは保証されていない)。
通常は、不要な行バージョンを持っていないページを読み飛ばします。 このとき、そのページに古いXID値の行バージョンがまだある可能性があったとしても読み飛ばします。 したがって、通常のVACUUMでは必ずしもテーブル内のすべての古い行バージョンを凍結するわけではありません。
24.1. 定常的なバキューム作業
最後にトランザクションIDが凍結されてからvacuum_freeze_table_ageの設定値を経過した後にバキュームされた場合
テーブルで凍結されているトランザクションIDと、現在のトランザクションIDの差がvacuum_freeze_table_age
の設定値を超えた後にバキュームされると(最後にトランザクションIDが凍結されてからvacuum_freeze_table_age
経過すると)、そのテーブルに対しては積極的に凍結が試みられます。
しかしながら、vacuum_freeze_table_age
を超えても、そもそもバキューム自体が実行されない場合には、凍結も実施されません。
テーブルのpg_class.relfrozenxidフィールドがこの設定で指定した時期に達すると、VACUUMは積極的なテーブル全て走査を行います。 積極的な全て走査は、無効タプルを含む可能性のあるページだけではなく、凍結されていないXIDあるいはMXIDを含むすべてのページを読む点で通常のVACUUMとは異なります。
vacuum_freeze_table_age
最後にトランザクションIDが凍結されてからautovacuum_freeze_max_ageの設定値に達して強制的にバキュームされた場合
最後のトランザクションIDの凍結からvacuum_freeze_table_age
を超えてもバキュームされないままだと、最終的にautovacuum_freeze_max_age
の段階で強制的にバキュームされます。
これにより、トランザクションIDの周回によるデータ損失対策が強制的に実施されます。
トランザクションID周回を防ぐためにVACUUM操作が強制される前までにテーブルのpg_class.relfrozenxid フィールドが到達できる最大(トランザクションにおける)年代を指定します。 自動バキュームが無効であった時でも、システムは周回を防ぐために自動バキューム子プロセスを起動することに注意してください。
autovacuum_freeze_max_age
手動でvacuum freezeを実行した場合
手動でバキューム時に凍結を実施した場合も、トランザクションIDが凍結されます。バキューム時にfreeze
オプションをつけることで手動で実施することができます。
FREEZE
積極的なタプルの「凍結」を選択します。 FREEZE指定は、vacuum_freeze_min_ageおよびvacuum_freeze_table_ageパラメータをゼロとしてVACUUMを実行することと同じです。 テーブルが書き換えられる時は、必ず積極的な凍結が行われるので、FULLが指定されているときは、このオプションは冗長です。
VACUUM
経過トランザクション数の確認
データベースごと
最後にトランザクションIDが凍結されてからの経過トランザクション数をデータベースごとに確認するには、例えば下記のようなSQLを実行します。
select
datname,
age(datfrozenxid) as age
from
pg_database
order by
2 desc,
1
;
テーブルごと
最後にトランザクションIDが凍結されてからの経過トランザクション数をテーブルごとに確認するには、例えば下記のようなSQLを実行します。
select
relname,
age(relfrozenxid) as age
from
pg_class
order by
2 desc,
1
;
コメント