はじめに
PostgreSQLを触っていると、たまに出てくる謎ワードがあります。
TOASTです。名前だけ見ると急にパンの話が始まったように見えます。
実際には、PostgreSQLが大きな値を扱うための仕組みのようです。
この記事では次のあたりを整理します。
- TOASTとは何か
- TOASTテーブルはいつ作られるのか
- 明示的に設定しないといけないのか
結論
先に結論です。
TOAST = 大きすぎるカラム値を圧縮したり、別テーブルに逃がしたりする仕組み
そして大事なのは、この2つを分けて考えることです。
TOASTテーブルが作られる = 大きな値を逃がせる準備がある
実際にTOASTテーブルへデータが入る = 大きな値が本当に行外保存されている
つまり、TOASTテーブルがあるからといって、データがTOASTされているわけではないということです。
TOASTとは何か
ではTOASTとはなんなのでしょうか?
その前に、PostgreSQLのテーブルについて少し話します。
PostgreSQLのテーブルは内部的にはページという単位で管理されています。標準的な設定だと1ページは8 KBです。
しかし、PostgreSQLではこういうテーブルをよく作ると思います。
CREATE TABLE articles (
id bigint,
body text
);
あるいは、こういうテーブルもよく作ると思います。
CREATE TABLE events (
id bigint,
payload jsonb
);
bodyやpayloadには巨大な値が入るかもしれませんよね?
大きな値をそのままテーブルの1行に押し込もうとすると、行サイズが大きくなりすぎて扱いづらくなります。
そこで、PostgreSQLは必要に応じて大きな値を圧縮する、別のTOASTテーブルへ行外保存するという処理をします。
これがTOAST、The Oversized-Attribute Storage Techniqueです。
全然パンじゃなかった。
PostgreSQLのドキュメント1では、TOASTは以下のように説明されています。
The technique is affectionately known as TOAST (or “the best thing since sliced bread”).
TOASTはスライスパン以来、最高の発明らしい
TOASTテーブルとは何か
さて、大きな値を逃すといっても、逃す先はどこなのでしょうか?
それはTOASTテーブルと呼ばれます。
イメージとしてはこんな感じです。
articles テーブル
+----+----------------------+
| id | body |
+----+----------------------+
| 1 | TOAST先への参照情報 |
+----+----------------------+
TOASTテーブル
+----------+-----------+------------------+
| chunk_id | chunk_seq | chunk_data |
+----------+-----------+------------------+
| xxx | 0 | bodyの断片その1 |
| xxx | 1 | bodyの断片その2 |
| xxx | 2 | bodyの断片その3 |
+----------+-----------+------------------+
最初は「メモリのポインタみたいなもの?」と思ったのですが、よくよく見ると違いました。
本体テーブルには実データそのものではなく、TOASTされた値を取り出すための参照情報が置かれます。そして、実データはTOASTテーブル側にチャンクとして分割保存されます。
なので、「本体テーブルには参照情報だけ置いて、大きな実データは別テーブルに分割して置く」仕組み、と考える方が正確そうです。
ポインタっぽく見える部分はありますが、メモリ上のアドレスを指しているわけではなくて、データベース内部でTOASTテーブル上の値をたどるための情報という理解が近そうです。
本体テーブルにはTOAST先への参照情報、TOASTテーブルにはチャンクされた値が置かれる
TOASTテーブルはいつ作られるのか
さて、そんなTOASTテーブルですが、いつ作られるのでしょうか?少なくとも私は自分で作ったことはありません。
TOASTテーブルは一番始めのCREATE TABLEをしたときに一緒に作られるのか、大きなデータがきたときにやばいと思って作られるのか。答えは前者でした。
TOAST可能な列があるテーブルでは、TOASTテーブルはCREATE TABLEした段階で用意されます。つまり、でかい値がINSERTされてから初めて作られるわけではありません。
いや、TOAST可能な列ってなんだよという話だと思います。
PostgreSQLのドキュメント1では、
a data type must have a variable-length (varlena) representation
と説明されています。要するに、varcharやtext、jsonbなどの可変長型ということですね。
このようなデータ型が含まれるテーブルをCREATEしようとしたとき、TOASTテーブルも一緒にできるというわけです。
ただし、TOASTテーブルが作られることと、使われることは別です。
TOASTテーブルがあるからといって、すべての可変長型の値がTOASTテーブルへ保存されるわけではありません。必要だったらTOASTテーブルに保存されるだけで、短い値の場合はそのまま本体テーブルに保存されます。
TOASTテーブルに格納されるデータはどれくらい大きいのか
さて、ではどれくらい大きい値だったらTOASTテーブルにデータが格納されるのでしょうか?
PostgreSQLのドキュメント1では、次のように説明されています。
The TOAST management code is triggered only when a row value to be stored in a table is wider than TOAST_TUPLE_THRESHOLD bytes (normally 2 kB)..
つまるところ、1行が一定サイズを超えるとTOAST対象の列を圧縮したり、TOASTテーブルへ行外保存したりするわけですね。このしきい値をTOAST_TUPLE_THRESHOLDと呼び、通常は約2 KBです。
なので、varchar(255)に普通の名前やメールアドレス程度の文字列を入れているだけの場合、TOASTテーブルが実質的に使われることはほとんどないはずです。
一方、jsonbに大きなJSONを格納している場合や、textやbyteaに大きなバイナリ相当のデータを格納している場合にはTOASTテーブルが使われる可能性が高くなります。
TOASTは明示的に設定しないといけないのか
PostgreSQLには、列ごとにSTORAGEという設定があります。
代表的なものは以下です。
| STORAGE | 圧縮 | 行外保存 | 説明 |
|---|---|---|---|
PLAIN |
しない | しない | TOASTしない |
MAIN |
する | なるべく行外に出さない | 本体テーブル優先 |
EXTERNAL |
しない | する | 圧縮せず行外保存 |
EXTENDED |
する | する | 圧縮も行外保存も許可 |
EXTENDEDは圧縮も行外保存も許可する設定です。
では、明示的にこうしないとTOASTされないのでしょうか?
多くの場合、TOASTを明示的に設定する必要はありません。
text、varchar、bytea、jsonb などの可変長型は、通常、デフォルトでTOASTの対象になり得ます。
つまり、これらの型では、普通にCREATE TABLEするだけでEXTENDEDがデフォルトの設定になることが多いです。
PostgreSQLのドキュメント2では
EXTENDED is the default for most data types that support non-PLAIN storage.
と説明されています。
TOASTテーブルがあるのは普通
ここまでを踏まえると、varchar、text、jsonb、などを含むテーブルではTOASTテーブルがあることが普通のことだとわかります。
なので、DBを見ていてpg_toastのようなものを見つけても、「なにこのテーブル...なんか間違って設定しちゃった...?」と思わなくて大丈夫です。
ただ、TOASTテーブルが実際に使われていそうなテーブルの性能を確認するときには、以下のようなことを気にした方がいいかもしれません。
- 実際にTOASTテーブルへ大量のデータが入っているか
- 巨大なTOAST列を頻繁に読んでいるか
-
SELECT *で不要な巨大列まで読んでいないか
まとめ
PostgreSQLのTOASTは、最初は分かりづらいですがやっていることはシンプルです。
つまるところ、
大きな値を、そのまま本体テーブルに抱え込まないようにする仕組み
です。
最後にポイントをまとめておきます。
- TOAST = PostgreSQLが大きな値を扱うための仕組み
- TOASTテーブル = 大きな値の退避先
- TOASTテーブルはTOAST可能な列があればCREATE TABLE時点で用意される
- 明示的に設定しなくても多くの可変長型はデフォルトでTOAST対象になり得る
TOAST、名前はかわいいですが、実態はPostgreSQLが大きなデータをいい感じに扱うための仕組みでした。
この記事が皆様の一助になれば幸いです。