4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

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
);

bodypayloadには巨大な値が入るかもしれませんよね?
大きな値をそのままテーブルの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テーブルがあるのは普通

ここまでを踏まえると、varchartextjsonb、などを含むテーブルではTOASTテーブルがあることが普通のことだとわかります。

なので、DBを見ていてpg_toastのようなものを見つけても、「なにこのテーブル...なんか間違って設定しちゃった...?」と思わなくて大丈夫です。

ただ、TOASTテーブルが実際に使われていそうなテーブルの性能を確認するときには、以下のようなことを気にした方がいいかもしれません。

  • 実際にTOASTテーブルへ大量のデータが入っているか
  • 巨大なTOAST列を頻繁に読んでいるか
  • SELECT *で不要な巨大列まで読んでいないか

まとめ

PostgreSQLのTOASTは、最初は分かりづらいですがやっていることはシンプルです。

つまるところ、

大きな値を、そのまま本体テーブルに抱え込まないようにする仕組み

です。

最後にポイントをまとめておきます。

  • TOAST = PostgreSQLが大きな値を扱うための仕組み
  • TOASTテーブル = 大きな値の退避先
  • TOASTテーブルはTOAST可能な列があればCREATE TABLE時点で用意される
  • 明示的に設定しなくても多くの可変長型はデフォルトでTOAST対象になり得る

TOAST、名前はかわいいですが、実態はPostgreSQLが大きなデータをいい感じに扱うための仕組みでした。

この記事が皆様の一助になれば幸いです。

  1. PostgreSQL 18 66.2. TOAST 2 3

  2. PostgreSQL 18 CREATE TABLE

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?