こんにちは!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のデータ構造と管理の基本はバッチリマスターできていますよ!
明日からの開発も、この知見を武器に楽しんでいきましょう!
コメント