PostgreSQLの障害対策 - WAL編 - | A Day In The Boy's Life

A Day In The Boy's Life

とあるエンジニアのとある1日のつぶやき。

PostgreSQLバックアップ3分クッキング 」で書いたように、PostgreSQLは非常に簡単なバックアップとリカバリ方法を兼ね備えていますが、これだけでは障害直前のデータに復旧することができないなどの問題点があります。


pg_dumpコマンドによるバックアップは、あくまでもコマンド実行時点でのDBのスナップショットであるため、コマンド発行以降のトランザクション情報は含まれていません。

トランザクションごとにpg_dumpコマンドを発行することは非現実的であるため、障害に強いDBを構築するにはその他の方法で障害直前までのデータを復旧する手段を用意しておく必要があります。


ここでは、PostgreSQLで用意されているWALとPoint In Time Recovery(PITR)の機能をうまく使って、障害に強いDBを構築する方法を書いてみたいと思います。

なお、エントリがかなり長くなったので2回に分けて書いています。

Point In Time Recovery(PITR)については、後編の「PostgreSQLの障害対策 - Point In Time Recovery編 - 」を参照してください。



WALって何だ?


WAL(Write Ahead Logging)は、ログ先行書き込みという日本語訳からわかるように、トランザクションの処理の内容をデータファイルに反映する前に、ログファイル(WALセグメントファイル)に書き出そうという動きをさせるものです。

詳しくは、こちらの記事がとても参考になります。


WALとは(Write Ahead Logging) @ InterDB


PostgreSQL7.1以降、WALはデフォルトで有効になっています。
このWALセグメントファイルは、データファイルを格納しているディレクトリ(PGDATA)以下にある、pg_xlogというディレクトリに出力されます。


$ ls -la pg_xlog/
合計 16416
drwx------  3 postgres postgres     4096  2月 16 11:30 .
drwx------ 10 postgres postgres     4096  2月 16 13:23 ..
-rw-------  1 postgres postgres 16777216  2月 16 11:35 000000010000000000000000
drwx------  2 postgres postgres     4096  2月 16 11:30 archive_status

データファイルに問題が起きた場合でも、このログファイルを使って復旧させることができるのがWALの特徴になります。
OracleのREDOログファイルに相当する機能ですね。


ってことで、WALセグメントファイルはインスタンス障害に特化したものということになります。
というのも、OracleのREDOログファイル同様にWALセグメントファイルも循環方式となっており、ある一定のタイミングで上書きされてしまいます。

吐き出されるWALセグメントファイルの数は、マニュアルによると


WALセグメントファイルは常に少なくとも1つあり、また、通常は(2 + checkpoint_completion_target)×checkpoint_segments + 1より多くはありません。

とあります。

一時的にこの数を超えることはあるようですが、この数の上限以下になるように不要なWALセグメントファイルを削除する動きをするようです。



WALセグメントファイルを使ってインスタンス障害に対応できるか実験


ってことで、実施にインスタンス障害を起こしてみて、復旧できるのかを試してみたいと思います。

ここではtestdbにある、test_tabテーブルに対してのトランザクション実行中にインスタンス障害を起こしてみます。
まず、ちゃんと復旧できたかを確認とるために、DBのデータファイルの位置を把握しておきます。


$ psql testdb

testdb=# select datname,oid from pg_database;
  datname  |  oid
-----------+-------
 postgres  | 10819
 testdb    | 16384
 template1 |     1
 template0 | 10818

PostgreSQLは、1テーブルに付き1データファイルという構造を持っています。
なので、test_tabのテーブルのデータファイル名を調べます。


testdb=# select relname , relfilenode from pg_class where relname = 'test_tab';
 relname  | relfilenode
----------+-------------
 test_tab |       16390

次に、実際のデータファイルの位置と状態も確認しておきます。


$ ls -la data/base/16384/16390
-rw------- 1 postgres postgres 8192  2月 16 18:34 data/base/16384/16390

では、実際にデータを投入して、チェックポイントを実行してみます。


testdb=# insert into test_tab values (1, 'hoge');
INSERT 0 1

testdb=# checkpoint;

ファイルのタイムスタンプが書き換わっていることからも、チェックポイントによってログバッファの内容がデータファイルへ書き込まれたことがわかります。


$ ls -la data/base/16384/16390
-rw------- 1 postgres postgres 8192  2月 16 18:42 data/base/16384/16390

では、次のトランザクションを開始して、この途中にインスタンス障害を起こしてみます。


testdb=# insert into test_tab values (2, 'foo');
INSERT 0 1

で、この間にpostmasterのプロセスをKILLしてみる。
この後、検索とかをしみると・・・。


testdb=# select * from test_db;
server closed the connection unexpectedly
        This probably means the server terminated abnormally
        before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.

ってことで、PostgreSQLが異常終了した状態です。
ちなみに、先ほどのデータファイルのタイムスタンプをもう一度確認してみると・・・


# ls -la data/base/16384/16390
-rw------- 1 postgres postgres 8192  2月 16 18:42 data/base/16384/16390

と、変わりが無いことからもログバッファの内容がデータファイルに書き込まれていないことがわかります。

では、PostgreSQLを起動させ、メディアリカバリが自動的に行われるかを確認してみます。


$ pg_ctl start
pg_ctl: another server might be running; trying to start server anyway
LOG:  database system was interrupted at 2009-02-16 18:42:14 JST
LOG:  checkpoint record is at 0/457EB0
LOG:  redo record is at 0/457EB0; undo record is at 0/0; shutdown FALSE
LOG:  next transaction ID: 0/649; next OID: 24582
LOG:  next MultiXactId: 1; next MultiXactOffset: 0
LOG:  database system was not properly shut down; automatic recovery in progress
LOG:  redo starts at 0/457EF8
LOG:  record with zero length at 0/458090
LOG:  redo done at 0/458068
LOG:  database system is ready
server starting

起動後に、testdbにつなげてSELECT文を実行してみると。


$ psql testdb

testdb=# select * from test_tab;
 no | name
----+------
  1 | hoge
  2 | foo
(2 rows)

2行目のnameがfooのデータを投入した時点でインスタンス障害が起こり、データファイルに反映されていない状態なのにWALセグメントファイルによってリカバリされていることがわかります。
ちなみにデータファイルのタイムスタンプを確認してみても、PostgreSQLを起動した時間帯と一致します。


# ls -la data/base/16384/16390
-rw------- 1 postgres postgres 8192 2月 16 18:50 data/base/16384/16390

WALセグメントファイル自体は、特に意識的に使うことはないのですが、このようなPostgreSQLがデータの整合性を保つために役立てていることがわかります。