Redis は読み取りを高速化し、有界操作を調整できるインメモリデータ構造サーバーです。 本番設計では、信頼できる情報源、一貫性ウィンドウ、容量ポリシー、障害動作、すべての キーの所有権を定義する必要があります。永続化とレプリケーションは復旧の選択肢を 改善しますが、キャッシュにソースデータベースのトランザクション不変条件を与える ものではありません。
Cache-Aside と一貫性ウィンドウ #
Cache-aside は読み取りと書き込みのポリシーをアプリケーションに残します。
アプリケーションは Redis を読み、ミスをデータベースからロードし、TTL 付きで結果を 保存して返します。ソースへの書き込みでは、影響を受けるキーを無効化または更新する 必要があります。データベースコミットとキャッシュ変更は 1 つのアトミック操作では ないため、読み取り側はその間に古いデータを観測できます。許容するウィンドウを定義し、 キャッシュ無効化をプロセス障害後も維持する必要がある場合は、トランザクショナル アウトボックスまたは変更データストリームを使います。
存在しない識別子への繰り返しリクエストが常にデータベースへ到達すると、キャッシュ ペネトレーションが発生します。信頼できる不在を確認した後、短期間の負のセンチネル を保存しますが、作成が可視になるべき間隔より長く存続させてはなりません。Bloom フィルターは確実に存在しない値を検索前に拒否できますが、偽陽性はバックエンドへ 到達します。
スタンピードと更新 #
人気のキーが期限切れになると、同時ミスによって高価なロードが重複する可能性があります。 緩和策には次があります。
- 関連キーが同時に期限切れにならないよう、TTL にランダムなジッターを加える。
- 他が古いデータを使う間、1 つの有界な更新処理だけがキーを再構築できるようにする。
- 同じキーへのプロセス内リクエストをまとめる。
- 完全な期限切れ前にホットキーを更新する。
更新ロックにはリースと障害経路が必要です。キャッシュミスを無期限待機に変えては ならず、Redis が利用できない可能性があるため、バックエンドサービスにも過負荷制御 が必要です。
期限切れ、削除、メモリプレッシャー #
値と有効期限をアトミックに設定します。
SET session:42 '{"user":42}' EX 1800Redis はアクセス時に期限切れキーを受動的に削除し、TTL のあるキーをサンプリングして 能動的にも削除します。したがって期限切れが定義するのは論理的な可視性であり、メモリ が回収される正確な時刻ではありません。TTL の選択には、新鮮さ、再計算コスト、 オブジェクトサイズ、同期期限切れのリスクを含めます。
maxmemory はデータに利用できるメモリを制限し、maxmemory-policy はプレッシャーへの
応答を選択します。ポリシーには noeviction、全キーまたは揮発性キーの LRU/LFU 近似、
ランダム削除、揮発性 TTL ベースの削除があります。揮発性ポリシーは有効期限のある
キーだけを対象とし、適格なキーがなくなると noeviction のように動作します。設定した
TTL が常駐を保証すると仮定せず、使用メモリ、断片化、削除、期限切れ、拒否された書き
込み、レイテンシー、キャッシュヒット率を監視します。
分散ロックとフェンシング #
一意の所有トークンと有界なリースを使って、単一インスタンス Redis ロックを取得します。
SET lock:invoice:42 4d1c9f... NX PX 300001 つのアトミックスクリプトを使い、トークンがまだ一致する場合だけ解放します。
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
end
return 0リースは放棄状態を制限しますが、排他的アクセスを証明しません。プロセスが TTL を 超えて停止し、ロックを失い、再開して新しい保持者と同時に書き込むことがあります。 下流リソースが破損し得る場合は、単調増加するフェンシングトークンを発行し、その リソースに、すでに観測した最大トークンより古いトークンを拒否させます。
レプリケーションは非同期であり、時計と停止がリースの判断に影響し、ネットワーク パーティションは所有権の変更を隠すことがあります。不変条件をすでに保護している 場合は、データベース制約、条件付き書き込み、コンセンサスコーディネーターを優先 します。Redis ロックは、障害モデルが文書化された有界で再試行可能な作業を保護する ために使います。
Fixed-Window Rate Limits #
A fixed window stores one counter for each subject and time bucket. Increment, set the initial expiry, and decide atomically:
local count = redis.call("INCR", KEYS[1])
if count == 1 then
redis.call("EXPIRE", KEYS[1], tonumber(ARGV[1]))
end
if count > tonumber(ARGV[2]) then
return {0, count}
end
return {1, count}Fixed windows need constant state, but traffic at the end of one bucket and the start of the next can create a burst close to twice the nominal limit. Derive bucket identity consistently; client clocks can disagree, so use server time in the script or route one subject through a consistent clock domain when boundary precision matters.
Sliding Windows and Buckets #
A sorted-set sliding log stores one unique member per request with its timestamp as the score. One script removes entries before the window, counts remaining entries, conditionally adds the request, and sets a TTL. The algorithm enforces a rolling count but consumes memory proportional to admitted events. Members must remain unique even when multiple requests share a timestamp.
A token bucket stores a token balance and last-refill time. Each request refills according to elapsed time up to a capacity, then consumes a token if available. It enforces a long-term rate while permitting a configured burst. A leaky bucket instead drains queued work at a fixed rate, smoothing output but requiring bounded queueing and an explicit rejection policy.
Lua scripts and Redis Functions make each decision atomic on one Redis node. Keep scripts deterministic and bounded; a long script blocks other command execution. Return limit, remaining capacity, and reset or retry timing only when those values accurately reflect the chosen algorithm.
Cluster Placement, Retries, and Observability #
Redis Cluster requires every key touched by one transaction, script, or function
to belong to the same hash slot. Use a hash tag such as
rate:{account-42}:write to colocate related keys without forcing unrelated
accounts into one slot. One hot subject can still overload its slot.
Timeout does not prove that a mutating command failed. Retrying INCR, XADD,
or a rate-limit script blindly can duplicate effects, so attach an operation ID
or make the script detect repeated requests when exact-once accounting matters.
Bound retries and expose Redis errors rather than silently replacing a strict
limit with unlimited access; security-sensitive systems must choose fail-open or
fail-closed deliberately.
Record script latency, errors, rejected and allowed counts, hot keys, slot distribution, replica lag, failovers, memory pressure, and clock skew. These signals distinguish policy behavior from Redis saturation or topology changes.
コメント
GitHub で閲覧・コメントする(投稿にはログインが必要です)