【テクニカル・上級編】 メモリ管理とエビクションポリシー – Redis

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で報復するが、設計が正しければ数マイクロ秒という異次元のパフォーマンスで応えてくれる。

さあ、次はどのキーを犠牲にするか、アルゴリズムを再考する時間だ。

コメント

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