PostgreSQL の B-tree を TEXT カラムに張ると行サイズ上限(2704B)で長いレコードの INSERT が落ちる
PostgreSQL
データベース
設計判断
知識
判断
運用
PostgreSQL で TEXT/可変長カラム(タイトル+本文など)にプレーン B-tree インデックスを張ると、インデックス行が 1 行あたり約 2704 バイト(btree version 4 の上限) を超えた瞬間に INSERT/UPDATE が失敗する。
バイト基準なので、日本語など 3 バイト文字が多いと数百文字で、ASCII 中心でも約 2700 文字で頭打ちになり、「特定の長いレコードだけ書き込めない」症状になる。文字数では閾値が一定に見えない点が紛らわしい。
さらに無益なことが多い
この種の index は「キーワード検索の高速化」目的で張られがちだが、検索が col ILIKE '%word%'(先頭ワイルドカードの部分一致)なら B-tree は使われない。性能に寄与せず、行サイズ上限で書き込みを壊すだけの純粋な負債になりやすい。
判断基準・対処
- 部分一致(
ILIKE '%...%'/LIKE '%...%')には B-tree でなくpg_trgmの GIN インデックス(gin_trgm_ops)を使う。trigram GIN は行サイズ上限がなく、部分一致に実際に効く。 - 全文検索なら
to_tsvector+ GIN、意味検索なら pgvector など別系統。生 TEXT への B-tree は避ける。 - 長文を完全一致で引きたいだけなら、ハッシュ列(
md5(col)等)や先頭 N 文字の式インデックスにする。 - アプリ側バリデーションの最大長(例: 50000 文字)と index のバイト上限が乖離していると「検証は通るが INSERT で落ちる」窓ができる。index を持つなら両者を整合させる。
切り分け・検証
- 「特定の長いレコードだけ保存できない」症状では、アプリのエラー文言を鵜呑みにせず DB ログを見る(
SQLSTATE 54000/index row size ... exceeds btree ... maximum 2704が出る)。bulk 系ハンドラは内部 DB エラーを汎用 400(不正なリクエスト)にマスクしがちで、真因はログ側にしか出ないことがある。 - 再現は、対象 index がある状態で 2704 バイト超の値を INSERT し当該エラーを確認。index を trgm GIN に張り替えると通る。