Redis Lua script debuggerを活用する

Redisでアトミック操作を実現したい時にLua scriptを記述したい時があるが、毎回実際にRedisに投げて確認するのは非常に面倒。動作中の問題や、redis.callの返り値の確認などを行うのも非常に面倒。

公式ドキュメントにLua script debuggerが用意されているという記述を発見したので使う。 Debugging Lua scripts in Redis | Redis

今回は、ハッシュ型の値に対する簡単な比較を行うCompare and Set操作を実装する。

先に実装を出すが、あるハッシュ型のキーと、ハッシュを引数で指定し、数値の値を格納する。 数値を格納する前に、指定したキーとハッシュを用いて、既に値が存在している場合にはその値と今回指定した値を比較し、大きい場合に限り値を更新するものである。

local value = tonumber(ARGV[2]);
local last_value = tonumber(redis.call('hget', KEYS[1], ARGV[1]))

if last_value == nil or value > last_value then
    redis.call('hset', KEYS[1], ARGV[1], value)
    return true
end

return false

成功した場合にはtrueを返し、失敗した場合にはfalseを返すようにする。

先に下準備として、redis-cli経由からデータを格納しておく。

$ redis-cli
127.0.0.1:6379> hset foo bar 10000
(integer) 1
127.0.0.1:6379> hset foo baz 20000
(integer) 1
127.0.0.1:6379> hgetall foo
1) "bar"
2) "10000"
3) "baz"
4) "20000"

下準備が整ったので、デバッガを起動する。デバッガの起動には --ldb オプションを指定する。 事前にテストしたいスクリプトを用意しておき、--evalのオプションとして指定する。 ちなみに、local hoge = 0 ぐらいを記述したもので指定しても動くので最初は適当な文を一つ書いてインタラクティブに確認する、みたいな方式でもいいかもしれない。

まず、数値として10000を指定して起動する。(これは、以前と同じ大きさの値になるので更新はスキップされる) 実行のステップごとに停止し、ユーザーの入力を受け付けて次のステップに進むか、特定の変数の現在の値を確認したり、適当な文を評価したりしたりできる。 n(next)を入力すると次のステップ。p(print)を入力すると特定の変数の値を出力。e(eval)を入力すると、現在の呼び出しフレーム外でのコンテキストで文を評価。

$ redis-cli --ldb --eval test.lua foo , bar 10000  
Lua debugging session started, please use:
quit    -- End the session.
restart -- Restart the script in debug mode again.
help    -- Show Lua script debugging commands.

* Stopped at 1, stop reason = step over
-> 1   local value = tonumber(ARGV[2]);
lua debugger> n
* Stopped at 2, stop reason = step over
-> 2   local last_value = tonumber(redis.call('hget', KEYS[1], ARGV[1]))
lua debugger> print value
<value> 10000
lua debugger> n
* Stopped at 4, stop reason = step over
-> 4   if last_value == nil or value > last_value then
lua debugger> eval redis.call('hget', KEYS[1], ARGV[1])
<retval> "10000"
lua debugger> eval redis.call('hget', KEYS[1], 'baz')
<retval> "20000"
lua debugger> n
* Stopped at 9, stop reason = step over
-> 9   return false
lua debugger> n

(nil)

(Lua debugging session ended -- dataset changes rolled back)

127.0.0.1:6379> 

if last_value == nil or value > last_value then の条件式にマッチせずに、return falseが実行されているのがわかります。redisからのreplyとしてはfalseに対応するnilが帰る。

次に数値として10001を指定して起動します。これは既存の10000よりも大きい値なので、更新されることを期待する。

$ redis-cli --ldb --eval test.lua foo , bar 10001
Lua debugging session started, please use:
quit    -- End the session.
restart -- Restart the script in debug mode again.
help    -- Show Lua script debugging commands.

* Stopped at 1, stop reason = step over
-> 1   local value = tonumber(ARGV[2]);
lua debugger> n
* Stopped at 2, stop reason = step over
-> 2   local last_value = tonumber(redis.call('hget', KEYS[1], ARGV[1]))
lua debugger> n
<redis> hget foo bar
<reply> "10000"
* Stopped at 4, stop reason = step over
-> 4   if last_value == nil or value > last_value then
lua debugger> n
* Stopped at 5, stop reason = step over
-> 5       redis.call('hset', KEYS[1], ARGV[1], value)
lua debugger> n
<redis> hset foo bar 10001
<reply> 0
* Stopped at 6, stop reason = step over
-> 6       return true
lua debugger> eval redis.call('hget', KEYS[1], ARGV[1])
<retval> "10001"
lua debugger> n

(integer) 1

(Lua debugging session ended -- dataset changes rolled back)

条件式がtrueと評価され、redis.call('hset', KEYS[1], ARGV[1], value) の実行が行われていることが分かる。 redisからのreplyとしては、trueに対応する1が返る。

デバッグが完了した時点で、実際にデバッガなしでlua scriptを実行する。

redis-cli --eval test.lua foo , bar 10000
(nil)

$ redis-cli
127.0.0.1:6379> hget foo bar
"10000"
$ redis-cli --eval test.lua foo , bar 10001
(integer) 1

$ redis-cli
127.0.0.1:6379> hget foo bar
"10001"
$ redis-cli --eval test.lua foo , bar 9999
(integer) 1

$ redis-cli
127.0.0.1:6379> hget foo bar
"10001"

問題なく動作していることが確認できる。