PostgreSQL 9.5 ALTER TABLE .. SET LOGGED / UNLOGGED

今日は9.5の小ネタを。
9.5のリリースノートを見ると、こんな項目がある。

Allow changing of the WAL logging status of a table ater creation with ALTER TABLE .. SET LOGGED / UNLOGGED (Fabrizio de Royes Mello)

要するにALTER TABLEによるテーブルの属性の変更可能な属性として
ALTER TABLE .. SET LOGGED / UNLOGGED
という項目が加わったと。
今日はそれを試してみた。

UNLOGGED TABLEって(おさらい)

  • 更新時に更新ログ(WAL)を書かないテーブル。
  • WALを書かないから、更新が高速になる。
  • そんかしクラッシュ時は表の内容が爆発四散・・・というか消えてしまう。南無三!

たとえば、こんな感じ。

test=# CREATE UNLOGGED TABLE test (id int, data text);
CREATE TABLE
test=# INSERT INTO test VALUES (generate_series(1,3), 'AAA');
INSERT 0 3
test=# TABLE test;
 id | data 
----+------
  1 | AAA
  2 | AAA
  3 | AAA
(3 rows)

で、この状態でPostgreSQLサーバがクラッシュ(例えばpg_ctl -m immediate)すると

$ pg_ctl stop -m i -D ~/pgdata/9.5-alpha2/
waiting for server to shut down....LOG:  received immediate shutdown request
WARNING:  terminating connection because of crash of another server process
DETAIL:  The postmaster has commanded this server process to roll back the current transaction and exit, because another server process exited abnormally and possibly corrupted shared memory.
HINT:  In a moment you should be able to reconnect to the database and repeat your command.
 done
server stopped

サーバを再起動するとクラッシュリカバリを行うのだが

$ pg_ctl start -D ~/pgdata/9.5-alpha2/
server starting
LOG:  database system was interrupted; last known up at 2015-08-11 21:49:17 JST
LOG:  database system was not properly shut down; automatic recovery in progress
LOG:  redo starts at 0/20581D00
LOG:  invalid record length at 0/2059D9F0
LOG:  redo done at 0/2059D9C8
LOG:  last completed transaction was at log time 2015-08-11 21:50:28.366269+09
LOG:  MultiXact member wraparound protections are now enabled
LOG:  database system is ready to accept connections
LOG:  autovacuum launcher started

testテーブルを参照すると、

$ psql test
psql (9.5alpha2)
Type "help" for help.

test=# \d
       List of relations
 Schema | Name | Type  | Owner 
--------+------+-------+-------
 public | test | table | nuko
(1 row)

test=# TABLE test;
 id | data 
----+------
(0 rows)

テーブル定義自体は残っているが、さっきINSERTしたデータは消えている。
UNLOGGEDな表ってのはそういうもの。

LOGGEDに変更されたら?

UNLOGGEDからLOGGEDに変更されたときに気になるのは、UNLOGGEDのときに更新されたレコードはどうなるのか、ということ。
LOGGEDに変更され、その直後にクラッシュしたらどうなるのか。

ということで試してみた。
まず、UNLOGGED TABLEを作成してレコードを挿入。

test=# CREATE UNLOGGED TABLE test (id int, data text);
CREATE TABLE
test=# \d test
 Unlogged table "public.test"
 Column |  Type   | Modifiers 
--------+---------+-----------
 id     | integer | 
 data   | text    | 

test=# INSERT INTO test VALUES (generate_series(1,3), 'AAA');
INSERT 0 3

その状態でALTER TABLEコマンドを使って、テーブルをLOGGEDに変更して、更にレコードを挿入する。

test=# ALTER TABLE test SET LOGGED ;
ALTER TABLE
test=# \d test
     Table "public.test"
 Column |  Type   | Modifiers 
--------+---------+-----------
 id     | integer | 
 data   | text    | 

test=# INSERT INTO test VALUES (generate_series(4,6), 'BBB');
INSERT 0 3
test=# TABLE test;
 id | data 
----+------
  1 | AAA
  2 | AAA
  3 | AAA
  4 | BBB
  5 | BBB
  6 | BBB
(6 rows)

ここでPostgreSQLサーバをimmediateモードで停止(クラッシュと同じ)させ、その後にPostgreSQLサーバを再起動する。

$ pg_ctl stop -m i -D ~/pgdata/9.5-alpha2/
waiting for server to shut down....LOG:  received immediate shutdown request
WARNING:  terminating connection because of crash of another server process
DETAIL:  The postmaster has commanded this server process to roll back the current transaction and exit, because another server process exited abnormally and possibly corrupted shared memory.
HINT:  In a moment you should be able to reconnect to the database and repeat your command.
 done
server stopped
$ pg_ctl start -D ~/pgdata/9.5-alpha2/
server starting
LOG:  database system was interrupted; last known up at 2015-08-11 22:14:38 JST
LOG:  database system was not properly shut down; automatic recovery in progress
LOG:  invalid record length at 0/205E5280
LOG:  redo is not required
LOG:  MultiXact member wraparound protections are now enabled
LOG:  database system is ready to accept connections
LOG:  autovacuum launcher started

さて、このときにtestテーブルの内容はどうなるのか。

  • 答え1 UNLOGGEDのときに更新されたデータも、LOGGEDに変更された後のデータも保持される。
  • 答え2 LOGGEDにに変更された後のデータのみ保持される。
  • 答え3 かわせない。現実は非情である。

もちろん、答え1が正しい。

test=# TABLE test;
 id | data 
----+------
  1 | AAA
  2 | AAA
  3 | AAA
  4 | BBB
  5 | BBB
  6 | BBB
(6 rows)

UNLOGGEDに変更されたら?

では逆にLOGGEDからUNLOGGEDに変更された場合はどうなのか?

test=# \d test
     Table "public.test"
 Column |  Type   | Modifiers 
--------+---------+-----------
 id     | integer | 
 data   | text    | 

test=# INSERT INTO test VALUES (generate_series(1,3), 'AAA');
INSERT 0 3
test=# ALTER TABLE test SET UNLOGGED ;
ALTER TABLE
test=# \d test
 Unlogged table "public.test"
 Column |  Type   | Modifiers 
--------+---------+-----------
 id     | integer | 
 data   | text    | 

test=# INSERT INTO test VALUES (generate_series(4,6), 'BBB');
INSERT 0 3
test=# TABLE test;
 id | data 
----+------
  1 | AAA
  2 | AAA
  3 | AAA
  4 | BBB
  5 | BBB
  6 | BBB
(6 rows)

この状態で、クラッシュしたらどうなるのか。

  • 答え1 UNLOGGEDのときに更新されたデータも、LOGGEDに変更された後のデータも消えてしまう。
  • 答え2 LOGGEDの状態で更新されたデータのみ保持される。
  • 答え3 かわせない。現実は非情である。

ということで、この状態でimmediateでPostgreSQLサーバを停止させ、再起動すると・・・

test=# \d test
 Unlogged table "public.test"
 Column |  Type   | Modifiers 
--------+---------+-----------
 id     | integer | 
 data   | text    | 

test=# TABLE test;
 id | data 
----+------
(0 rows)

もちろん、答え1のように全てのデータが消えてしまう。

ここまでの挙動から推測されること(単なる推測)

この実験から推測したこと。

  • UNLOGGEDからLOGGEDになった時点で、現状のUNLOGGEDテーブルに存在しているレコードが書き込まれた状態を復元するためのWALが書き出される。
  • LOGGEDからUNLOGGEDになった時点で、現状のテーブルに存在しているレコードが全て削除された(TRUNCATEと同様?)というWALが書き出され、以降はWALが書き出されなくなる。

本当にそうなのかどうかは、まだきちんと調べていない。

この機能を使って、

  • 大量のロードを行うときにはUNLOGGEDに変更してガン速ロード
  • ロード後にLOGGEDに変更

みたいないいとこ取りできるかなーと思ったけど、多分、ALTER TABLEでLOGGEDに変更するときに、大量のWAL書き出しが発生するから、トータルではそんなに変わらないような気もしてきた。

一応、試してみた。
UNLOGGED TABLEに1000万件データを挿入後に、LOGGEDに変更。

test=# INSERT INTO test VALUES (generate_series(1, 10000000), 'AAA');
INSERT 0 10000000
Time: 5750.821 ms
test=# ALTER TABLE test SET LOGGED ;
ALTER TABLE
Time: 5743.382 ms

INSERTとALTERの合計時間は約11.5秒。

UNLOGGEDでないフツーのテーブルに1000万件データを挿入した場合。

test=# INSERT INTO test VALUES (generate_series(1, 10000000), 'AAA');
INSERT 0 10000000
Time: 13302.407 ms

INSERTの時間は約13秒。

トータルでみるとそれほど差はない気がする。

レプリケーション環境の場合はどうなるのよ?

という疑問も当然出てくるけど、これは明日以降試してみる(仕事後に気力が残っていたら)。