Redis、Lua Script vs Transaction
Redis の Transaction は、RDBMS脳には難しい
Redis の Transaction で行われている処理は、キューイング後の Atmic な一括処理だけです。
コマンド途中にエラーが発生してもロールバックは行われません。
Redis Transaction の例
よくある利用方法としては、 INCR
+ EXPIRE
を Atomic に実行したいときでしょうか。
例えば、下記のようなコマンドになります。
redis> MULTI # ここで Transaction 開始
OK
> INCR hoge
QUEUED
> EXPIRE hoge 60
QUEUED
> EXEC # ここでキューイングされたコマンドが Atomic に実行
1) (integer) 1
2) OK
INCR
のような value に数値が期待されているコマンドを、 value が文字列の key に対して発行すると当然エラーが発生します。
redis> SET hoge fuga
OK
redis> GET hoge
"fuga"
redis> INCR hoge
ERR ERR value is not an integer or out of range
上記を Transaction で実行すると、ロールバックせずに SET hoge fuga
はそのまま残る。
redis> MULTI # ここで Transaction 開始
OK
redis> SET hoge fuga
QUEUED
redis> GET hoge
QUEUED
redis> INCR hoge
QUEUED
> EXEC # ここでキューイングされたコマンドが Atomic に実行
1) OK
2) "fuga"
3) (error) ERR value is not an integer or out of range
なぜロールバックされないのか?
Transactions に記載されている思想設計が理由です。
- エラーの原因はプログラミング中にあり、これは本番環境に入るまでの開発途中で気づくことが出来るため
- Syntax Error や Wrong Data Type に起因するため
- Redis は内部的にロールバックを実装していないから、シンプルに高速になっているため
つまり Redis の Transaction とはなにか?
複数のコマンド実行時に、他のクライアントに割り込まれないように Atomic に実行させる機構です。
Redis のもうひとつの Atomic な実行方法
Redis には Lua Scripting という、もうひとつの Atomic な複数コマンド実行方法があります。
例えば一番最初の INCR
+ EXPIRE
を Lua Script で実装すると下記のようになります。
redis> EVAL "local r = redis.call('INCR', KEYS[1]) redis.call('EXPIRE', KEYS[1], ARGV[1]) return r" 1 hoge 60
(integer) 1
Lua Script vs Transaction
Redis scripting and transactions に記載の通り、 Lua Script が v2.6 で実装されたのに対し、 Transaction は v1.2 で実装されました。
そして、通常 Lua Script の方が単純で高速な上、中間値も扱えるなど出来ることも多いです。
そのため Redis 公式には、 Lua Script が広く使われるようになれば Transaction 機能を廃止する可能性もあると記載されています。
一方で、 Lua Script は逆になんでも出来てしまう点で非常に注意が必要です。
デバッガとして Redis Lua scripts debugger も存在はしていますが、それでもロジックがアプリケーション外に散るデメリットもあります。
そのため、よく使うコマンドの組み合わせ (INCR
+EXPIRE
など ) のみを予め実装した Helper や Utility クラスを用意しておくのもひとつの手です。
一方で Transaction は工夫しなくても、コードが意味的に読みやすいため、実装ミスによる Redis を長時間ブロッキングしてしまうリスクも小さいです。
Lua Scripting の注意点を共有しながら最大限効果を享受するか、わかりやすさと機能制限を重視して Transaction を利用するか
開発チーム内で十分話し合って、方針を決めると良いでしょう。
Discussion