【テクニカル・上級編】 TTLと有効期限 – Redis

RedisのTTL: 追放のアルゴリズムとメモリの深淵

多くのエンジニアが「キーに有効期限を設定する」という行為を、単なるキャッシュの自動掃除として片付けている。だが、数億キーを抱えるクラスタの設計において、その認識は命取りになる。RedisにおけるTTL(Time To Live)の実装は、単なるタイマー管理ではない。それは、CPUサイクルとメモリ空間の高度な均衡を保つための、極めて洗練された「遅延削除(Lazy Deletion)」と「アクティブ削除(Active Expiration)」のダンスである。

今日は、Redis内部で何が起きているのか、その深層を解き明かそう。

—

1. 期限付きキーの管理構造:`expires`ディクショナリ

Redisの各データベースには、メインのデータ格納用ディクショナリとは別に、`expires`という名の独立したディクショナリが存在する。これが重要だ。

  • メインのディクショナリ: キーから値へのポインタを保持。
  • `expires`ディクショナリ: キーから「有効期限(UNIXタイムスタンプ)」へのポインタを保持。

この分離により、既存のキーに対して後から有効期限を付与したり、逆に削除したりする操作が、メインのデータ構造に影響を与えることなく、O(1)の計算量で完結する。メモリ使用量については、`expires`内の各エントリは`long long`型(8バイト)のタイムスタンプを保持するだけだ。数千万キー規模であっても、この管理コストは致命的ではない。

2. 二段構えの「追放」メカニズム

なぜRedisは、期限が切れた瞬間に即座にメモリを解放しないのか? それは、すべての期限切れキーを正確なタイミングで監視しようとすれば、CPUがその管理だけで飽和してしまうからだ。

A. 遅延削除 (Lazy Expiration)

これは最も効率的な生存戦略だ。Redisは、誰かがそのキーにアクセスしようとした瞬間に期限チェックを行い、期限が過ぎていれば削除する。

  • メリット: CPU負荷が低い。
  • デメリット: 二度とアクセスされない「ゴミ」がメモリ上に残り続ける。

B. アクティブ削除 (Active Expiration)

遅延削除だけではメモリが溢れる。そこでRedisは、`serverCron`というバックグラウンドタスクで、定期的に以下のアルゴリズムを走らせている。

1. `expires`ディクショナリからランダムに20個のキーを抽出する。
2. その20個の中で期限切れのものを削除する。
3. 期限切れキーの割合が25%を超えていた場合、ステップ1に戻る。

この「確率論的なアプローチ」こそがRedisの天才的な点だ。全キーを走査するのではなく、サンプリングによってメモリ空間全体を「ある程度の清潔さ」に保つ。これにより、システム全体のスループットを維持しながら、メモリの枯渇を防ぐことができる。

3. レプリケーションと一貫性の罠

アーキテクトが最も注意すべきは、`Master`と`Replica`の挙動だ。

Redisのレプリケーションにおいて、期限切れの削除は「受動的」に行われる。つまり、Replicaは自分で勝手にキーを消してはいけない。Masterが「このキーを削除しろ」という`DEL`コマンドを生成し、それをReplicaに送信することで、初めて同期的な削除が完了する。

もしあなたがTTLを短く設定しすぎ、かつキーの読み取り頻度が極端に低いシステムを設計した場合、何が起きるか。MasterとReplicaの間で、メモリ使用量に大きな乖離(フラグメンテーション)が発生するのだ。

/ 内部的な削除フローの擬似的な概念 /
void expireIfNeeded(redisDb db, robj key) {
if (!keyIsExpired(db, key)) return; // まだ生きているなら即リターン

/ 削除処理 /
server.expired_keys++;
propagateExpire(db, key); // レプリカへ削除コマンドを伝播させる
dbDelete(db, key);
}

4. 限界を突破するためのアーキテクトの知見

大規模運用を行う際、以下の点に留意せよ。

1. メモリ断片化(Fragmentation):
頻繁なTTL設定と削除は、jemallocのメモリ確保アルゴリズムに負荷をかける。特にキーのサイズが不揃いな場合、メモリ断片化率(`mem_fragmentation_ratio`)が急騰する。`activedefrag`を有効化し、適宜`MEMORY PURGE`を検討せよ。

2. イベントループのブロッキング:
`EXPIREAT`をループで大量に発行すると、`serverCron`の処理時間が長引き、メインスレッドが止まる。有効期限の設定は、一括処理ではなく、適切なバッチサイズに分割して行うのが鉄則だ。

3. LuaスクリプトとTTL:
Luaスクリプト内でアクセスされたキーは、スクリプト実行中に期限が切れても削除されないという特異な仕様がある(一貫性を保つため)。この仕様を知らずに「キーが存在するはず」という前提でコードを書くと、論理破綻を招く。

結論

RedisのTTL機能は、単なる「便利な機能」ではない。それは、メモリの効率的な解放と、高性能なレスポンスという二律背反する要求を、確率的なアルゴリズムで解決した「工学の極致」だ。

あなたが大規模アーキテクチャを設計するなら、この「サンプリングによる削除」がシステムのCPU使用率にどう影響するか、そして「いつ削除されるか分からない」という非決定性がアプリケーションのロジックにどう波及するかを深く考察する必要がある。

Redisを使いこなすということは、そのメモリ管理の呼吸を理解することに他ならない。本質を見極めよ。コードの向こう側にある、データ構造の鼓動を。

コメント

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