PostgreSQL Advent Calendar 2012(全部俺)のDay 15です。
PostgreSQLに限らず、RDBMSにおいて「特定のテーブルの更新差分だけを取得したい」というのは、DBAの夢であると言えます(よね?)。
トランザクションログの中には当然ながら「更新情報」が記録されているわけですが、それを再利用可能な形で取り出す方法が無く、涙で枕を濡らした思い出を持つDBAも多いことでしょう。多分。
もう少し分かりやすく申しますと、PostgreSQLのメジャーバージョンアップの際などにはpg_dumpによるexport/importが必要となるわけですが、データ量が多くなって来ている今、データのexportにも時間がかかりますし、24x365のシステムも増えていますので、移行している間にもデータの更新が発生してしまいます。
なので、こういった作業の合間に発生する更新情報(少ないとは言え)を、保存しておいて、後から再利用できると便利ですよね? ね? という話であります。
というわけで、今回はPostgreSQLにおいて、テーブルの更新差分を取得する方法をご紹介します。
今回はtablelogというモジュールを使います。
PgFoundry: Table Audit: Project Info
http://pgfoundry.org/projects/tablelog/
tablelogモジュールは、テーブルデータに更新が発生した場合に、トリガを使ってその更新データを別のログテーブルに保存するモジュールです。
ですので、特定のテーブルにロギング用のトリガを設定しておくことで、後刻、ログテーブルから更新情報を時系列で取り出すことが可能になる、ということです。
余談ですが、「tablelog」をGoogleで検索すると「次の検索結果を表示しています: tabelog」と言われるのですが、ちょっと違います。惜しいですが。
それでは、tablelogモジュールをインストールしてみます。
tar.gzを展開して、環境変数USE_PGXSを1に設定しつつ、make & make install を実行します。
なお、今回はインストールスクリプトやロギングの設定用のSQL関数をいくつか追加していますので、パッチ(table_log-0_4_4_snaga.diff)も適用します。
table_log-0_4_4_snaga.diff
https://gist.github.com/4291369
まず、ターゲットとなるテーブルのサンプルを作成します。
ログの設定はSQL関数の table_log_init() を使用します。最初の引数はロギングのレベルで、3~5の値を取ります。
ロギングレベルの4はtrigger_idと呼ばれるIDも含めて保存し、ロギングレベル5は更新したユーザ名も含めて保存します(詳細は後述)。
ここではロギングレベルを3にして設定を行います。
それでは、実際にテーブルにレコードをINSERTしてみましょう。
次に、データのUPDATEを行ってみます。ここでは、小文字を大文字に変換してみます。
なお、ロギングを停止したい場合には、disable_table_log()関数を使うことで停止することができます。(これはオリジナルには無く、今回適用したパッチで追加されるSQL関数です)
ログレベルを4にするとtrigger_idと呼ばれる値が追加され、ログレベルを5にすると更新を実行したユーザも記録されるようになります。
trigger_idというのは、ロギングの対象テーブルにプライマリキーが存在しない場合に、プライマリキーの代替えとしてレコードを一意に識別するために設定される BIGSERIAL 値です。
以下は、テーブルt1, t2, t3について、それぞれログレベルを変えて変更差分を取得したものです。見ていただくと分かりますが、ログレベルによってログテーブルのカラム数が違っています。
というわけで、今回はテーブルの更新情報を取得、保存するtablelogモジュールを御紹介しました。
データベースのデータが大きくなり、かつメンテナンス時間を取りづらい昨今、テーブルの更新情報を取得してうまく使えば、運用管理がもっと容易になるケースが多くあると思います。
ぜひ、こういったモジュールもうまく使いこなしていただければと思います。
ではまた。
PostgreSQLに限らず、RDBMSにおいて「特定のテーブルの更新差分だけを取得したい」というのは、DBAの夢であると言えます(よね?)。
トランザクションログの中には当然ながら「更新情報」が記録されているわけですが、それを再利用可能な形で取り出す方法が無く、涙で枕を濡らした思い出を持つDBAも多いことでしょう。多分。
もう少し分かりやすく申しますと、PostgreSQLのメジャーバージョンアップの際などにはpg_dumpによるexport/importが必要となるわけですが、データ量が多くなって来ている今、データのexportにも時間がかかりますし、24x365のシステムも増えていますので、移行している間にもデータの更新が発生してしまいます。
なので、こういった作業の合間に発生する更新情報(少ないとは言え)を、保存しておいて、後から再利用できると便利ですよね? ね? という話であります。
■tablelogモジュール
というわけで、今回はPostgreSQLにおいて、テーブルの更新差分を取得する方法をご紹介します。
今回はtablelogというモジュールを使います。
PgFoundry: Table Audit: Project Info
http://pgfoundry.org/projects/tablelog/
tablelogモジュールは、テーブルデータに更新が発生した場合に、トリガを使ってその更新データを別のログテーブルに保存するモジュールです。
ですので、特定のテーブルにロギング用のトリガを設定しておくことで、後刻、ログテーブルから更新情報を時系列で取り出すことが可能になる、ということです。
余談ですが、「tablelog」をGoogleで検索すると「次の検索結果を表示しています: tabelog」と言われるのですが、ちょっと違います。惜しいですが。
■tablelogモジュールのインストール
それでは、tablelogモジュールをインストールしてみます。
tar.gzを展開して、環境変数USE_PGXSを1に設定しつつ、make & make install を実行します。
なお、今回はインストールスクリプトやロギングの設定用のSQL関数をいくつか追加していますので、パッチ(table_log-0_4_4_snaga.diff)も適用します。
table_log-0_4_4_snaga.diff
https://gist.github.com/4291369
次に、tablelogを使用したいデータベースに対してインストールを行います。
[snaga@devsv02 pgsql]$ tar zxf table_log-0.4.4.tar.gz
[snaga@devsv02 pgsql]$ cd table_log-0.4.4
[snaga@devsv02 table_log-0.4.4]$ ls
Makefile table_log.c table_log_init.sql tests
README.table_log table_log.h table_log.sql.in
[snaga@devsv02 table_log-0.4.4]$ patch -p1 < ../table_log-0_4_4_snaga.diff
patching file Makefile
patching file table_log.sql.in
patching file table_log_uninstall.sql.in
[snaga@devsv02 table_log-0.4.4]$ env USE_PGXS=1 PATH=/usr/pgsql-9.1/bin:$PATH make
sed 's,MODULE_PATHNAME,$libdir/table_log,g' table_log.sql.in >table_log.sql
sed 's,MODULE_PATHNAME,$libdir/table_log_uninstall,g' table_log_uninstall.sql.in >table_log_uninstall.sql
gcc -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic -I/usr/include/et -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -Wformat-security -fno-strict-aliasing -fwrapv -fpic -I. -I. -I/usr/pgsql-9.1/include/server -I/usr/pgsql-9.1/include/internal -I/usr/include/et -D_GNU_SOURCE -I/usr/include/libxml2 -I/usr/include -c -o table_log.o table_log.c
gcc -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic -I/usr/include/et -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -Wformat-security -fno-strict-aliasing -fwrapv -fpic -L/usr/pgsql-9.1/lib -L/usr/lib64 -shared -o table_log.so table_log.o
rm table_log.o
[snaga@devsv02 table_log-0.4.4]$ su
Password:
[root@devsv02 table_log-0.4.4]# env USE_PGXS=1 PATH=/usr/pgsql-9.1/bin:$PATH make install
/bin/mkdir -p '/usr/pgsql-9.1/share/contrib'
/bin/mkdir -p '/usr/pgsql-9.1/lib'
/bin/mkdir -p '/usr/share/doc/pgsql/contrib'
/bin/sh /usr/pgsql-9.1/lib/pgxs/src/makefiles/../../config/install-sh -c -m 644 table_log.sql table_log_uninstall.sql '/usr/pgsql-9.1/share/contrib/'
/bin/sh /usr/pgsql-9.1/lib/pgxs/src/makefiles/../../config/install-sh -c -m 755 table_log.so '/usr/pgsql-9.1/lib/'
/bin/sh /usr/pgsql-9.1/lib/pgxs/src/makefiles/../../config/install-sh -c -m 644 ./README.table_log '/usr/share/doc/pgsql/contrib/'
[root@devsv02 table_log-0.4.4]# exit
exit
[snaga@devsv02 table_log-0.4.4]$
これでインストールは完了です。
[snaga@devsv02 table_log-0.4.4]$ psql -f /usr/pgsql-9.1/share/contrib/table_log.sql testdb2
SET
CREATE FUNCTION
CREATE FUNCTION
(...snip...)
CREATE FUNCTION
CREATE FUNCTION
[snaga@devsv02 table_log-0.4.4]$
■ターゲットとなるテーブルにロギングを設定する
まず、ターゲットとなるテーブルのサンプルを作成します。
次に、上記で作成したテーブル t1 に対してログ取得を設定します。
testdb2=# CREATE table t1 ( uid integer primary key, uname text not null );
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t1_pkey" for table "t1"
CREATE TABLE
testdb2=#
ログの設定はSQL関数の table_log_init() を使用します。最初の引数はロギングのレベルで、3~5の値を取ります。
ロギングレベルの4はtrigger_idと呼ばれるIDも含めて保存し、ロギングレベル5は更新したユーザ名も含めて保存します(詳細は後述)。
ここではロギングレベルを3にして設定を行います。
testdb2=# SELECT table_log_init(3, 't1');
table_log_init
----------------
(1 row)
testdb2=# \d+
List of relations
Schema | Name | Type | Owner | Size | Description
--------+--------+-------+-------+---------+-------------
public | t1 | table | snaga | 0 bytes |
public | t1_log | table | snaga | 0 bytes |
(2 rows)
testdb2=#
■テーブルを更新してログを取得する
それでは、実際にテーブルにレコードをINSERTしてみましょう。
上記のように、テーブルt1に対して、そのログテーブルであるテーブルt1_logに、カラムのデータに加えて、トリガの種別(INSRET/UPDATE/DELETE)、タプルの種別(更新前のデータoldか、更新後のデータnewか)と、トリガが実行された時刻が保存されています。
testdb2=# SELECT * FROM t1;
uid | uname
-----+-------
(0 rows)
testdb2=# INSERT INTO t1 VALUES ( 101, 'satoshi nagayasu' );
INSERT 0 1
testdb2=# SELECT * FROM t1;
uid | uname
-----+------------------
101 | satoshi nagayasu
(1 row)
testdb2=# SELECT * FROM t1_log;
uid | uname | trigger_mode | trigger_tuple | trigger_changed
-----+------------------+--------------+---------------+-------------------------------
101 | satoshi nagayasu | INSERT | new | 2012-11-26 13:34:01.218549+09
(1 row)
testdb2=#
次に、データのUPDATEを行ってみます。ここでは、小文字を大文字に変換してみます。
今度は、UPDATEのログのため、trigger_modeがUPDATEとなり、更新前のレコード(old)と更新後のレコード(new)の両方が記録されています。
testdb2=# UPDATE t1 SET uname = upper(uname);
UPDATE 1
testdb2=# SELECT * FROM t1;
uid | uname
-----+------------------
101 | SATOSHI NAGAYASU
(1 row)
testdb2=# SELECT * FROM t1_log;
uid | uname | trigger_mode | trigger_tuple | trigger_changed
-----+------------------+--------------+---------------+-------------------------------
101 | satoshi nagayasu | INSERT | new | 2012-11-26 13:34:01.218549+09
101 | satoshi nagayasu | UPDATE | old | 2012-11-26 13:34:38.38462+09
101 | SATOSHI NAGAYASU | UPDATE | new | 2012-11-26 13:34:38.38462+09
(3 rows)
testdb2=#
■ロギングの停止
なお、ロギングを停止したい場合には、disable_table_log()関数を使うことで停止することができます。(これはオリジナルには無く、今回適用したパッチで追加されるSQL関数です)
testdb2=# SELECT disable_table_log('t1');
disable_table_log
-------------------
(1 row)
testdb2=# \d t1+
Table "public.t1"
Column | Type | Modifiers
--------+---------+-----------
uid | integer | not null
uname | text | not null
Indexes:
"t1_pkey" PRIMARY KEY, btree (uid)
testdb2=#
■ログレベルの違い
ログレベルを4にするとtrigger_idと呼ばれる値が追加され、ログレベルを5にすると更新を実行したユーザも記録されるようになります。
trigger_idというのは、ロギングの対象テーブルにプライマリキーが存在しない場合に、プライマリキーの代替えとしてレコードを一意に識別するために設定される BIGSERIAL 値です。
以下は、テーブルt1, t2, t3について、それぞれログレベルを変えて変更差分を取得したものです。見ていただくと分かりますが、ログレベルによってログテーブルのカラム数が違っています。
testdb2=# \x
Expanded display is on.
testdb2=# SELECT * FROM t1_log;
-[ RECORD 1 ]---+------------------------------
uid | 101
uname | satoshi nagayasu
trigger_mode | INSERT
trigger_tuple | new
trigger_changed | 2012-11-26 13:48:24.141572+09
testdb2=# SELECT * FROM t2_log;
-[ RECORD 1 ]---+------------------------------
uid | 101
uname | satoshi nagayasu
trigger_mode | INSERT
trigger_tuple | new
trigger_changed | 2012-11-26 13:48:24.142149+09
trigger_id | 1
testdb2=# SELECT * FROM t3_log;
-[ RECORD 1 ]---+------------------------------
uid | 101
uname | satoshi nagayasu
trigger_mode | INSERT
trigger_tuple | new
trigger_changed | 2012-11-26 13:48:24.142683+09
trigger_id | 1
trigger_user | snaga
testdb2=#
■まとめ
というわけで、今回はテーブルの更新情報を取得、保存するtablelogモジュールを御紹介しました。
データベースのデータが大きくなり、かつメンテナンス時間を取りづらい昨今、テーブルの更新情報を取得してうまく使えば、運用管理がもっと容易になるケースが多くあると思います。
ぜひ、こういったモジュールもうまく使いこなしていただければと思います。
ではまた。