【入門編】 有効期限の内部メカニズム – Redis

こんにちは!Redisの奥深い世界へようこそ。
世界中のシステムを裏で支えるRedisですが、実は「データの片付け方」において、ものすごく巧妙で頭の良い仕組みを持っているんです。

「データを保存したら、自動で消えてほしい」
そんな願いを叶えてくれるのが有効期限(TTL: Time To Live)の機能ですよね。でも、Redisがどうやって膨大なデータの中から期限切れのキーを見つけ出し、綺麗に掃除しているか、気になったことはありませんか?

今回は、この「期限切れの裏側にあるアルゴリズム」を、日常のたとえ話を交えながら、一緒に紐解いていきましょう。
ここをクリアすれば、Redisの動きが手に取るようにわかるようになりますよ。ぜひ最後までついてきてくださいね!

—

1. 冷蔵庫の賞味期限に例えてみよう

突然ですが、みなさんの家の冷蔵庫を想像してみてください。
中にはたくさんの食材が入っていますよね。中には「牛乳:3日後まで」「お肉:明日まで」といったように、それぞれ賞味期限がついています。

もし、あなたが「賞味期限が切れた瞬間に、秒単位で完璧にすべての食材をゴミ箱に捨てる係」だとしたらどうでしょう?
冷蔵庫の中身をずっと監視していなければならず、食材が増えれば増えるほど、あなたの労力は爆発的に跳ね上がり、やがて力尽きてしまいますよね。

実は、Redisも同じ悩みを抱えています。
メモリ(RAM)という限られたスペースの中に、何百万ものデータ(キー)が保存されており、その多くに有効期限が設定されています。もしCPU(Redisの脳みそ)が「すべてをリアルタイムで監視して消去する」ことに全力を注いだら、肝心のアプリケーションからのデータ読み書きが遅くなってしまいます。

そこでRedisは、「サボっているわけではないけれど、効率よく、絶対にゴミを残さない」という、天才的な2つの掃除アプローチを編み出しました。それが「パッシブ削除」と「アクティブ削除」です。

—

2. 掃除の仕組みその1:パッシブ削除(受動的なお掃除)

1つ目は、パッシブ削除(Passive Expiration)です。日本語にすると「受け身の削除」ですね。

これは先ほどの冷蔵庫のたとえで言うなら、「自分が何かを取り出そうと冷蔵庫を開けたとき、『あ、これ賞味期限切れてるじゃん!』とその場でゴミ箱に捨てる」という行動です。

Redisの動きはこうです:
1. アプリケーションが「このキーのデータをちょうだい!」とRedisにお願いする。
2. Redisがそのキーを探しに行く。
3. その際、そのキーに有効期限が設定されており、かつ時間が過ぎていることに気づく。
4. 「おっと、もう期限切れだね」とその場でデータを消去し、アプリケーションには「データはないよ(nil)」と返す。

メリットと限界

非常にシンプルで、CPUが無駄な労力を使わない素晴らしい方法です。
しかし、弱点もあります。もし「一度もアクセスされない期限切れのデータ」があった場合、それは永遠に冷蔵庫の奥底に眠り続け、メモリを圧迫し続けることになります。

これでは困りますよね。そこで登場するのが2つ目の仕組みです。

—

3. 掃除の仕組みその2:アクティブ削除(能動的なお掃除)

2つ目は、アクティブ削除(Active Expiration)です。こちらはRedisが自発的に行う「巡回清掃」です。

冷蔵庫のたとえなら、「定期的に(例えば1分に1回)、冷蔵庫の中からランダムにいくつか食材を取り出して、賞味期限を切れていないかチェックし、切れていたら捨てる」という作業を裏でこっそり行うイメージです。

Redisの内部では、これを次のようなアルゴリズムで高頻度(デフォルトでは1秒間に10回!)で実行しています。

1. 有効期限が設定されているキーの中から、ランダムに20個のキーを摘み出します。
2. その20個の中に、期限切れのものが含まれていればすべて削除します。
3. もし、その20個のうち25%以上(つまり5個以上)が期限切れだった場合、「おや、この冷蔵庫は全体的に結構な数の期限切れがありそうだな」と判断し、すぐにステップ1に戻って再び別の20個をチェックします。

なぜ「ランダム」なのか?

ここで「なぜ全部綺麗に見渡さないの?全部チェックしたほうが確実でしょ?」と思ったあなたは鋭いですね!

もしRedisが、メモリ上にある数百万〜数千万のキーを毎回ぜんぶチェックしようとすると、CPUがその計算だけで手一杯になってしまい、アプリからのリクエストを処理できなくなってしまいます(これを「CPUの独占」と呼びます)。

Redisは、「完璧な美しさ」よりも「システムの軽快さ(低レイテンシ)」を選びました。ランダムにサンプリングすることで、CPUに負担をかけずに、裏で綺麗に保つという絶妙なバランスを取っているのです。

—

4. 実際にコードと動きを見てみよう

百聞は一見にしかず。実際にRedisを操作して、この挙動を体感してみましょう!
(ターミナルなどで `redis-cli` を開いていると仮定して進めますね。)

1. キーに有効期限(TTL)をセットする(例:5秒間だけ生きるデータ)
127.0.0.1:6379> SETex session:user_99 5 “active_token_xyz”
OK

2. すぐに中身を確認してみる(まだ5秒以内なので残っている)
127.0.0.1:6379> GET session:user_99
“active_token_xyz”

3. 残り時間を秒単位で確認してみる
127.0.0.1:6379> TTL session:user_99
(integer) 3 # あと3秒で消えますよ、という意味

— (ここで5秒以上待つ) —

4. 5秒経ったあとにアクセスしてみる(パッシブ削除の発動!)
127.0.0.1:6379> GET session:user_99
(nil) # もう消えているので、データはありません

このように、期限が切れた瞬間にデータがスッと消えてくれるのは、裏側でパッシブ削除とアクティブ削除のコンビネーションが絶妙に働いているおかげなんです。

—

5. 先輩エンジニアからの実践アドバイス

最後に、実務でRedisを扱う上で絶対に知っておいてほしい「プロの知見」をひとつ伝授します。

それは、「大量のキーに『同じ秒数』の有効期限を同時に設定してはいけない」ということです。

例えば、深夜0時に一斉にリセットされるような仕組みを作るとき、100万件のデータすべてに「TTL: 86400秒(24時間後)」をピタッと設定してしまうと、24時間後のその瞬間に、100万件のデータが一気に「期限切れ」を迎えます。

そうすると、アクティブ削除のチェックに引っかかる確率が跳ね上がり、Redisがその大量削除の処理で一時的に忙しくなり、システム全体がカクついてしまう現象(有効期限の嵐 / Expiration Storm)が起きてしまうのです。

【対策】
もし一斉に期限が切れてほしいデータであっても、有効期限に少しだけ「ゆらぎ(ランダムな秒数)」を持たせてあげましょう。

例:24時間(86400秒)に設定したい場合
-> 86400秒 + (0〜300秒のランダムなズレ)

たったこれだけの工夫で、削除のタイミングが分散され、Redisは常に安定したハイパフォーマンスを維持し続けます。これが「わかっているエンジニア」の設計です。

—

まとめ

いかがでしたか? Redisの有効期限の裏側には、次のようなシンプルかつ合理的な仕組みがありました。

  • パッシブ削除: 読み込みに来たその瞬間に、自分で気づいて綺麗に消す。
  • アクティブ削除: 1秒に10回、裏でランダムにサンプリングしてコツコツ掃除する。
  • 完璧な全数検査をしない: CPUの軽快さを守るための、Redisならではの美しいトレードオフ。

この仕組みを知っていれば、今後Redisでキャッシュやセッションストアを設計する際にも、自信を持って最適なコードが書けるはずです。

ここをクリアしたあなたなら、もうRedisのデータ構造と管理の基本はバッチリマスターできていますよ!
明日からの開発も、この知見を武器に楽しんでいきましょう!

コメント

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