PostgreSQLで各テーブルの総サイズと平均サイズを知る


DBを使っている時、どのテーブルがどのくらい容量を食っているか知りたいことがあると思う。
また今後のサイズ増加を見積もりたいとき、あるテーブルの1行あたりの平均バイトサイズも知りたいはず！

## PostgreSQLで見るべきところ

pg_classのテーブルの

- relpagesがブロック数
- reltuplesが行数

を表すらしい。

http://www.postgresql.jp/document/9.3/html/catalog-pg-class.html

これらは実際にはプランナが用いる推測値。ANALYZEコマンドを打つとこれらの統計情報が更新されるので、ANALYZE直後にやるほうが正確。まぁ大抵、概算が知りたいだけなのであまり気にする必要ないかもしれないが。

ブロックサイズは8K(`SHOW block_size;`で確認可能)なので
`relpages / 128`Mbytesのサイズを占領しているということ。

平均サイズは、バイト数で知りたいので`relpages * 8192 / reltuples`すれば良い。0割りが起こる可能性があるのでちゃんとやりたければCASE文を使えばいい気がするが、めんどくさい場合はめちゃくちゃ小さい値を分母に足したのでOK。

## 確認方法

上の議論を踏まえ、以下のクエリを発行する。

```sql
SELECT relname, reltuples, (relpages / 128) as mbytes, (relpages * 8192.0 / (reltuples + 1e-10)) as average_row_size FROM pg_class ORDER BY mbytes DESC;
```


こうすると、以下のように`テーブル名`、`行数`、`占有サイズ(Mbyte)`、`平均サイズ(byte)`を占有サイズが大きい順に知ることができる。

```bash:sql_result
      relname       | reltuples | mbytes |     row_size
--------------------+-----------+--------+------------------
 some_table         |     89693 |    210 | 2456.75973866702
 other_table        |     93741 |     52 | 583.844509398135
....
```

なお、例えばユーザー情報を`users`テーブルに格納するとしたら、これののサイズは`users`テーブルのみじゃなく、それのIndexのサイズも加算しないといけないので注意。
Railsで作成した場合は`users_pkey`とか`index_users_on_hoge`みたいなテーブル名で存在しているはず。

## より良いクエリ

上のクエリだとpg_toastなどが上に来ることがあり、原因がぱっとはつかめないことがある。
(参考: http://www.postgresql.jp/document/8.4/html/storage-toast.html )


そういう時は、以下のクエリを走らせると良い。
自分は、以下のPDFから拾った。詳細は知らないw
https://wiki.postgresql.org/images/a/ab/Pganalyze_Lightning_talk.pdf

```sql
SELECT pgn.nspname, relname, pg_size_pretty(relpages::bigint * 8 * 1024) AS size, CASE WHEN relkind = 
't' THEN (SELECT pgd.relname FROM pg_class pgd WHERE pgd.reltoastrelid = pg.oid) WHEN nspname = 
'pg_toast' AND relkind = 'i' THEN (SELECT pgt.relname FROM pg_class pgt WHERE SUBSTRING(pgt.relname 
FROM 10) = REPLACE(SUBSTRING(pg.relname FROM 10), '_index', '')) ELSE (SELECT pgc.relname FROM 
pg_class pgc WHERE pg.reltoastrelid = pgc.oid) END::varchar AS refrelname, CASE WHEN nspname = 
'pg_toast' AND relkind = 'i' THEN (SELECT pgts.relname FROM pg_class pgts WHERE pgts.reltoastrelid = 
(SELECT pgt.oid FROM pg_class pgt WHERE SUBSTRING(pgt.relname FROM 10) = REPLACE(SUBSTRING(pg.relname 
FROM 10), '_index', ''))) END AS relidxrefrelname, relfilenode, relkind, reltuples::bigint, relpages 
FROM pg_class pg, pg_namespace pgn WHERE pg.relnamespace = pgn.oid AND pgn.nspname NOT IN 
('information_schema', 'pg_catalog') ORDER BY relpages DESC; 
```

以下のようにサイズもGB, MBで表示してくれ、Index関係も何のIndexかも表示してくれる。

```bash:sql_result
 nspname  |       relname        |    size    |   refrelname   | relidxrefrelname | relfilenode | relkind | reltuples | relpages 
----------+----------------------+------------+----------------+------------------+-------------+---------+-----------+----------
 pg_toast | pg_toast_16589       | 33 GB      | some_table     |                  |    33058906 | t       |  17976238 |  4347405
 pg_toast | pg_toast_16976       | 5963 MB    | other_table    |                  |    33059508 | t       |   3088986 |   763266
 public   | another_table        | 3681 MB    |                |                  |    33059230 | r       |  85995424 |   471139
 pg_toast | pg_toast_16589_index | 421 MB     | pg_toast_16589 | some_table       |    33058908 | i       |  18334784 |    53837
```