世界を動かす技術を、日本語で。

例で学ぶブルームフィルタ

概要

  • Bloom filter は、要素が集合に存在するかを高速かつ省メモリで判定するデータ構造
  • 確率的データ構造 であり、偽陽性が発生する可能性
  • ビットベクトルと 複数のハッシュ関数 を利用
  • フィルタサイズやハッシュ関数数で 誤判定率を調整可能
  • 多様な用途や実装例が存在し、最適化には計算式を利用

Bloom filterの基礎

  • Bloom filter は、要素が集合に存在するかを 高速かつ省メモリ で判定するデータ構造
  • この効率性の代償として、 確率的な誤判定(偽陽性) が発生
  • 基本構造は ビットベクトル で、各要素を複数のハッシュ関数でハッシュ化し、そのインデックスのビットを1に設定
  • 要素追加時は、 複数のハッシュ関数 でビットベクトルの該当ビットを1に
  • 存在判定時も同様にハッシュ化し、全ビットが1なら「存在するかも」、1つでも0なら「絶対に存在しない」と判定

ハッシュ関数の選択

  • 独立性と一様性 の高い、かつ高速なハッシュ関数が推奨
  • 暗号学的ハッシュ関数(例:sha1)は非推奨 (速度面で不利)
  • よく使われるハッシュ関数例
    • murmur
    • xxHash
    • fnvシリーズ
    • HashMix
  • 実装例
    • Chromium: murmur
    • Plan9: シンプルなハッシュ
    • RedisBloom: murmur
    • Apache Spark: murmur
    • influxdb: xxhash
    • bloomd: murmur, SpookyHash
    • Sqlite: 独自実装
    • RocksDB: xxh3
    • ScyllaDB: murmur

Bloom filterのサイズ設計

  • フィルタサイズ(m)とハッシュ関数数(k) で偽陽性率を調整可能
  • 偽陽性率の近似式: (1-e^(-kn/m))^k
  • 設計手順
    • 想定挿入要素数(n)を決定
    • フィルタサイズ(m)を仮決定
    • 最適なハッシュ関数数(k)を計算: (m/n)ln(2)
    • 偽陽性率を計算し、許容範囲外ならmを再調整

性能とメモリ効率

  • 挿入・判定ともに O(k) の計算量
  • メモリ効率は 許容する偽陽性率想定要素数 に依存
  • 要素範囲が狭い場合は ビットベクトル が有利
  • 要素数の見積もりが困難な場合は ハッシュテーブルスケーラブルBloom filter が適切

主な用途

  • キャッシュの存在判定

  • ネットワークパケットのフィルタリング

  • データベースのクエリ最適化

  • バイオインフォマティクス等の大規模データ処理

    • 詳細用途や実例は WikipediaC. Titus Brownの講演 を参照

参考文献

  • Network Applications of Bloom Filters: A Survey, Broder and Mitzenmacher
  • Wikipedia: Bloom filter
  • Less Hashing, Same Performance, Kirsch and Mitzenmacher
  • Scalable Bloom Filters, Almeida et al

Hackerたちの意見

僕が好きなトリックがあるんだ。メンバーシップチェックをたくさんやる小さいセットに対して、64ビットのブルームフィルターをトリビアルなハッシュ関数で追加するってやつ。これ、バカみたいに聞こえるかもしれないけど、コストがすごく小さいから、賭けみたいに使えるんだよね。もしうまくいかなかったら、挿入やメンバーシップチェックに10nsくらい追加されるだけだけど、うまくいったらものすごく作業を節約できるんだ。

Chromiumは色んなところでこれをやってるよ。記事ではmurmurを使ったSafe Browsingだけリンクしてるけど、レンダラー(Blink)は一般的にrapidhashを使ってて、例えば以下のようなマイクロフィルターを使ってるんだ: - 特定のケースでのquerySelector() - CSSバケットのハッシュルックアップのプレフィルタリング - アクセシビリティのための特定のAria属性を探すときの要素の迅速な拒否 こんな小さなフィルター(32ビットや64ビット)が機能するなんて驚きだけど、実際に機能することが多いんだ。もっと大きなブルームフィルターもあるよ。(僕はいくつか追加した)

このアーティクルは、僕みたいな人に向けて書かれてるね。名前は聞いたことあったけど、言及されるたびに調べようと思ってたんだ。あなたの記事を見て、やっと調べてみたら、探してた完璧なイントロだったよ :)

同じく。

iBooks(検索用)に実装するように言われた時に知ったんだ — もう10年以上前の話。

ほんと楽しいよね。問題が出てきて、ブームフィルターが必要になるとワクワクしちゃう。まあ、ドメインによってはそんなに頻繁にはないけどね。

Eli Benderskyからの別のブルームフィルターに関する投稿もすごく良かったので、興味がある人は読んでみてね: https://eli.thegreenplace.net/2025/bloom-filters/

ブルームフィルターの別のビジュアライゼーションはこのページの最後にあるよ: https://www.chrislaux.com/hashtable.html

僕はCassandraのリードスパイクをデバッグしているときにブルームフィルターにハマったんだ。キーが存在しないときでもたくさんのsstableルックアップがあって、最初は意味が分からなかったけど、各sstableにブルームフィルターがあってディスクをスキップするためのものだと気づいたんだ。でもデフォルトの偽陽性率が0.1くらい高すぎて、うちのケースには合わなかった。ほとんどのリードはキャッシュミスだったから、その偽陽性が厄介で、0.01に変更したら、少しメモリを使ったけど無駄なリードが減って、p99のリードレイテンシが16-18%も改善されたよ。

過去にブルームフィルターを使ったことはあったけど、どう機能するかはよく理解してなかったんだ。ある日、Wikipediaの記事を参考にして32ビットのMurmurHash関数を使って実装してみたら、すごく簡単だったんだ。C++を使ってるなら、std::vector(C++23からはstd::bitsetも)を使うと、ビットを効率的に保存するのがもっと簡単になるよ。

2009年に大学のためにCUDAでブルームフィルターを書いたんだ。僕の指導教官は元Nvidiaの人だった。その後、キャリアの中でGPUプログラミングは全くやらなかったけど、もし別の選択をしていたら、1億ドルくらい稼げたかもしれないな。

ビットコインを買っておけばもっと儲けられたかも…って言ってるだけだけど。

1970年のCSのアイデアだって考えると、ありえないよね。GPGPUのアイデアはどれも自由だったはず。10年前にGPUでハッシュキャッシュの実装を書いたけど、今は価値ないだろうな。

名誉プロジェクトで機械学習アルゴリズムをCUDAに移植して、肩をすくめて組み込みプログラミングに行ったよ。

最近、ログメッセージのアンチスパム機能にブルームフィルターを使ったんだ。ロガーではメッセージをハッシュ化してフィルターに入れた。エントリーがあればメッセージは出力しなかったんだ。それから数秒ごとにフィルターを繰り返し処理して、すべてのビットをクリアしてた。フィルターのビットを原子的にクリアする心配がいらなくて、メッセージが入ってきてビットがクリアされてたら、再度ログされるのに十分だったから、うまくいったよ。以前の実装は見たメッセージのカウントを保持していて、Nで飽和しちゃってたから、特定のメッセージが繰り返しログされると、フィルターがクリアされる速度でしか見れなかったんだ。ブルームフィルターについて知ってからしばらく経って、実際に役立つ使い方を見つけられて満足だったよ。

作者へのメモ -- インタラクティブな部分が大好き。ポイントを強調するために一つ提案があるんだけど、ハッシュ衝突がある2つの文字列の例をユーザーに示して、一つをボックスに入力させて、もう一つを別のボックスでテストさせるといいと思う。それで「セットに入ってるかも!」って答えが出る理由がわかるはず。

"bloom" と "demonstrators"(末尾のスペースに注意) どちらも衝突する: fnv: 7 murmur: 12

ブームフィルターを重ねて使えば、木をクエリするみたいにハッシュ化できるよ。