【実務・中級編】 Luaスクリプト実行 – Redis

Redis Luaスクリプト:高負荷システムを「一撃」で制するアトミック・アーキテクチャ

Redisをただの「速いKVS」として使っているなら、それは宝の持ち腐れだ。

我々のようなエンジニアが直面する最も厄介な問題は、「複数のコマンドをまたぐ操作の整合性(アトミシティ)」だ。トランザクション(MULTI/EXEC)を使えばいいと考えるかもしれないが、あれは「コマンドのキューイング」に過ぎない。実行中に条件分岐を挟むことはできず、クライアントとの往復回数(RTT)も増える。

そこで登場するのが、Redisサーバーサイドで完結するLuaスクリプトだ。これこそが、Redisを「ただのキャッシュ」から「超高速なドメインロジックエンジン」へと昇華させるための鍵である。

—

1. なぜ「Lua」なのか?:RTTを殺し、アトミシティを統べる

RedisのLuaスクリプトは、サーバーサイドで実行される単一のコマンドとして扱われる。スクリプトが実行されている間、Redisは他のコマンドを一切受け付けない(シングルスレッドモデルの強みを逆手に取った完全な排他制御)。

  • ネットワークオーバーヘッドの排除: 10コマンド必要な処理を、1回のEVALで完結させる。
  • 最強の排他制御: ロック(Redlock等)を実装するまでもないような微細なアトミック操作において、競合を物理的に封じ込める。

2. 実践的な設計パターン:EVALSHAを愛せ

初心者は`EVAL`を連発するが、実務では`EVALSHA`が標準だ。スクリプトを毎回文字列で送るのではなく、`SCRIPT LOAD`でサーバーにキャッシュし、SHA1ハッシュで呼び出す。

— 典型的な「チェックして更新」パターン
— KEYS[1]: ユーザーの残高キー, ARGV[1]: 減算したい金額
local balance = tonumber(redis.call(‘get’, KEYS[1]) or 0)
if balance >= tonumber(ARGV[1]) then
return redis.call(‘decrby’, KEYS[1], ARGV[1])
else
return -1 — 残高不足を通知
end

エンジニアとしての極意:
アプリケーションの起動時に`SCRIPT LOAD`を行い、そのSHA1を定数として管理せよ。万が一スクリプトがサーバー上に存在しない(再起動などで消えた)場合のみ、`NOSCRIPT`エラーを検知して`EVAL`で再送する「セーフティ・ガード」を実装するのが、プロの流儀だ。

3. パフォーマンスの境界線:そのスクリプトは「重すぎないか?」

ここからは警告だ。RedisのLuaスクリプトはブロッキング処理である。

  • 計算量に注意せよ: 実行時間が長いスクリプトは、その間Redisを完全に停止させる。`O(N)`のループを回すような処理は、たとえアトミックであっても禁忌だ。
  • タイムアウトを想定せよ: `lua-time-limit`(デフォルト5秒)を超えると、Redisはスクリプトを強制停止できなくなる。その場合、`SCRIPT KILL`を打つ必要があるが、書き込みが発生した後のスクリプトは`SHUTDOWN NOSAVE`しか選択肢がなくなる。これは本番環境では悪夢だ。

結論: Luaで行うのは「ロジックの実行」であって、「重いデータ処理」ではない。

4. 堅牢な運用を支えるコードレビューの視点

私が設計レビューで必ず確認するポイントは以下の3点だ。

1. 決定論的(Deterministic)か:
Luaスクリプト内では、時刻取得(`TIME`)やランダムな動作をしてはならない。Redisのレプリケーションは「スクリプトの再実行」で行われるため、同じ入力には常に同じ結果が返る必要がある。
2. キーの明示:
Luaスクリプト内で触れるキーは必ず`KEYS[]`引数として渡せ。これはRedis Cluster環境下で必須の作法だ。これを行わないと、キーの分散先が特定できず、クラスター内でエラーになる。
3. 引数のバリデーション:
Lua内で受け取る`ARGV`の型チェックを怠るな。特に`tonumber()`の結果が`nil`になるケースを考慮しないコードは、本番での障害の温床となる。

5. まとめ:Redisを「賢く」使え

Luaスクリプトを使いこなすことは、Redisの限界を拡張することと同義だ。しかし、それは「何でもできるから何でもやらせる」という誘惑との戦いでもある。

  • シンプルな読み取り・書き込みは標準コマンドで。
  • 複雑な条件分岐や、複数のキーをまたぐ整合性が必要な処理はLuaで。

この境界線を見極め、計算量を意識した設計ができれば、あなたのシステムは数万リクエスト/秒の負荷の中でも、驚くほど安定した応答速度を維持するはずだ。

コードは美しく、動作は堅牢に。それが、Redisアーキテクトの矜持である。

コメント

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