Hashインデックス、実は「食わず嫌い」してない?PostgreSQLの隠れた実力派を使いこなす話
現場でバリバリやってるエンジニアのみんな、お疲れ様。
今日はちょっとマニアックかもしれないけど、データベースのパフォーマンス改善において「ここぞ」という時に効くHashインデックスの話をしようと思う。
正直なところ、現場で「とりあえずインデックス貼っとくか」となると、9割方はB-treeを選ぶよね。それは正しい判断だ。B-treeは汎用性が高くて、範囲検索もソートもこなせる。いわば「定食のメインディッシュ」みたいな存在だ。
でも、たまに「このカラム、等価比較(`=`)しかしないんだよな」っていうケースがあるはずだ。そのとき、B-treeの万能性におんぶに抱っこしてないか?今日は、そんなニッチだけど尖った性能を持つHashインデックスの活用術を紐解いていくよ。
—
1. なぜ「Hash」なのか? B-treeとの決定的な違い
Hashインデックスが何をしているかというと、検索対象の値をハッシュ関数にぶち込んで、その「ハッシュ値」を使ってデータを探している。
B-treeが「木構造」を辿って値を比較していくのに対して、Hashは「計算一発」で場所を特定する。これが何を意味するか。データ量が増えても、検索コストがほとんど変わらないんだ。
B-treeと比較したときの「旨味」
- メモリ効率が圧倒的: B-treeはノードの構造を維持するために、それなりにオーバーヘッドがある。一方、Hashインデックスは非常にコンパクト。インデックスサイズが小さければ、それだけメモリ(Shared Buffers)に乗る確率が上がり、ディスクI/Oを抑えられるってわけだ。
- 等価比較の爆速: `WHERE user_id = ‘12345’` のようなクエリにおいて、Hashはまさに無双する。
2. 昔は「おもちゃ」だった? PostgreSQL 10の革命
ここまで読んで、「いやいや、Hashインデックスってクラッシュしたら壊れるんでしょ?」と思った人、鋭いね。実はそれ、PostgreSQL 9.6までの話なんだ。
PostgreSQL 10以降、HashインデックスはWAL(Write Ahead Log)に書き込まれるようになり、クラッシュリカバリに対応した。 これによって、ようやく「実務で安心して使えるインデックス」として昇格したんだ。だから、今のモダンな環境で「古い知識」のまま避けているとしたら、それはすごくもったいないことなんだよ。
3. 実践!Hashインデックスの使いどころ
じゃあ、どんな時に使うべきか。コードを見てみよう。
— UUIDカラムや、長大な文字列の等価比較に最適
CREATE INDEX idx_user_token_hash ON users USING HASH (token);
注意点:ここは覚えて帰ってくれ
Hashインデックスは「等価比較」に特化している。これに尽きる。
- 範囲検索はできない: `WHERE token > ‘abc’` とか `BETWEEN` は使えない。
- ソートもできない: `ORDER BY` には貢献しない。
- 複合インデックスは組めない: あくまで単一カラム専用だ。
だから、「めちゃくちゃ頻繁に検索されるけど、範囲検索は絶対に発生しない巨大なカラム」、例えばUUIDや、ハッシュ化されたトークンなんかがベストなターゲットになる。
4. WAL書き込み負荷の現実
一つだけ注意点を。HashインデックスもWALには書き込まれるから、更新(UPDATE/INSERT)がめちゃくちゃ多いテーブルだと、B-tree同様にそれなりの書き込み負荷はかかる。
ただ、B-treeと比べて「インデックス構造そのものが小さい」おかげで、キャッシュ効率が良い。結果的に、メモリに乗るサイズに収まりやすく、大規模なシステムで「B-treeだとインデックスが肥大化しすぎてメモリから溢れる」という悩みを解消できることもある。
—
まとめ:エンジニアとしての「引き出し」を増やそう
今日伝えたかったのは、「何でもかんでもB-tree」という思考停止から脱却しよう、ということだ。
- 等価比較しかしない巨大なカラムはないか?
- インデックスのサイズを削減することで、I/O負荷を減らせないか?
こういった視点でデータベースを見直すと、これまで見えなかったボトルネックが見えてくるはずだよ。もちろん、闇雲に導入するのはNGだ。まずは開発環境で `EXPLAIN ANALYZE` を叩いて、実行計画とコストを比較する。そのプロセスこそが、エンジニアとしての技術力を高めてくれる。
Hashインデックス、食わず嫌いせずにぜひ試してみてくれ。困ったことがあれば、またいつでも聞いてくれよな!
コメント