あいつの日誌β

働きながら旅しています。

redis で Lua を使ってみたので備忘録

EVAL と EVALSHA の違い

一連の処理を記述した lua スクリプトを redis-server に渡す事ができる。

>>> import redis
>>> r = redis.StrictRedis(host='localhost', port=6379, db=0)
>>> increment = 'return redis.call("INCR", KEYS[1])'
>>> ret = r.execute_command('EVAL', increment, 1, ['foo'])
>>> print ret
1

MySQL のストアドプロシージャのように一連の処理を redis-server に予め登録する事もできる。 LOAD した際に sha1 が返されるのでそれを使えばいい

>>> sha1 = r.execute_command('SCRIPT', 'LOAD', increment, parse="LOAD")
>>> print sha1
f793247de6e1e3c553cd42d39c812df499e679e4
>>> ret = r.execute_command('EVALSHA', sha1, 1, ['foo'])
>>> print ret
2

lua を使ってトランザクションできるか調べる

userA が持っているお金を userB に渡す処理を考える

  • user:A:money を減らす
  • user:B:money を増やす

話を簡単にするために所持金が不足しているかどうかなどの問題は考えない事にします。

>>> import redis
>>> r = redis.StrictRedis(host='localhost', port=6379, db=0)
>>> r.set('user:A:money', 1000)
>>> r.set('user:b:money', 1000)

以下の2つのコマンドを lua で一連の処理単位(トランザクション)にしたい

>>> r.decr('user:A:money', 100)
900
>>> r.incr('user:B:money', 100)
1100

PythonLua で書くとこうなる

>>> give_money = '''
local x = redis.call('DECRBY', KEYS[1], ARGV[1])
local y = redis.call('INCRBY', KEYS[2], ARGV[1])
return {KEYS[1], x, KEYS[2], y}
'''
>>> print r.execute_command('EVAL', give_money, 2, 'user:A:money', 'user:B:money', 100)
['user:A:money', 900L, 'user:B:money', 1100L]
>>> print r.execute_command('EVAL', give_money, 2, 'user:A:money', 'user:B:money', 100)
['user:A:money', 800L, 'user:B:money', 1200L]

redis-cluster では?

直接 redis-cli でやってみる。個別に処理する事は可能だった。

% redis-cli -c -p 7000
127.0.0.1:7000> set 'user:A:money' 1000
-> Redirected to slot [6288] located at 127.0.0.1:7001
OK
127.0.0.1:7001> set 'user:B:money' 1000
-> Redirected to slot [18] located at 127.0.0.1:7000
OK

もしかしてとか思ったけどやっぱりこうなった

127.0.0.1:7000> EVAL "redis.call('DECRBY', KEYS[1], ARGV[1]);redis.call('INCRBY', KEYS[1], ARGV[1]);" 2 'user:A:money' 'user:B:money' 100
(error) CROSSSLOT Keys in request don't hash to the same slot