【実務・中級編】 ハッシュインデックス – PostgreSQL

「PostgreSQLのインデックスといえばB-tree」。これはもう、我々の業界では教条的なまでの常識ですよね。実際、9割以上のケースでB-treeを選んでおけば間違いありません。

でも、たまに「このカラム、等価比較(=)しかしないんだけど、B-treeだとインデックスが肥大化しすぎてメモリ効率が悪いな」と感じることはありませんか?

そんな時、思い出してほしいのが「ハッシュインデックス」です。今日は、実務で使うエンジニアに向けて、この「知る人ぞ知る」存在をどう使いこなすべきか、現場の視点で語らせてください。

—

ハッシュインデックスって何者?

簡単に言うと、ハッシュ関数を使って計算した「ハッシュ値」でデータを管理するインデックスです。

B-treeがデータを順番に並べて「範囲検索(> や <)」や「ソート」に対応しているのに対し、ハッシュインデックスは「その値と一致するかどうか(=)」だけを高速に探すことに全振りしています。

実はPostgreSQL 10以前は、ハッシュインデックスはWAL(Write Ahead Log)に書き込まれず、クラッシュリカバリの際にインデックスが壊れるという「地雷」のような存在でした。でも、今のPostgreSQLは違います。ちゃんとWALにも対応し、レプリケーション環境でも安全に使えるようになっているんです。

なぜB-treeではなくハッシュを選ぶのか

理由はシンプル。「サイズが小さいから」です。

特に、UUIDや長めの文字列(URLやメールアドレスのハッシュ値など)をインデックスにする場合、B-treeだとツリー構造のオーバーヘッドでインデックスサイズが巨大化しがちです。

一方で、ハッシュインデックスはハッシュ値そのものを管理するため、インデックスの「幅」が短く、結果としてインデックス全体のサイズが小さくなるケースが多い。インデックスが小さくなれば、メモリ上のキャッシュ効率が上がり、結果として検索速度が劇的に向上する……というわけです。

—

実践:どうやって使うのか

使い方は、普通のインデックス作成とほとんど変わりません。`USING HASH` を指定するだけです。

— 例えば、UUIDカラムに対するインデックス
CREATE INDEX idx_user_uuid_hash ON users USING HASH (user_id);

これだけで、`SELECT FROM users WHERE user_id = ‘…’` のようなクエリが、B-treeよりも効率的に処理される可能性があります。

—

現場で気をつけるべき「落とし穴」

ここまで聞くと「じゃあ全部ハッシュにすればいいじゃん!」と思うかもしれませんが、それは早計です。いくつか注意点があります。

1. 範囲検索やソートは「絶対に」できない

当然ですが、`WHERE user_id > ‘…’` や `ORDER BY user_id` が必要なカラムには使えません。これを使ってしまうと、インデックスが無視されてフルスキャンが走ります。

2. 複数のカラムを組み合わせたインデックスは作れない

B-treeなら `CREATE INDEX … ON (col1, col2)` といった複合インデックスが作れますが、ハッシュインデックスは単一カラム専用です。

3. レプリケーションとWALの負荷

昔よりは改善されましたが、それでも大量の更新が頻発するテーブルでハッシュインデックスを多用すると、WALログの出力量が増え、レプリケーションラグの原因になることがあります。更新頻度が激しいテーブルへの適用は、慎重にベンチマークを取るのが鉄則です。

—

結局、どう使い分けるべき?

僕が現場で判断する時の基準はこんな感じです。

  • 基本はB-tree: 迷ったらB-tree。これで遅いと感じるまでがスタートラインです。
  • ハッシュを検討するケース:
  • 検索条件が「完全一致」のみ。
  • 対象カラムの値が長く、インデックスサイズが肥大化してメモリに乗らない。
  • 特定の巨大テーブルで、ピンポイントの検索速度をあと一歩絞り出したい。

最後に:まずは計測から

データベースのチューニングは「勘」でやると大抵失敗します。`EXPLAIN ANALYZE` を使って、実際にどちらのインデックスがどのくらい速いのか、そしてどれくらいディスクを占有しているのかを比較してみてください。

インデックスのサイズは以下のクエリで確認できます。

SELECT pg_size_pretty(pg_relation_size(‘idx_user_uuid_btree’)) as btree_size,
pg_size_pretty(pg_relation_size(‘idx_user_uuid_hash’)) as hash_size;

理論を知ることも大事ですが、最後はデータとマシンに聞くのが一番。ぜひ、次の案件で「ここ、ハッシュでいけるかも?」と思ったら、試してみてください。

では、また次回の記事で!現場からは以上です。

コメント

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