TOASTとは
PostgreSQLには「TOAST」という、大きなデータを効率的に扱うための仕組みがある。
TOASTは "The Oversized-Attribute Storage Technique"(過大属性格納技法) の略で、PostgreSQLのページサイズ(8KB)を超えるような大きなデータを保存する際に自動的に働く。名前の由来は、データを「スライスしたパン(toast)」のように小さなチャンク(塊)に分割して管理することから来ている。
TOASTの基本的な動作
- 対象データ型:TEXT、BYTEA、JSON、配列など、可変長データ型のみ
- 動作タイミング:データが約2KB(正確には1996バイト)を超えると発動
- 保存方法:データを圧縮または分割して、専用のTOASTテーブル(
pg_toast_xxxxx)に保存 - データ構造:全ての可変長データは先頭4バイトに値の長さを格納(varlenaヘッダ)
PostgreSQLは以下の順序でデータを処理する:
- データが小さい(約2KB以下)→ そのまま元テーブルに保存
- データが大きい → まず圧縮を試みる
- 圧縮後も大きい → TOASTテーブルに分割保存し、元テーブルにはポインタのみ残す
本記事では、PostgreSQL 17を使ってTOASTの実際の動作を観察する。
実験環境
- PostgreSQL 17
- Docker Compose
services: postgres: image: postgres:17 environment: POSTGRES_PASSWORD: password POSTGRES_DB: toast_test ports: - "5432:5432" volumes: - pgdata:/var/lib/postgresql/data - ./init.sql:/docker-entrypoint-initdb.d/init.sql command: - "postgres" - "-c" - "shared_preload_libraries=pg_stat_statements" - "-c" - "max_connections=100" volumes: pgdata:
実験:TOASTの動作を観察する
実験1:TOASTテーブルの確認
まず、TOASTをサポートするデータ型(TEXT)を含むテーブルを作成する。
CREATE TABLE toast_test ( id SERIAL PRIMARY KEY, data TEXT );
この時点で、PostgreSQLは自動的にTOASTテーブルを作成する。以下のクエリで確認できる。
SELECT c.relname as table_name, t.relname as toast_table_name FROM pg_class c LEFT JOIN pg_class t ON c.reltoastrelid = t.oid WHERE c.relname = 'toast_test';
結果:
table_name | toast_table_name ------------+------------------ toast_test | pg_toast_16426
pg_toast_16426という名前のTOASTテーブルが作成されている。この時点ではまだデータを挿入していないため、TOASTテーブルの中身は空だ。
実験2:小さなデータの挿入
小さなデータを挿入してみる。
-- 様々なサイズのデータを挿入 INSERT INTO toast_test (data) VALUES (repeat('A', 100)); INSERT INTO toast_test (data) VALUES (repeat('A', 500)); INSERT INTO toast_test (data) VALUES (repeat('A', 1000)); INSERT INTO toast_test (data) VALUES (repeat('A', 2000)); INSERT INTO toast_test (data) VALUES (repeat('A', 3000)); -- サイズ確認 SELECT id, length(data) as text_length, pg_column_size(data) as stored_size FROM toast_test ORDER BY id;
結果:
id | text_length | stored_size ----+-------------+------------- 1 | 100 | 104 2 | 500 | 504 3 | 1000 | 1004 4 | 2000 | 2004 5 | 3000 | 3004
この時点でTOASTテーブルを確認しても、まだデータは入っていない。
SELECT chunk_id, chunk_seq, pg_column_size(chunk_data) as chunk_size FROM pg_toast.pg_toast_16426 ORDER BY chunk_id, chunk_seq;
結果:
chunk_id | chunk_seq | chunk_size ----------+-----------+------------ (0 rows)
なぜTOASTに保存されないのか?
上記のデータは全て同じ文字('A')の繰り返しだ。PostgreSQLはデフォルトでデータを圧縮するため、'A'の繰り返しは非常に高い圧縮率で圧縮され、2KB以下に収まってしまう。結果として、TOASTテーブルに追い出される閾値(約2KB)に達しない。
実験3:圧縮できないデータの挿入
圧縮できないランダムなデータを大量に挿入する。
-- ランダムな文字列を32KB分生成して挿入 INSERT INTO toast_test (data) SELECT string_agg(md5(random()::text), '') FROM generate_series(1, 1000);
このデータはmd5()で生成されたランダムな文字列(32文字)を1000個連結したもので、合計約32KBになる。ランダムなデータは圧縮効率が低いため、TOASTテーブルに保存されるはずだ。
再度TOASTテーブルを確認する。
SELECT chunk_id, chunk_seq, pg_column_size(chunk_data) as chunk_size FROM pg_toast.pg_toast_16426 ORDER BY chunk_id, chunk_seq;
結果:
chunk_id | chunk_seq | chunk_size
----------+-----------+------------
16434 | 0 | 2000
16434 | 1 | 2000
16434 | 2 | 2000
16434 | 3 | 2000
16434 | 4 | 2000
16434 | 5 | 2000
16434 | 6 | 2000
16434 | 7 | 2000
16434 | 8 | 2000
16434 | 9 | 2000
16434 | 10 | 2000
16434 | 11 | 2000
16434 | 12 | 2000
16434 | 13 | 2000
16434 | 14 | 2000
16434 | 15 | 2000
16434 | 16 | 68
(17 rows)
観察結果:
- chunk_id: 16434 - このデータを識別するID(元テーブルの該当行に対応)
- chunk_seq: 0〜16 - データが17個のチャンクに分割されている
- chunk_size: 2000バイト(最後だけ68バイト) - 約2KBずつ分割
合計サイズ:2000 × 16 + 68 = 32,068バイト
PostgreSQLは大きなデータを約2KB(デフォルトではTOAST_MAX_CHUNK_SIZE = 1996バイト、ヘッダ込みで2000バイト前後)ごとに分割し、TOASTテーブルに保存していることが確認できた。
わかったこと
1. TOASTテーブルはテーブル作成時に自動生成される
可変長データ型を含むテーブルを作成すると、PostgreSQLは自動的に対応するTOASTテーブル(pg_toast_xxxxx)を作成する。
2. 小さなデータはTOASTに保存されない
データサイズが約2KB以下の場合、TOASTテーブルには保存されず、元のテーブルにそのまま格納される。
3. 圧縮可能なデータは圧縮される
PostgreSQLはデフォルトでデータを圧縮する(pglzアルゴリズム使用)。圧縮後に2KB以下になれば、TOASTテーブルには保存されない。
4. TOASTは約2KBごとにデータを分割する
大きなデータは約2KB(1996バイト + ヘッダ)ごとに分割され、chunk_seqで順序が管理される。最後のチャンクは残りのサイズになる。
今後の調査項目
- データを削除した場合、TOASTテーブルのレコードはいつ削除されるのか(即座か、VACUUMまで残るのか)
- 行外インメモリTOAST(PostgreSQL 14以降の機能)の動作確認
- TOAST戦略(PLAIN/EXTENDED/EXTERNAL/MAIN)の違いとパフォーマンスへの影響
- chunk_idと元テーブルの行(ctid)の対応関係
- 圧縮アルゴリズム(pglz vs lz4)の性能比較
- TOASTによるSELECT/UPDATE性能への影響
次回はこれらの調査の続きを行う。