【入門編】 redis.call関数 – Redis

こんにちは!Redisの世界へようこそ。
今日は、Redisの奥深い機能の中でも、知っていると「おっ、こいつデキるな!」と周囲を一目置かせること間違いなしの「Luaスクリプト」、そしてその中で主役として動く`redis.call`関数についてお話ししますね。

「スクリプト」とか「関数」とか聞くと、少し難しそうに聞こえるかもしれません。でも大丈夫。ここをクリアすれば、あなたのRedisの操縦スキルは一気にプロの領域へジャンプアップしますよ。一緒に楽しく紐解いていきましょう!

—

1. なぜ `redis.call` が必要なの?(日常の例え話)

まずは、イメージしやすいように日常のたとえから始めましょう。

想像してください。あなたは超人気カフェのワンオペ店員です。
お客さんから「カフェラテ1つと、クッキーを1枚ください。あ、もしカフェラテが売り切れてたら、クッキーも注文しないでキャンセルしてね」と言われました。

もし、これを1つずつレジに打ち込むとしたらどうでしょう?
1. カフェラテの在庫を確認して、あればキープする。
2. (ここで万が一、別の店員が横から割り込んできたら……?)
3. クッキーをレジに通す。

これだと、間に別の処理が入り込んで「あれ?在庫がズレた!」なんて事故が起きる可能性がありますよね。

データベースの世界でもこれと同じことが起きます。「Aのデータを取って、それを加工して、Bのデータを更新する」という一連の流れ(トランザクション)を、途中で邪魔されずに一気にやりたいのです。

そこで登場するのが、Redisの中で小さなプログラム(Luaスクリプト)を動かす仕組み。そして、そのプログラムの中から「Redisに直接命令を投げるための魔法の受話器」こそが、今回テーマにする `redis.call` なんです。

—

2. `redis.call` の正体と本質

`redis.call` は、Redisに組み込まれたプログラミング言語(Lua)の中で使います。

一言で言うと、役割はこれだけ。
> 「Luaスクリプトの中から、普段私たちが使っているRedisのコマンド(GETやSETなど)を、そのまま同期的に(終わるのを待ってから次に進む形で)呼び出すこと」

最大の特徴は、「原子性(アトミシティ)」が保証されること。
つまり、`redis.call` を含んだLuaスクリプトが実行されている間は、Redisは他の誰からのリクエストも一切受け付けません。あなた専用の貸切状態になり、途中でデータが書き換わる心配がゼロになるのです。

—

3. 書いて動かしてみよう!実践コード

百聞は一見に如かず。実際にコードを見てみましょう。
ここでは、「あるキーの値を読み込んで、もし条件に合えば別のキーに書き込む」という一連の処理を `redis.call` で書いてみます。

Redisのクライアント(`redis-cli`など)から、`EVAL` というコマンドを使ってLuaスクリプトを実行します。

— 【Luaスクリプトのイメージ】
— 1. “counter” というキーの値をみる
local current = redis.call(‘GET’, ‘counter’)

— 2. もし値がまだなかったら(nilなら)、初期値として「10」を設定する
if not current then
redis.call(‘SET’, ‘counter’, ’10’)
return “Initialized to 10”
else
— 3. すでにあったら、現在の値に 5 を足して更新する
local new_value = tonumber(current) + 5
redis.call(‘SET’, ‘counter’, new_value)
return “Updated to ” .. new_value
end

これをRedis上で実行すると、次のような挙動になります。

1回目:まだ “counter” は存在しないので、初期化される
127.0.0.1:6379> EVAL “local current = redis.call(‘GET’, ‘counter’); if not current then redis.call(‘SET’, ‘counter’, ’10’); return ‘Init’; else local n = tonumber(current) + 5; redis.call(‘SET’, ‘counter’, n); return n; end” 0
“Init”

2回目:値が存在するので、5が足される (10 + 5 = 15)
127.0.0.1:6379> EVAL “local current = redis.call(‘GET’, ‘counter’); if not current then redis.call(‘SET’, ‘counter’, ’10’); return ‘Init’; else local n = tonumber(current) + 5; redis.call(‘SET’, ‘counter’, n); return n; end” 0
(integer) 15

どうでしょう? `redis.call` を使うことで、「取得する ➔ 条件を見る ➔ 書き換える」という一連の複雑な判断を、Redisのサーバー側で一瞬のうちに安全に完結させることができました。

—

4. 注意しておきたい「エラー」の挙動

概要にもあった通り、`redis.call` はエラーが発生すると例外をスロー(中断)します。

例えば、文字列が入っている場所に対して、数値を前提としたコマンド(数学的な計算など)を無理やり実行して `redis.call` が失敗した場合、そのスクリプトの実行は即座にストップします。

「途中で失敗したら、そこまでの変更を全部なかったことにしたい(ロールバックしたい)」という時、RedisのLuaスクリプトは非常に強力です。エラーが起きた時点でそれ以降の処理は実行されず、エラーメッセージが返されます。
(※厳密には部分的に実行された分が残る場合もあるので、トランザクション設計は慎重に行うのがシニアの嗜みです!)

—

おわりに

いかがでしたでしょうか?
`redis.call` は、単なるコマンド実行の道具ではありません。「複数のRedisコマンドを束ねて、安全かつ高速にサーバー内で完結させるための最強のカード」です。

ここをクリアできれば、あなたはもうRedisの初学者を卒業し、より高度なアプリケーション設計に挑戦する準備ができました。

実務で複雑なデータ操作に直面したとき、「あ、ここはLuaと `redis.call` の出番だな」と引き出しからサッと取り出せたらカッコいいですね。応援しています!

コメント

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