「PostgreSQLのテーブル設計、なんとなくデフォルトのまま作ってない?」
もしそうなら、ちょっとだけ時間をください。今日はPostgreSQLの「知る人ぞ知る守護神」、TOAST(The Oversized-Attribute Storage Technique)の話をしよう。
大規模なデータを扱うシステムを設計していると、必ずと言っていいほど「テキストカラムが肥大化して、パフォーマンスがガタ落ちする」という壁にぶつかるんだ。このとき、PostgreSQLが裏側で何をしているのか、どう制御すべきかを知っているかどうかで、エンジニアとしての価値が大きく変わる。
—
1. TOASTってそもそも何者?
PostgreSQLのページサイズはデフォルトで8KB固定だ。でも、世の中には10KBや1MBを超えるような巨大なテキストやJSONBが溢れている。
「じゃあ、そんな巨大データはどこに格納されるの?」
そこで登場するのがTOASTだ。PostgreSQLは、ある一定サイズ(デフォルトで2KB)を超えたデータを自動的に別領域へ追い出し、圧縮したり分割したりして格納する仕組みを持っているんだ。これにより、8KBという制約の中でも巨大データを扱えるようになっている。
要は、「メインのテーブル領域を汚さないための避難所」だと思っていい。
—
2. 4つのストレージ戦略:使い分けがプロの技
各カラムには、データがTOAST領域にどう扱われるかを決める「ストレージ戦略」が設定できる。これを理解するのが、パフォーマンスチューニングの第一歩だ。
- PLAIN: TOASTを使わない。圧縮も分割もしない。小規模なカラムに最適。
- EXTENDED: デフォルトの設定。 まず圧縮を試み、それでもデカければTOASTへ追い出す。一番汎用性が高い。
- EXTERNAL: 圧縮はしないが、分割してTOASTへ追い出す。高速な読み取りが必要な巨大データ向け。
- MAIN: 圧縮するが、TOASTへの追い出しは「どうしても入り切らない時だけ」にする。読み取り速度を優先したい時に使う。
—
3. 実務で「おっ」と思わせる設定術
例えば、ログを格納するテーブルで、検索には使わない巨大なJSONBカラムがあるとしよう。デフォルトのままだと、頻繁に圧縮・解凍が発生してCPUを無駄遣いする可能性がある。
そんなときは、こうやって制御するんだ。
— テーブル作成時にストレージ戦略を指定する例
CREATE TABLE system_logs (
id SERIAL PRIMARY KEY,
event_name TEXT,
payload JSONB — ここが巨大になる予定
);
— payloadカラムを「圧縮なし・分割優先」のEXTERNALに変更する
ALTER TABLE system_logs ALTER COLUMN payload SET STORAGE EXTERNAL;
ここが現場の知恵:
もし、読み取り頻度が極端に低く、ディスクI/Oを抑えたいなら、`EXTERNAL`にしておくとCPU負荷が下がる。逆に、頻繁にアクセスするなら`MAIN`を選択して圧縮後のデータがメイン領域に収まるように祈る、というのも一つの戦略だね。
—
4. 「TOASTの閾値」をいじる勇気
実は、TOASTが発動する「2KB」という閾値、これもテーブル単位で調整できるんだ。
— 5KBを超えたらTOASTに追い出すように変更
ALTER TABLE system_logs SET (toast_tuple_threshold = 5120);
「なんでこんなことするの?」と思うかもしれない。例えば、「メイン領域にはできるだけ多くの行を詰め込みたい(スキャン効率を上げたい)」という場合は、この閾値をあえて小さくする。逆に、「CPU負荷を下げて、少しでもメイン領域で完結させたい」なら閾値を上げる。
これはまさに、アプリケーションの特性とハードウェアの性能を見極めたエンジニアだけが許される「特権的な設定」だよ。
—
最後に:慢心は禁物
TOASTを理解すると「とりあえず何でも入るから安心」と思いがちだけど、それは間違いだ。
TOASTにデータが溢れると、物理的な読み取りが増えてパフォーマンスは確実に劣化する。 結局のところ、データモデルを適切に正規化して、巨大なテキストを別テーブルに分離する「テーブル設計の基本」に勝るものはないんだ。
TOASTはあくまで「最後の砦」。設計段階で、「このカラムは本当にここのテーブルに必要か?」を問い続けること。それができるエンジニアが、結局一番長く愛されるシステムを作れるんだよ。
現場からは以上だ。また何かあればいつでも聞いてくれ。
コメント