やあ。最近、PostgreSQLのJSONBを「とりあえず何でも突っ込める便利な箱」として使って、後からクエリの遅さに頭を抱えている現場をよく見かけるんだよね。
「JSONBは便利だけど、インデックスを貼ろうとすると途端に難しくなる」……そう感じているなら、それは君が正しい道を進んでいる証拠だよ。今日は、JSONBとどう付き合えばパフォーマンスを殺さずに済むのか、現場で培った「泥臭いけど効く」戦略を共有するね。
—
1. まずは基本の「GINインデックス」を押さえる
JSONBの検索を速くしたいと思ったら、真っ先に思い浮かぶのがGIN(Generalized Inverted Index)だよね。デフォルトで作成されるインデックスは、JSONB内のあらゆるキーと値をインデックス化してくれる。
CREATE INDEX idx_data ON my_table USING GIN (data);
これで `data @> ‘{“status”: “active”}’` のようなクエリは爆速になる。でも、ここで注意が必要なんだ。「とりあえず全部インデックスする」ということは、更新のたびにインデックスの書き換えコストが跳ね上がるってこと。データ更新頻度が高いテーブルでこれをやると、あっという間に書き込み性能がボトルネックになるよ。
2. 劇的に速くなる「jsonb_path_ops」の秘密
デフォルトのGINインデックスは柔軟だけど、実はちょっと「おしゃべり」すぎるんだ。もし、君が「特定のキーと値のペア」を検索することしかしないなら、`jsonb_path_ops` を使うべきだよ。
CREATE INDEX idx_data_path ON my_table USING GIN (data jsonb_path_ops);
これを使うと、インデックスサイズが小さくなり、検索効率も向上する。なぜかって? 標準のGINは「値そのもの」もインデックスに含めるけど、`jsonb_path_ops` は「パスと値のハッシュ値」をインデックス化するからだ。
ただし、注意点が一つ。`jsonb_path_ops` は `@> (包含演算子)のみをサポートするという制限がある。`?` や `?&` といった演算子は使えなくなるから、自分のクエリパターンを事前にしっかり見極めてから選んでくれ。
3. 「部分インデックス(Partial Index)」こそが最強の武器
個人的に一番おすすめしたいのがこれだ。「テーブルのデータ全部にインデックスを貼る必要なんてある?」という問いかけから始まる手法だよ。
例えば、「エラーログだけはJSONBの中身で高速検索したいけど、正常なログはインデックス不要」というケースは多いよね。そんな時はこう書く。
CREATE INDEX idx_error_logs ON my_table USING GIN (data)
WHERE (data ->> ‘level’) = ‘error’;
これの何がすごいって、インデックスのサイズが劇的に小さくなること。サイズが小さければメモリ(shared_buffers)に乗りやすくなるし、更新時のオーバーヘッドも最小限に抑えられる。無駄なインデックスを作らない、これこそがデータベース設計の美学だと思わない?
4. 特定のパスに絞った「式インデックス」
もし、JSONBの中の特定の深い階層だけを頻繁に検索するなら、わざわざ巨大なGINインデックスを作る必要はないかもしれない。
— data -> ‘user’ -> ‘id’ だけを抜き出してB-treeインデックスを貼る
CREATE INDEX idx_user_id ON my_table ((data -> ‘user’ ->> ‘id’));
JSONBのGINは万能だけど、B-treeの方が得意なケースも多々あるんだ。特に値が数値やIDなどのスカラー値なら、B-treeの方が検索効率が良いし、ソートも効く。ここらへんの使い分けができるようになると、一人前のエンジニアとして一目置かれるようになるよ。
—
最後に:銀の弾丸はない
今日話したテクニックは強力だけど、銀の弾丸じゃない。「とりあえず全部GIN」はやめて、まずは実行計画(`EXPLAIN ANALYZE`)を見て、本当にインデックスが使われているか、無駄なスキャンをしていないかを確認する癖をつけてほしい。
JSONBは柔軟性の代償として「設計者の意図」を強く要求するデータ型だ。面倒かもしれないけど、この一手間が数ヶ月後の君を救うことになる。
もし、「うちのこの複雑なクエリはどうすればいい?」っていう悩みがあれば、いつでも相談してくれ。現場の泥臭い知恵をまた共有するよ。頑張ろうぜ!
コメント