【実務・中級編】 TOASTテーブルの設計 – PostgreSQL

「またTOASTでハマってるのか? まあ、無理もないよ。PostgreSQLを触り始めて一番最初に『魔法の箱』だと思って、そのあと『実は厄介な地雷』だと気づくのがこのTOASTだからね」

そんな声が聞こえてきそうな気がしたので、今日は少し腰を据えて、PostgreSQLの「巨大データ処理の裏方」であるTOASTについて話をしようと思う。教科書的な定義はドキュメントに譲るとして、現場で「なぜ遅いのか」「なぜインデックスが効かないのか」を解決するための、実践的な勘所を共有するよ。

—

TOASTは「溢れたデータの避難所」だ

まず基本のおさらい。PostgreSQLには「1ページ(デフォルト8KB)に全データを詰め込む」という鉄則がある。でも、数MBのJSONや長文テキストを保存しようとしたらどうなる? ページに収まりきらないよね。

そこで登場するのが TOAST (The Oversized-Attribute Storage Technique) だ。
ページから溢れそうなデータを見つけると、PostgreSQLは気を利かせてデータを別テーブル(TOASTテーブル)に追い出し、メインテーブルには「ポインタ」だけを残してくれる。

これが何を意味するか。「メインテーブルの検索速度を維持しつつ、巨大データも扱える」という、エンジニアにとっての夢のような仕組みなんだ。でも、ここには落とし穴がある。

—

「しきい値」を意識したことはあるか?

TOASTが発動するデフォルトのしきい値は「2KB」だ。つまり、2KBを超える列があると、PostgreSQLは自動的に圧縮や外部格納を検討し始める。

ここで現場の知恵を一つ。もし、頻繁にアクセスする列が2KBギリギリのサイズで、TOASTに押し出されたり戻ったりを繰り返すと、クエリのたびに「ポインタを辿って別のテーブルを読みに行く」というオーバーヘッドが発生する。

そんな時は、`STORAGE`戦略を調整するのも手だ。

— 特定のカラムだけTOASTの挙動を変える
ALTER TABLE my_table ALTER COLUMN large_json SET STORAGE EXTERNAL;

`EXTENDED`(デフォルト)だと「圧縮+外部格納」だけど、CPU負荷を下げたい場合や、すでに圧縮済みのデータを入れる場合は、`EXTERNAL`(圧縮なし・外部格納のみ)を選ぶだけで、CPUのリソースを節約できる場面がある。

—

インデックス性能への「見えないダメージ」

ここからが一番伝えたいことだ。「TOASTされた列にインデックスを貼る」という行為について。

みんなやりがちなんだけど、数MBのJSON全体にそのままインデックスを貼ろうとすると、インデックスサイズが巨大化して、メモリ(shared_buffers)を食いつぶすことになる。結果、インデックス検索どころか、スキャンそのものが遅くなる。

解決策:インデックスの「的」を絞れ

巨大なデータを扱うときは、必ず「必要な部分だけ」をインデックス化する癖をつけよう。

— JSONの特定のキーだけインデックスにする(これなら軽量だ)
CREATE INDEX idx_json_user_id ON my_table ((data->>’user_id’));

こうすれば、TOASTされた本体をわざわざロードしなくても、インデックスツリーだけで検索が完結する。インデックス設計の鉄則は、「いかにポインタを辿らせないか」だ。

—

現場で役立つチェックリスト

TOASTが原因でパフォーマンスが悪化しているかも?と思ったら、以下のコマンドで現状を確認してみてくれ。

— どのテーブルがTOASTテーブルを使っているか確認する
SELECT relname, reltoastrelid FROM pg_class WHERE reltoastrelid != 0;

— 特定のテーブルのストレージ情報を確認
SELECT attname, attstorage FROM pg_attribute
WHERE attrelid = ‘my_table’::regclass;

もし、特定のテーブルの読み込みが異常に遅いなら、`pg_toast` スキーマにあるテーブルの肥大化を疑うべきだ。`VACUUM`が効いていないと、TOASTテーブルはどんどん太っていく。

—

最後に:エンジニアとしてのアドバイス

「データベースに何でも詰め込める」というのはPostgreSQLの強みだけど、「詰め込みすぎないのが腕の見せ所」でもある。

  • 巨大データは、本当にDBに保存する必要があるのか?
  • S3のようなオブジェクトストレージに逃がして、DBにはパス(URI)だけ持つべきではないか?

こういう判断を設計段階で下せるのが、プロのエンジニアだ。TOASTは便利な魔法だけど、それに頼りすぎると、後で泣くのは自分自身だよ。

もし今、パフォーマンス問題に悩んでいるなら、まずは`EXPLAIN (ANALYZE, BUFFERS)`を引いてみてくれ。`Heap Fetches`が異常に高かったら、それはTOASTが悲鳴を上げている証拠だ。

さて、今日はここまで。また現場で困ったことがあったら聞きに来てくれ。一緒に最高にクールなDBアーキテクチャを作っていこう。

コメント

タイトルとURLをコピーしました