Slony-IでレプリケーションしているDBに対して、停止せずにテーブルを追加する方法

Slony-IPostgreSQL用の非同期のシングルマスタレプリケーションソフト。
意外と簡単にPostgreSQLレプリケーションをすることが可能です。


今回は、一旦運用を開始したレプリケーションしているDBに対して、機能追加等でテーブルの追加が必要になった場合に
Slonyを止めずに新しくレプリケーション対象のテーブルを追加するやり方を紹介します。
(他にもっと簡単な方法があるかもしれませんが。あれば教えてください)

1.新しいテーブルを追加する

execute_script経由で、セット全体にテーブルを追加。
full_path_to_sql_script_fileにCREATE TABLE文を書いて、事前に準備しておきます。

execute_script [options] (セット番号) full_path_to_sql_script_file
2.新セットを追加する

slon_tools.confに新しいセットの設定を追加
set_id は稼働中のset_idと被らないように。
table_id は、select max(tab_id) from _<<レプリケーション名>>.sl_table;の結果+1
sequence_id は、select max(seq_id) from _<<レプリケーション名>>.sl_sequence ;の結果+1

slonik_create_set (セット番号) | slonik
3.追加したセットの購読
slonik_subscribe_set (追加したセット番号) (ノード番号) | slonik
4.セットのマージ

追加したセットを元のセットにマージする

slonik_merge_sets (ノード番号) (元のセット) (追加したセット番号) | slonik


これで完了です。簡単です。

PostgreSQL CE 8 Goldを受けてきました。

久しぶりのベンダー試験です。
前回の「PostgreSQL CE Silver」以来なので、緊張するかと思ったのですが、もう慣れたものですね・・。

今月末ぐらいをターゲットに勉強してたのですが、段々飽きてきたので1週間程集中して勉強し、
2日前に試験予約して強行受験してきました。

セクション毎の正解率

全体の成績は90%で、無事合格。

セクション 構成 正解率
運用管理 20%(6問) 100%
パフォーマンスチューニング 60%(18問) 94%
障害対応 20%(6問) 66%

正解率は90%なので、3問不正解だった模様。
パフォーマンスチューニングの1問は、どれが間違っているのか良く解らないが、
障害対応は、pg_resetxlog絡みの問題だと思われる。
この辺りは、幸いというかなんというか実務で発生したことがないので理解が不足してました。

勉強方法

この試験は、試験対策の教科書や問題集がないので勉強しづらいですが、JPUGの皆さんの努力のおかげで
日本語のマニュアルや資料は豊富にあります。
つまりは、これらをいかに活用するかということが大事かと。


まずは、試験の範囲として公開されている部分とマニュアルの該当部分を紐付ける作業を行いました。
マニュアル全部に目を通すとなると、さすがに途中で飽きそうだったので、該当部分を見て、
理解して、実行して、バージョンの差異をまとめるという方法をとりました。


正直、試験に受かるためだけならそこまでやる必要はないのでしょうが、私の目的は
PostgreSQL8.3系をきちんと使いこなせるようになることでしたので、このような方法となりました。


PostgreSQL関連のメモとして使ってたサイトで、公開していますので受ける方は参考にしてください。
マニュアルをまとめただけなので、微妙ですが・・・。

試験感想

「思ったより簡単だった」というのが、正直な感想です。pg_classやpg_statsなんかのデータを見せられて、
○○なのはどれか、○○するためにはどうしたらよいか等の問題が出たら厳しいかな思ってましたが
そこまでは聞かれることはありませんでした。
計算が必要な問題が何個かあったりしたのですが、電卓機能があったのが驚きました。ディスク容量の計算も
自力でやる覚悟はしてたのですがw

これから受ける方にアドバイス

  • Goldの練習問題は、8と7.4両方解いておくこと。普通に出題されます。(5問ぐらい?)
  • 重箱の隅を突付くような問題はなく、きちんと理解してれば解けます。
  • 実行計画を変更するには、どんな方法があるのかをよく理解する。

その他

これでようやく実務に見合う資格が取得できたと思いますが、資格自体がマイナーなため某転職サイトでは、
資格欄に表示すらされません。もっと、PostgreSQLを使いこなせるエンジニアが増えてメジャーになれることを願います。

pg_statsinfoを使用する際の注意点

pg_statsinfoは、PostgreSQLやOSのリソース情報、統計情報をスナップショットとして取得するためのツールで、
複数のスナップショット間の差分をレポートとして出力できます。
これによって、DBで行われた活動状況の確認や性能ボトルネックの発見、また問題発生時の原因調査などが簡単になります。


数十台のPostgreSQLサーバを管理しようとすると、さすがに個別で見るのは厳しいので、半年程前に
pgstatspackで取得したスナップショット情報を集中管理し、Web上で各レポートやグラフの表示ができるものを
こっそり作成してたりしたのですが、情報取得部分だけ作って絶賛放置中でした・・・。
(取得してた情報はそれなりに役に立ったのですが。)


ですが、良さそうなツールが公開されたとのことで、早速使ってみることに。


日本で開発されているので、ドキュメントが全て日本語で書かれていて、しかも豊富です。


pg_statinfoのページはこちら


インストールは、マニュアルを見てセットアップすると問題なくできたのですが、いざスナップショットを取得しようとすると
以下のようなエラーが発生しました。

stats=# SELECT get_stats();
ERROR: sql error
DETAIL: ERROR: relation "_replication_xxxxx.partind_replication_xxxx_sl_log_1-node-21" does not exist

SQLがエラーになる箇所があるようで、うーむ、困った。
インデックス名の"-"がダメな気がするのですが、該当のインデックスはSlony-Iが自動で作成するものなので、さらに困った。
Slony-Iのsrc/backend/slony1_funcs.sqlの5747行目付近(addPartialLogIndicesの中)に書かれています。


既に、Slony-Iは稼動中なのでそこに手を入れるわけにもいかず、pg_statsinfoの方に手をいれることにしました。


修正する箇所は、stats_info/stats_info_sql.hのSQL_INSERT_T_INDEXINFO_FORMATで定義されているSQL。(386行目付近)

pg_relation_size(stat_idx.schemaname ||''.''||stat_idx.indexrelname), \

       ↓

pg_relation_size(stat_idx.schemaname ||''.''||''\"''||stat_idx.indexrelname||''\"''), \

上記の変更することで、うまくスナップショットが取得できるようになりました。
もう少し使ってみて、問題無い様でしたら本格的に運用に乗せていこうかなと考えています。
(レポートの自動取得やメール送信なんかもできたら、結構楽できそうな気がします。)



ちなみに環境は、こんな感じです。
管理DB:PostgreSQL8.3.0
対象DB:PostgreSQL8.3.3+Slony-I 1.2.14

追記1

スナップショットを削除するdelete_statsがうまく動かずに、以下のエラーが発生します。

stats=# SELECT delete_stats(1);
ERROR: relation "t_colcorr" does not exist
CONTEXT: SQL statement "DELETE FROM t_colcorr WHERE snapshot_id = $1 "
PL/pgSQL function "delete_stats" line 27 at SQL statement

「t_colcorrテーブルがない」とのことなので、テーブルを見てみると確かにない・・・。
これは、きっとt_columncorrテーブルのことだと信じて、delete_stats関数の該当箇所を修正。


すると消せるようになりました。

5/18追記

pg_statsinfo1.0.1でこのバグは修正されました。

追記2

マニュアルに間違いっぽ箇所を発見。

ソースによるインストールのPATHを通すところです。

$ cp $PGSRC/contrib/pg_statsinfo-*/target_db/pg_get_logfilterinfo $LOGFILTER
$ cp $PGSRC/contrib/pg_statsinfo-*/target_db/* $REPORT
$ export PATH=$LOGFILTER:$REPOTER:$PATH

    ↓

$ cp $PGSRC/contrib/pg_statsinfo-*/target_db/pg_get_logfilterinfo $LOGFILTER
$ cp $PGSRC/contrib/pg_statsinfo-*/reporter/* $REPORT
$ export PATH=$LOGFILTER:$REPORT:$PATH

レポート出力時のサンプルで$REPORTを指定しているので、きっと$REPORTが正しいのでしょう。

要望1

情報取得対象となるschemaを選択できるようにして欲しい。
基本的にpublic schemaだけで十分なケースが多いので、システムカタログ等の情報はサイズも大きくなるので
取らないようにできるようになったらいいですね。
(私は幾つかのSQLの条件にschema名を指定しています。)


もっとも、情報を取得しないschemaでボトルネックが発生しないことが前提ですが。

要望2

Webの管理画面とか欲しいですねー。(言うだけなら無料なので)
Open Flash Chart等を使ってグラフ表示できたら、尚いいですねー。(無茶も言うだけなら無料!!)

気が向いたら、自分で作りますか。。。

PostgreSQL8.3.7にLudia1.5.2をインストールする

2009/04/07時点の最新版であるPostgreSQL8.3.7+Ludia1.5.2をRHEL5.2にインストールしていたところ、
Ludiaのmake時に以下のようなエラーが発生した。

pgsenna2.c: In function 'pgs2build0':
pgsenna2.c:613: warning: passing argument 4 of 'IndexBuildHeapScan' makes integer from pointer without a cast
pgsenna2.c:613: error: too few arguments to function 'IndexBuildHeapScan'
make[1]: *** [pgsenna2.lo] エラー 1

PostgreSQL8.3.3+Ludia1.5.1+RHEL5.2の環境では問題なくインストールできていたので、
PostgreSQLかLudiaのマイナーバージョンアップの影響の可能性が高い。


試しに、Ludiaを1.5.1に戻してみたが状況は変わらず。原因はほぼPostgreSQL側にあるようだ。


エラーの内容を見ると、IndexBuildHeapScanの4番目の引数がまずいよと言っているっぽいので、
src/backend/catalog/index.cの該当箇所を見てみると・・・・

  • PostgreSQL8.3.5
double IndexBuildHeapScan(Relation heapRelation,
                          Relation indexRelation,
                          IndexInfo *indexInfo,
                          IndexBuildCallback callback,
                          void *callback_state)
  • PostgreSQL8.3.6
double IndexBuildHeapScan(Relation heapRelation,
                          Relation indexRelation,
                          IndexInfo *indexInfo,
                          bool allow_sync,
                          IndexBuildCallback callback,
                          void *callback_state)

確かに、4番目の引数にallow_syncが追加されています・・・。
ということは、PostgreSQLのバージョンアップにLudiaがまだ対応できていない状況。

とりあえず、Ludiaを使用する予定がある方は、PostgreSQL8.3.5以降のバージョンを使うと危険ということです。
Ludia1.5.3以降で対応して頂けることに期待しましょう。




それでも、俺はPostgreSQL8.3.7+Ludia1.5.2を使いたいぜ!!って言う人はLudiaのソースちょっとだけ弄ると
うまくコンパイルが通るようになります。

  • pgsenna2.c
reltuples = IndexBuildHeapScan(heap, index, indexInfo,
                                 buildCallback, (void *) &bs);

reltuples = IndexBuildHeapScan(heap, index, indexInfo,true
                                 buildCallback, (void *) &bs);


新しく追加された4番目のallow_sync引数にbooleanの値を与えて上げればよいだけですが、問題はこの引数を変更すると
どんな影響があるのか?ということで、ちょっと調べてみると8.3.6の変更点の下記に該当するっぽいです。

GIN インデックスを構築する間は、同期スキャンを行なわないようになりました。(Tom)

これは、GIN は TID の昇順での行を挿入するように最適化されているため、同期スキャンによって
インデックス構築が3倍以上遅くなる可能性があるためです。

allow_syncにtrueを指定すると前バージョンと同じ動作になり、falseを指定するとGINインデックス構築中に同期スキャンを
行わないような設定になるようです。

今回は、あくまでも暫定対応ということでallow_syncにfalseを指定することにしました。
これ以上、変更点に躓いても仕方が無いので・・・。



重要なので2度言いますが、Ludiaを使用する予定がある方は、PostgreSQL8.3.5以降のバージョンを使うと危険です。
大人しく8.3.5を使用しましょう。

CREATE INDEXを使いこなす

検索時には大変お世話になっているインデックス。
使われなくても文句も言わずに更新され続けるインデックス。
ずっと放置して運用しているといつの間にか肥大化しているインデックス。


今回は、そんなインデックスを再構築する際に気をつけることをまとめてみました。


まずは、インデックスを作成、または再作成するやり方について簡単に。

CREATE INDEX

普通にインデックスを作成。
他のトランザクションはテーブルを読み取ることはできるが、対象テーブル内の行を挿入、更新、削除しようとすると、
インデックス作成が完了するまでブロックされる。


CREATE INDEX CONCURRENTLY

挿入、更新、削除をブロックしないインデックスの作成方法。
テーブルを2回スキャンする必要があり、潜在的にそのインデックスを使用する可能性がある、実行中のすべての
トランザクションが終わるまで待機する必要がある。
通常よりも総作業時間がかかり、また、完了するまでの時間が非常に長くなる。


REINDEX INDEX

インデックスの再構築。
インデックスの元となるテーブルの書き込みをロックしますが、読み込みはロックしません。
また、処理中のインデックスに対する排他ロックを取得するので、そのインデックスを使用する読み込みはブロックされる。


まとめ

上記の内容を簡単にまとめると下表のようになります。

方式 読み込みブロック 更新ブロック 備考
CREATE INDEX 無し 有り 普通
CREATE INDEX CONCURRENTLY 無し 無し 作業時間が長い
REINDEX INDEX 一部有り 有り reindex中のindexを使用する読み込みは待機


実際、運用中にインデックスの再構築を行う必要がある場合(読み込み、書き込みともに停止できない)は、

  1. CREATE INDEX CONCURRENTLYで新しくインデックスを作成する。
  2. 古いインデックスを削除
  3. 先程作成したインデックスを元の名前にリネーム
  4. Analyzeを実施

こんな感じでやるのが、良いのではないでしょうか。


夜間等、使用されない時間帯が決まっている場合、定期的にメンテナンスの時間が確保できる場合は、
reindexを行う方が簡単なのは間違いないです。

遅いSQLの見つけ方

OracleSQLのチューニングをそこそこしていた人がPostgreSQLで同じことをやろうとした場合に
問題になるのはチューニング対象のSQLを探す作業だと思う。


Oracleであれば、SQL traceを仕掛けたり、$sql_textなどから引っ張ってきたり、Statspackのレポート読んだりと
色んな方法で遅いSQLを特定することが可能だ。
しかし、PostgreSQLではOracle程簡単に対象のSQLを特定するのは難しい。(この辺りの機能がもう少し欲しいところ)


ということで、今回は遅いSQLを見つける特定する方法です。

log_min_duration_statementの設定を変更する

postgresql.confにlog_min_duration_statementというパラメータがある。
指定したミリ秒数以上、時間がかかったSQLをログに出力してくれるものだ。
MySQLのスロークエリーログと同じような感じらしい(MySQLは全然詳しくないので伝聞ですが)

普段、遅いクエリが走っていないか、夜間バッチ等、会社に居ない間に実行されるクエリのパフォーマンスを
確認したい場合なんかに向いている機能ではないでしょうか。

こんな感じで出力されます。

2009-02-18 11:43:23 JST LOG:  duration: 2750.721 ms  statement: select count(*) from master_tbl;

ちなみにPDOを使っていた場合も、2行目にパラメータを出力してくれるので再実行する際も楽です。

2009-02-23 04:08:10 JST LOG:  duration: 8511.299 ms  execute pdo_pgsql_stmt_0098b4b0: SELECT COUNT(*) FROM tableA  WHERE id = $1 AND type = 0
2009-02-23 04:08:10 JST DETAIL:  parameters: $1 = '2782034'

気をつける点としては、このパラメータを変更すると閾値を上回ったSQLをすべてログに出力するので、下手するとログ出力がボトルネックになりかねないということ。
設定する際は、最初は大きめにとって徐々に閾値を下げていくという方向でやるのがいいでしょう。

実行中のSQLを取得

もうひとつの方法は、現在実行中のSQLを取得する方法。

下記のSQLを実行すると、現在実行中のSQLと実行時間を取得できます。

testdb=# SELECT
testdb-#    pid,
testdb-#    start,
testdb-#    now() - start AS lap,
testdb-#    current_query
testdb-# FROM
testdb-#    (SELECT
testdb(#        backendid,
testdb(#        pg_stat_get_backend_pid(S.backendid) AS pid,
testdb(#        pg_stat_get_backend_activity_start(S.backendid) AS start,
testdb(#        pg_stat_get_backend_activity(S.backendid) AS current_query
testdb(#    FROM
testdb(#        (SELECT pg_stat_get_backend_idset() AS backendid) AS S
testdb(#    ) AS S
testdb-# WHERE
testdb-#    current_query <> ''
testdb-# ORDER BY
testdb-#    lap DESC;
  pid  |             start             |       lap        |                           current_query
                                                                                                                                                                                                                                                          • -
10755 | 2009-02-23 23:42:46.152279+09 | -00:00:02.403189 | SELECT max(aid) FROM tableA WHERE id=3661012 10757 | 2009-02-23 23:42:46.158289+09 | -00:00:01.109199 | SELECT * FROM tableB WHERE id=7818 AND valid=true

遅いWebの画面の調査時なんかには、どのSQLが遅いか簡単に調べられます。
log_min_duration_statementを設定するのは、いつ遅いSQLが実行されるか分からない時、または夜間人が居ない時間帯に実行される時、
上記のSQLを実行するのは、遅いSQLが実行されるのが分かる時といった具合に使い分けるのがいいでしょう。

pg_stat_statements

8.3までは、上記の2つしか方法がなかったわけですが、PostgerSQL8.4からpg_stat_statementsが使えるようになるみたいです。


OracleのStatsPackの「SQL ordered by Elapsed Time」「SQL ordered by Executions」の項目と同じような値が簡単に取得で
きるようになります。

$ pgbench -i bench

postgres=# SELECT pg_stat_statements_reset();

$ pgbench -c10 -t300 -M prepared bench

postgres=# \x
postgres=# SELECT * FROM pg_stat_statements ORDER BY total_time DESC LIMIT 3;
[ RECORD 1 ]------------------------------------------------------------
userid     | 10
dbid       | 63781
query      | UPDATE branches SET bbalance = bbalance + $1 WHERE bid = $2;
calls      | 3000
total_time | 20.716706
rows       | 3000
[ RECORD 2 ]------------------------------------------------------------
userid     | 10
dbid       | 63781
query      | UPDATE tellers SET tbalance = tbalance + $1 WHERE tid = $2;
calls      | 3000
total_time | 17.1107649999999
rows       | 3000
[ RECORD 3 ]------------------------------------------------------------
userid     | 10
dbid       | 63781
query      | UPDATE accounts SET abalance = abalance + $1 WHERE aid = $2;
calls      | 3000
total_time | 0.645601
rows       | 3000

どのSQLが何回実行されて、トータル何秒処理時間が掛かっているかと出してくれます。
今までの方法だと、特定の遅いSQLは発見することが可能でしたが、実行回数が多くちょっとだけ遅いSQLの場合は、
発見するのが困難だったのでこれはいい改善点だと思います。

8.4が出たら早速使いたいと思いますが、移行するのがまた面倒な・・・・。




と、こんな感じでやればボトルネックとなっているSQLを特定することが可能だと思います。

他にもツールとか使えば、もっと簡単に特定できるかもしれせんが、その辺りは詳しくないので省略。

使用していないインデックスを見つける

普段は検索性能向上のために黙々と頑張ってくれているインデックス。
だが、中にはこっそりサボっているインデックスもいる。
アプリケーションの設計変更やデータ量、値の偏り、バージョンアップによる実行計画の変更等、
色々なケースでインデックスを使用しなくなることがあり得る。


使用していないインデックスは、役に立たないだけではなく、更新処理が遅くなる、Vacuum時に無駄なサーバーリソースを
消費する等、迷惑きわまりない存在に成り果てる。

なので、定期的にインデックスを使用しているかどうかチェックして、不要な場合は削除しましょう。


ということで、今回は使われていないインデックスを特定する方法について書いてみる。

使用するのは、標準統計情報ビューのpg_stat_user_indexes

pg_stat_user_indexes

主なカラムは以下の通り。

データ型 説明
relname name テーブル名
indexrelname name インデックス名
idx_scan bigint インデックススキャンの実行回数
idx_tup_read bigint 読みとられたインデックスタプル数
idx_tup_fetch bigint このインデックスを使用したインデックススキャンで抽出された有効なテーブル行数

以下が実際に実行したサンプルです。

testdb=# SELECT
testdb-#     relname ,
testdb-#     indexrelname ,
testdb-#     idx_scan ,
testdb-#     idx_tup_read ,
testdb-#     idx_tup_fetch
testdb-# FROM
testdb-#     pg_stat_user_indexes
testdb-# ORDER BY
testdb-#     relname ,
testdb-#     indexrelname;

 relname | indexrelname       | idx_scan  | idx_tup_read | idx_tup_fetch
                                                                                                                                                • -
cnt | cnt_pkey | 950457987 | 91594968 | 91562375 comment | comment_pkey | 32237 | 7810386 | 7810111 comment | comment_tid_res_id | 12560263 | 59697535 | 59673333 comment | comment_uid | 1718765 | 70085458 | 70083194 genre | genre_pkey | 431746352 | 431746352 | 431746352 thread | thread_last_upd | 2859 | 7176586 | 6969599 thread | thread_pkey | 75243881 | 76019793 | 75202913 thread | thread_sum_comment | 0 | 0 | 0 thread | thread_uid | 6604740 | 61243842 | 60595664 vote | vote_pkey | 9042530 | 48153652 | 48153646 vote | vote_tid_uid | 0 | 0 | 0

今回のサンプルでは、threadテーブルのthread_sum_commentインデックスとvoteテーブルのvote_tid_uidインデックスが
idx_scanの値が0なので、今まで一度も使用されていないことが分かる。

注意点1

pg_stat_user_indexesの有効な情報を見るには、以下の設定を行う必要があります。
PostgreSQL8.2以前のバージョンでは、stats_row_levelパラメータがデフォルトでは無効になっているので有効にする
必要があります。PostgreSQL8.3以降では、パラメータの名前がtrack_countsと変更されて、デフォルトで有効となっています。

注意点2

pg_stat_user_indexesは統計情報なので、このビューの値はデータベースの作成時、またはサーバの再起動時からの累積値となっています。
一度も使われていないものは、idx_scanの値が0なので分かりやすいが途中で使われなくなったケースでは1度見ただけでは分かりません。


そのため、pg_stat_reset()関数を使用して統計情報をリセットするか、ある程度の期間を空けて2回以上pg_stat_user_indexesを確認し
差分を取ってみるかしないと分からないケースもあります。
(月次バッチのみ使っているインデックスとかもあり得るのでその辺りはアプリケーションを確認する必要がある)