Redisを殺さないための『SCAN』— アーキテクチャから紐解く反復走査の真髄
Redisを扱うエンジニアとして、`KEYS ` を本番環境で実行するような愚行は、もはや「死の宣告」と同義だ。なぜそうなのか。そして、なぜ `SCAN` が単なる「KEYSの代替品」ではなく、分散システムにおける妥協なき設計の結晶なのか。
今日は、ドキュメントの表面をなぞるような解説はしない。Redisの内部構造であるハッシュテーブル(`dict`)の挙動から、なぜSCANが「一見奇妙なカーソル体系」を採用しているのか、その深淵に迫る。
—
1. KEYSコマンドの罪:シングルスレッドの終焉
Redisはシングルスレッドで駆動する。その本質は「O(1)の高速処理」にある。
`KEYS ` は、Redisの全キー空間を管理する巨大なハッシュテーブルを全走査(O(N))する。この間、Redisは他のすべてのリクエストをブロックする。
もし君が数百万のキーを抱えるメモリ空間で `KEYS ` を叩けば、イベントループは数ミリ秒〜数秒間完全に停止する。それは即ち、プロダクション環境の全接続がタイムアウトし、雪崩式に障害が連鎖する悪夢を意味する。
2. SCANの核心:内部実装の「妥協なき設計」
`SCAN` は、このブロック問題を解決するために「イテレータ」という概念を持ち込んだ。だが、ここからが重要だ。Redisのハッシュテーブルは、データの挿入・削除に伴い、再ハッシュ(Rehash)という構造変化を繰り返す。
もし反復走査中にハッシュテーブルが拡張されたらどうなるか?
単純なインデックスによるトラバースでは、キーの重複や欠落が確実に発生する。これを防ぐためにRedisが採用したのが「リバース・バイナリ・インクリメント(Reverse Binary Increment)」というアルゴリズムだ。
なぜリバースなのか?
ハッシュテーブルがサイズを2倍に拡張する際、既存のデータは新しいテーブルへ効率的に移動される。リバース・バイナリ・インクリメントは、テーブルのサイズが指数関数的に拡大しても、走査済みの範囲を最大限に保全する。
- 通常のインクリメント: サイズが変わると走査の整合性が崩壊する。
- リバース・バイナリ: 下位ビットから上位ビットへ向かって走査することで、ハッシュテーブルの分割・統合時においても、データの取りこぼしを最小限(あるいは再ハッシュ時の重複のみ)に抑えることができる。
3. 実践:アーキテクトのためのSCAN最適化
SCANを使う際、最も重要なのは `COUNT` オプションの解釈だ。多くのエンジニアが「一度に取得する件数」と誤解しているが、正しくは「各ステップでスキャンを試みるバケット数」である。
1万件のバケットを走査対象にする。
実際には、ハッシュ衝突の影響で戻り値は1万件より少なくなることもあれば、
空のバケットが多ければ0件が返り、カーソルだけが進むこともある。
SCAN 0 MATCH “user:session:” COUNT 10000
限界を突破する運用Tips
1. COUNTの値をケチるな: `COUNT` が小さすぎると、Redisは何度もシステムコールを繰り返し、ネットワークのオーバーヘッドが積み重なる。1,000〜10,000程度が、メモリとレイテンシのスイートスポットだ。
2. 不完全性を許容する: SCANは「整合性(Consistency)」ではなく「可用性(Availability)」を優先する。再ハッシュ中に発生したキーは、走査から漏れるか、あるいは二度出現する可能性がある。この仕様を前提とした設計(冪等性の確保など)をアプリケーション層に組み込むのが、プロの仕事だ。
4. なぜ「全走査」が必要なのかを疑え
最後に、アーキテクトとして最も伝えたいこと。
「SCANが必要なアーキテクチャ」そのものを疑うべきだ。
- 大規模なキーの走査が必要な場合、Redisのキー設計自体が「アンチパターン(フラットすぎる)」である可能性が高い。
- 特定のプレフィックスを頻繁に走査するなら、`SET` や `SORTED SET` を使ったインデックス管理を検討せよ。
- そもそも、Redisは「検索エンジン」ではない。検索クエリが必要なら、ElasticsearchやMeilisearchを併用するのがエンジニアリングの定石だ。
結びに代えて
Redisの `SCAN` は、非常に巧妙で、かつ「運用上の敗北」を技術的にカバーするための高度なツールだ。
しかし、本当に優れたシステムは、SCANさえも必要としない。データ構造を最適化し、クエリの複雑性をデータ側にオフロードする。それが、Redisを極めた先に見える景色だ。
カーソルを回すのは、最後の手段にせよ。君のRedisインスタンスが、今日も平和であることを祈る。
コメント