【テクニカル・上級編】 メモリ削除ポリシー (Eviction Policies) – Redis

Redisメモリ管理の深淵:Eviction Policyが隠蔽する「妥協」と「真実」

Redisを単なる「高速なKVS」と捉えているのであれば、それはまだ入り口に立ったに過ぎない。メモリが飽和したその瞬間、Redisの内部で何が起きているのか。`maxmemory`に達したとき、Redisは冷徹なアルゴリズムに従ってデータを切り捨てる。

この「削除の決断」こそが、システムのレイテンシ、ひいてはビジネスの命運を左右する。今回は、ドキュメントの表面をなぞるだけでは決して辿り着けない、RedisのEviction Policyの深層について解説する。

—

1. 近似LRUの欺瞞:なぜRedisは「正確なLRU」を選ばなかったのか

多くのエンジニアが「LRU(Least Recently Used)」という言葉から、双方向連結リスト(Doubly Linked List)による厳密な順序管理を想像する。だが、RedisのLRUは「近似(Approximation)」である。

なぜ厳密なLRUではないのか?

厳密なLRUを実装するには、アクセスがあるたびにリストを操作し、ポインタを書き換える必要がある。これはロック競合の温床となり、シングルスレッドで動作するRedisにおいて致命的なパフォーマンス低下を招く。

Redisはこれを回避するため、「サンプリング」という手法を採用している。

// Redisにおける近似LRUのサンプリング処理の概念図
// maxmemory-samplesの数だけランダムにキーを選び、その中で最も古いものを捨てる
// この「ランダムサンプリング」が、計算量O(1)を維持する鍵となる
void evictionPoolPopulate(int dbid, dict sampledict, struct evictionPoolEntry pool, dict keydict) {
// 実際の実装では、サンプリングされたキーのidle timeを比較し、
// プール内で最も適したものを削除候補として選択する
}

この「数個のキーをランダムに選んで比較する」というアプローチは、サンプル数(`maxmemory-samples`)が5〜10あれば、理論的にかなり正確なLRUに近い結果を生む。厳密な整合性を捨て、統計的な最適解を選ぶ。これがRedisが伝説的な速度を維持するための「賢明な妥協」だ。

—

2. LFUの導入:歴史的アクセス頻度という概念

Redis 4.0で導入されたLFU(Least Frequently Used)は、単なる「古いもの」ではなく「使われていないもの」を捨てるために存在する。

特筆すべきは、そのデータ構造だ。Redisは24ビットのフィールドを使い、以下を記録している。
1. Logarithmic Counter (8bit): アクセス頻度。飽和しないよう、時間の経過とともに減衰する。
2. LRU (16bit): 最後のアクセス時刻(分単位)。

ここでの肝は、「頻度が同じなら、古い方を捨てる」という組み合わせだ。単純な頻度カウントだけでは、過去に一度だけ爆発的にアクセスされた「ゾンビデータ」がメモリを占拠してしまう。LFUは、時間減衰と頻度を組み合わせることで、ワークロードの変化に追従する動的なメモリ管理を実現している。

—

3. 運用における「デッドライン」:Eviction Policyの選択指針

アーキテクトとしてシステムを設計する際、どのポリシーを選ぶかはアプリケーションの特性に完全に依存する。

| ポリシー | 特徴と適性 |
| :— | :— |
| allkeys-lru | キャッシュ用途の最適解。ホットなデータは残り、冷たいデータは消える。 |
| volatile-lru | TTL設定が混在する場合に有用。生存期限付きのデータのみを対象とする。 |
| allkeys-lfu | アクセスパターンが偏っており、長期的な「利用頻度」を重視する場合に有効。 |
| noeviction | 「メモリ不足でエラーを返す」という選択。データの完全性が最優先される場合にのみ使う。 |

警告:物理メモリとRedisメモリの乖離

`maxmemory`を設定しても、OSのオーバーヘッドやフラグメンテーション(断片化)が無視できない壁として立ちふさがる。特に、Redisは`jemalloc`を用いてメモリを管理しているが、長時間稼働させるとアロケータの断片化により、実際の使用量以上に物理メモリを消費する。

redis-cliからフラグメンテーションの状況を監視する
used_memory_rss / used_memory が 1.5倍を超えていたら要注意
redis-cli info memory | grep mem_fragmentation_ratio

—

4. 伝説的アーキテクトからの提言

多くのエンジニアが「削除ポリシーをチューニングすれば解決する」と勘違いしている。しかし、真のアーキテクトはメモリを使い切る前に、データのライフサイクルを設計する。

1. TTLの徹底: 明示的な削除ほど効率的なメモリ管理はない。`EXPIRE`を恐れるな。
2. キー設計のサイズ最適化: Redisはキーと値のメモリオーバーヘッドが大きい。Hashを使ってキーを束ねることで、メモリ消費量を劇的に削減できる。
3. 論理的な分離: 削除されるべきデータと、永続化されるべきデータを同じインスタンスに混在させない。`maxmemory-policy`はインスタンス単位でしか適用できないからだ。

Redisのメモリ管理は、単なる設定値の調整ではなく、あなたが扱うデータの「時間軸」と「重要度」を定義する行為である。その深淵を理解したとき、あなたのシステムは初めて、真の意味でスケーラブルになる。

次回の更新では、Redisの「メモリ断片化」と、その物理層での闘い方について深掘りしよう。技術に妥協するな。本質を見極めろ。

コメント

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