PostgreSQLをwarmupするツール(pg_warmup)作りました

pg_warmupとは

PostgreSQL用のwarmupツールです。slave追加する時とかに、キャッシュ載っていない状態でサービスインさせるとパフォーマンスでないで障害起きるみたいなことがよくある話かと思いますが、それを回避するために必要なテーブルやインデックスをキャッシュ(disk cache もしくは shared_buffer)に乗っけておくというツールです。

y-asaba/pg_warmup · GitHub

なお、PostgreSQLについてはDirect IO使って自分でIOをscheduleするような仕組みはなくて(正確に言うとWALの書き出しでDirect IO使う場合もある、はず)、page cache使うのでshared_bufferの値は物理メモリの25%くらいにしておけというbest practiceがあります。

詳しく知りたい方は、最近議論あったMLのスレッドにサマリがありますので、そちらを御覧ください。

仕組み

基本的には、cacheに乗っける=データを読み込む、ということをするだけです。直接ファイルを読み込んでpage cacheに乗っけるか、PostgreSQLのshared bufferを経由してデータを読み込みます。

どうやって物理ファイルのパスを特定するか?

pg_classのrelfilenodeに格納されるので、それを使います。例えばpgbench -i -s 200 hogeを実行しておくと、pgbench_accountsなどのテーブルができます。このpgbench_accountsのファイルの場所を調べるSQLがこれです。

SELECT relname, current_setting('data_directory') || '/' ||  pg_relation_filepath(oid) AS filepath,
       pg_relation_size(oid) AS filesize
   FROM pg_class WHERE relname = 'pgbench_accounts'

psqlで実行するとfilepathカラムに絶対パスが入っています。

     relname      |                  filepath                  |  filesize
------------------+--------------------------------------------+------------
 pgbench_accounts | /Users/y-asaba/pg/92/data/base/86530/86556 | 2685902848
(1 row)

ただ、このまま使うのは若干まずいところがあって、すでに説明したようにファイルは1GBで分割されるので、上のファイルパス + . + 数字というファイル名も読み込みの対象とする必要があります。

% find . | grep 86556                                                                                                                                                                 [22:12:37]
./base/86530/86556 ← これ
./base/86530/86556.1 ←これ
./base/86530/86556.2 ← これ
./base/86530/86556_fsm
./base/86530/86556_vm

インデックスのファイルパスもちょっとだけ複雑ですが、同じようにpg_classから取り出します。

SELECT relname, current_setting('data_directory') || '/' ||  pg_relation_filepath(oid) AS filepath,
       pg_relation_size(oid) AS filesize
   FROM pg_class WHERE oid IN (SELECT indexrelid FROM pg_index
                                  WHERE indrelid = (SELECT oid FROM pg_class WHERE relname = 'pgbench_accounts'))

pgbench_accountsの場合はprimary keyがあるだけなので、1件だけしか返ってきませんが、複数のインデックスがあれば複数行返ってきます。

       relname        |                  filepath                  | filesize
-----------------------+--------------------------------------------+-----------
 pgbench_accounts_pkey | /Users/y-asaba/pg/92/data/base/86530/86561 | 449249280

データの読み込み

cat して /dev/nullに捨てるだけです。ionice使える環境であればionice -c 3 cat fileをしたりもします。

shared bufferに乗っける

これも単純にseq scanするようなクエリを投げます。具体的にはカウントするだけです。

SELECT COUNT(*) FROM pgbench_accounts

ただ、この場合だとインデックス使っていないので、インデックスはshared bufferには乗っからないので注意してください。

使い方

pypiに登録したので、pip install pg_warmupでインストールすることができます。python 2.7と3.3では動作確認をしました。コマンドはgithubにあるREADMEにある通りで、

initdbしたディレクトリはinitdbをしたユーザしか読めないので、pg_warmupはinitdbを実行したユーザで実行してください。あと、warmupしようとしているPostgreSQLのport番号が違う場合や、パスワードが設定されている場合は、PG*環境変数をあらかじめセットしておいてください。

Usage: pg_warmup [options]

Options:
  -h, --help            show this help message and exit
  -t TABLE, --table=TABLE
                        warmup the named table
  -d DATABASE, --database=DATABASE
                        dbname
  -i                    use ionice command
  -s                    cache on shared buffer, not page cache
  -x                    execute warmup