Redisの「死」を制御せよ:メモリ管理とエビクション戦略の深淵
Redisを単なる「高速なKVS」として使っているうちは、まだ初心者だ。
真のアーキテクトにとって、Redisは「メモリという限られたリソースの上で、いかにしてエントロピーを制御し、決定論的なパフォーマンスを維持するか」という高度なオペレーティングシステムに近い。
今回は、Redisのメモリ管理の心臓部であるエビクション(Eviction)ポリシーの裏側を、実装レベルの視点から解剖する。
—
1. maxmemoryの罠:メモリは「空き」ではない
`maxmemory`を設定すれば安心だと思っているなら、それは幻想だ。Redisのメモリ管理は、単にデータサイズを監視しているわけではない。
- 断片化(Fragmentation)の恐怖: jemallocによるアロケーションには、どうしても断片化が伴う。`used_memory`が閾値に達していなくても、OS側のRSS(Resident Set Size)は膨れ上がる。
- メモリオーバーヘッド: Redisの各オブジェクトは、`robj`構造体というメタデータを持っている。特にハッシュやリストが多層化すると、データ本体よりもポインタやメタデータの占有率が跳ね上がる。
極限の知見: `maxmemory`の設定値は、物理メモリの限界値ではなく、「断片化係数(`mem_fragmentation_ratio`)」を考慮した「安全なマージン」を含めた値でなければならない。実運用では、常に余裕を持って80〜90%程度に抑えるのが鉄則だ。
—
2. 近似LRUアルゴリズムの真実
多くの開発者は、RedisのLRUが「完全なLRU(厳密な順序付け)」であると勘違いしている。しかし、もしRedisが全てのキーを厳密にソートして保持していたら、それはもはやKVSではなく、ただの巨大なオーバーヘッドの塊だ。
RedisのLRUは「近似LRU」である。
- サンプリングのメカニズム: `maxmemory-samples`で指定された数だけキーをランダムに抽出し、その中から最も古い(LRU)もの、あるいは利用頻度が低い(LFU)ものを捨てる。
- なぜランダムなのか: 全キーをスキャンするコストを避け、定数時間での処理を保証するためだ。`maxmemory-samples`を増やせば精度は向上するが、CPUコストは指数関数的に増大する。
デフォルトは5。高負荷なシステムではここを調整するが、
10を超えても精度の向上分は極めて微小。
CONFIG SET maxmemory-samples 5
—
3. ポリシー選択の極意:使い分けの哲学
エビクションポリシーの選択は、アプリケーションの「生存戦略」そのものだ。
allkeys-lru vs volatile-lru
- `allkeys-lru`: キャッシュとして割り切るなら最強だ。最も古いデータが消えることは、システム全体にとっての「新陳代謝」である。
- `volatile-lru`: 有効期限(TTL)付きのデータのみを対象とする。これは「寿命のあるデータ」と「永続させるデータ」が混在するアーキテクチャで必須となる。
LFU (Least Frequently Used) の導入
LRUが「最後にいつ使ったか」を追うのに対し、LFUは「どれだけ頻繁に使われたか」をカウントする。
- LFUが輝く場面: 一部のキーにアクセスが集中するが、そのキーが極めて古い場合。LRUだと「最近使っていない」と判定され捨てられてしまうが、LFUなら「アクセス頻度が高い」として保護される。
—
4. 限界を突破するためのアーキテクチャ最適化
メモリ管理を最適化するための、現場で培った「銀の弾丸」を紹介する。
A. オブジェクトの共有とエンコーディング
Redisは特定の条件下でメモリを劇的に圧縮する。
- Integer Shared: 0〜9999の整数は共有される。
- ZipList/ListPack: ハッシュやリストの要素数が少ない場合、ポインタの羅列をやめ、連続したメモリ領域に詰め込む。
/ Redis内部でのエンコーディング確認 /
/ 頻繁にOBJECT ENCODINGを実行し、最適化が効いているか監視せよ /
OBJECT ENCODING my_huge_hash
B. 運用上の絶対ルール:メモリのパニック回避
エビクションポリシーを設定したとしても、`maxmemory`に達した瞬間にRedisは「書き込み拒否(OOM)」を返す可能性がある。
1. アクティブな削除: Redisは書き込みのたびにメモリを解放しようとする。この処理コストがレイテンシスパイクを引き起こす。
2. 回避策: 限界に達する前に、アプリケーション側で「古いキーの削除(`DEL` / `UNLINK`)」をバッチで行うべきだ。`UNLINK`は非同期でメモリを解放するため、メインスレッドをブロックしない。
—
結びに代えて
Redisのメモリ管理を制する者は、システム全体のレイテンシを制御できる。
単に設定ファイルを弄るのではなく、データ構造のライフサイクルを理解し、メモリの「断片化」と「アクセスパターン」の相関をプロファイリングせよ。
Redisは、あなたの設計が甘ければ即座にOOMで報復するが、設計が正しければ数マイクロ秒という異次元のパフォーマンスで応えてくれる。
さあ、次はどのキーを犠牲にするか、アルゴリズムを再考する時間だ。
コメント