pg_buffercacheをもうちょっと見やすくする

この記事は [PostgreSQL Advent Calendar 2016](http://qiita.com/advent-calendar/2016/postgresql) の11日目です。

# pg_buffercacheをもうちょっと見やすくする

[pg_buffercache](http://www.postgresql.jp/document/current/html/pgbuffercache.html)の話です。

これまでにも何度かpg_buffercacheは紹介されているので、使ったことがある人もいるでしょう。

共有バッファにのったキャッシュを覗くアレです。拡張モジュールをロードすると、pg_buffercacheというビューが出来て、キャッシュの状態がわかるようになります。

ただメモリブロック毎にレコードが返されるので、そのままの出力では一目で確認するのは難しいです。
そこで、SQLで集計したりするわけですが、単純にcountするするだけでなく、もうちょっと何とかすることにします。

## 準備

試した環境はPostgreSQL 9.6です。たぶん9.4以上が必要です。

兎にも角にもpg_buffercacheモジュールが必要です。

```sql
# CREATE EXTENSION pg_buffercache;
CREATE EXTENSION
```

共有バッファにロード出来るpg_prewarmがあると分かりやすいので、これも入れます。

```sql
CREATE EXTENSION pg_prewarm;
CREATE EXTENSION
```

## 方針

細かくというよりは大まかに見たいと思ったので、パッと一覧で見えるような出力にします。
SQLでやることか...と思わなくもないですが、SQLで出力を頑張ります:sweat_smile:

## SQL

以下のSQLを作成しました。

```sql:buffercache.sql
WITH r AS (
SELECT div(bufferid, :N) AS id,
       mode() WITHIN GROUP
           (ORDER BY reldatabase) AS reldatabase
     , mode() WITHIN GROUP
           (ORDER BY relfilenode) AS relfilenode
     , mod(mode() WITHIN GROUP (ORDER BY relfilenode)::int
           ,52) AS singlecode
  FROM pg_buffercache
 GROUP BY id
), rr AS (
SELECT
       r.id
     , d.datname
     , c.relname
     , r.relfilenode
     , CASE
            WHEN relname IS NULL THEN
                 '.'
            WHEN singlecode >= 26 THEN
                 chr(ascii('A') + 6 + singlecode)
            ELSE chr(ascii('A') + singlecode)
        END AS sc
  FROM r
  LEFT JOIN pg_class AS c
         ON (r.relfilenode = c.relfilenode)
  LEFT JOIN pg_database AS d
         ON (r.reldatabase = d.oid)
 ORDER BY id
)
SELECT array_to_string(
         array_agg(sc ORDER BY rr.id)
       , '') AS buffercache
  FROM rr
 UNION
SELECT '|'|| sc ||'='||'('||rr.datname||')'||rr.relname
  FROM rr
 ORDER BY 1;
```

buffercache.sqlのファイルとしてpsqlで実行してみます。

## 使い方

使い方は、psqlの変数を使用しているので、psqlのオプションに`-v N=20`のように渡します。これは、1ブロック1文字だと大量に出過ぎるので、1文字のブロック単位を指定します。

実際の実行は以下のようにします。

```
$ psql -X -t -Pformat=wrapped -Pcolumns=80 -v N=20 -f buffercache.sql
..........mC.C.................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
.........................................
 |C=(btest)pg_amop
 |C=(btest)pg_amop_opr_fam_index
```

"."はNULLを変換していて空いています。起動直後なので、ほとんどキャッシュされていません。
適当なテーブルを作成してみます。

```sql
# CREATE TABLE buffercache_test (id int, m int);
# INSERT INTO buffercache_test (id, m) 
       VALUES (generate_series(1,10000000), (random()*10000)::int % 100);
```

pg_prewarmを使用してキャッシュにロードしてみます。とりあえず100に制限してロードしてみます。

```sql
# SELECT pg_prewarm('buffercache_test','buffer','main',0,100);
 pg_prewarm 
------------
        101
(1 row)
```

pg_buffercacheのSQLを再度実行してみましょう。

```
 psql -X -t -Pformat=wrapped -Pcolumns=80 -v N=20 -f buffercache.sql
 jjnvuioeEBBBBB...BB...djoC.....................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
.........................................
 |B=(btest)buffercache_test
 |C=(btest)pg_description_o_c_o_index
 |E=(btest)pg_statistic
 |d=(btest)pg_depend_reference_index
 |e=(btest)pg_operator
 |i=(btest)pg_operator_oprname_l_r_n_index
```

buffercache_testがキャッシュにのっているがわかります。

続けてテーブル全部をロードしてみます。

```sql
# SELECT pg_prewarm('buffercache_test');
```

```
# psql -X -t -Pformat=wrapped -Pcolumns=80 -v N=20 -f buffercache.sql
 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB.
.BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB.
.BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB.
.BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB.
.BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB.
.BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB.
.BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB.
.BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBH.uheowBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB.
.BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB.
.BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB.
.BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
 |B=(btest)buffercache_test
 |H=(btest)pg_amproc_fam_proc_index
 |e=(btest)pg_operator
 |h=(btest)pg_operator_oid_index
```

buffercache_test でほぼ埋める結果となりました。
違うテーブルをロードしたりするとbuffercache_testだったところが別のテーブルに置き換わって、追い出されていることも確認出来ます。

※ 一旦共有バッファのキャッシュにのったものをクリアするにはPostgreSQLの再起動しかなさそうです。

## まとめ

とりあえず大雑把に確認する用途には使えるかなと思います。
postgresq.confのshared_bufferの値を変えたりして、共有バッファのキャッシュを確認しながらだとパフォーマンスチェックも楽しく出来ます:smile:

## おまけ

9.6で罠にハマったので、別ページに書いときました。
[PostgreSQL 9.6 psqlの動作の非互換](http://qiita.com/items/1aebfc6a44d674c36614)