Redis/Valkeyを、5つの役割で使い分ける — インメモリ活用の設計
キャッシュ、分散ロック、大容量受け渡し、ルーティング解決、在庫台帳。同じ技術を役割ごとに最適化して使う。
WHYひとつの技術を、5つの顔で使う
サーバーレス基盤で数多くの関数が並走すると、「共有できる高速な記憶」の必要性が随所で立ち上がる。参照を速くしたい、同時実行を1つに絞りたい、大きなデータを軽く受け渡したい、その時刻の正しい設定を引きたい、在庫を1件も狂わせず更新したい。これらは一見バラバラの要件だが、いずれもインメモリKVS(Redis/Valkey)の得意分野に落ちる。私たちは同じ技術を、役割ごとに設計を最適化して使い分けている。
鍵になるのは、Valkeyが「単なるキャッシュ」ではなく、Luaによる原子的操作とSorted Set等のリッチなデータ構造を備えた小さな計算基盤である点だ。用途を絞れば、シンプルな構造で強力な保証が得られる。
ROLES5つの役割と、それぞれの型
- (1) キャッシュ:高頻度の参照結果をTTL付きで置き、DB往復を減らす。整合はTTLと明示的無効化で担保。
- (2) 分散ロック:
SET key val NX PXでアトミックに取得し、解放はLuaで「自分のトークンだった場合のみDEL」する。多重実行を1本に絞る。 - (3) 大容量ペイロードの受け渡し:関数間・キュー経由で大きなデータを直接運ばず、TTL付きでValkeyに退避し「キーだけ」を渡す。メッセージは軽く保つ。
- (4) ルーティング解決:Sorted Setにバージョンを時刻スコアで積み、「その時刻以前の最新版」を
ZREVRANGEBYSCOREで1件引く。時刻バージョニングの解決に使う。 - (5) リアルタイム在庫台帳:引当・確定・解放をLuaスクリプトで原子的に数量更新。負数化や二重引当を構造的に防ぐ。
ISOLATE役割ごとにクラスタを分離する
用途を1つの物理クラスタに同居させると、片方の負荷やメモリ逼迫、障害が他方へ波及する。基幹用・在庫用・販促用・ルーティング用など、特性の異なるワークロードは接続先を分離するのが基本方針だ。在庫台帳のような整合最優先の系と、揮発しても再生成できるキャッシュ系を切り離すことで、影響半径を小さく保てる。
CODEルーティング解決の一例
時刻バージョニングでは、リクエスト時刻に対して「その時点で有効な最新版」を1件だけ引きたい。バージョンIDをスコア(有効開始時刻)付きでSorted Setに積んでおけば、範囲降順の先頭1件で解決できる。
-- KEYS[1]: バージョン集合キー / ARGV[1]: 参照時刻(epoch) -- 参照時刻以前で最新のバージョンIDを1件返す local ver = redis.call( "ZREVRANGEBYSCORE", KEYS[1], ARGV[1], "-inf", "LIMIT", 0, 1 ) if #ver == 0 then return nil end -- 有効版なし return ver[1]
TRADEOFF速さと引き換えに払うもの
インメモリKVSは低レイテンシとLuaによる原子性が魅力だが、代償もある。第一に揮発性——メモリ上の状態は失われうるため、恒久データはRDB等へwrite-behindで書き戻す設計が要る。第二にメモリ容量とキー設計——載る量には限界があり、TTLとキー命名の規律を欠くと肥大化する。第三にクラスタ分離の運用コスト——影響半径を絞る代わりに、監視・接続管理・容量計画が用途分だけ増える。第四にキャッシュ整合——更新時の無効化を怠ると古い値を返す。速さのために正しさの一部を自分で担保する、という交換であることを忘れてはいけない。
Valkeyは「速いキャッシュ」ではなく、Luaとリッチな構造を備えた小さな共有計算基盤だ。キャッシュ・ロック・受け渡し・ルーティング・在庫台帳という役割ごとに設計を最適化し、用途別にクラスタを分離する。得るのは低レイテンシと原子性、払うのは揮発性・容量・運用・整合の管理コスト。この交換を意識して初めて、同じ技術を5つの顔で安全に使える。