前回まででredisのレプリケーションが同期を取る仕組みについてやったので、
今日はRedisのトランザクションとロックにまつわるパフォーマンスの話をしようと思います。
キーワード: 楽観的ロック 悲観的ロック トランザクション 2フェーズコミット MULTI/EXEC
①従来の良くあるRBDのトランザクション
リレーショナルデータベースのトランザクションについて簡単におさらいしましょう。RDBは強烈なACID特性を持つのですが、それを特徴づける機能にトランザクションっていうのがあります。
トランザクションとは簡単に言うと、複数の処理をまとめて一つとすることです。
RDBでは
BEGIN
COMMIT
ROLLBACK
っていうのがありますが、これをアプリサイドのコードで上手い事使い分けるわけです。疑似コードで言うと
try:
conn.begin()
conn.save('あるSQL文')
conn.delete('あるSQL文')
conn.commit()
catch:
conn.rollback()
finaly:
conn.close()
的な?詳しくはJDBCとかのちゃんとしたコード見た方がいいでしょう。意味合いとしては、例外が発生したら全ての変更を破棄し、そうで無ければbegin -> commitの間の処理を全て正常に処理し、最後にコネクションを解放して終了です。
このbegin -> commit 間が所謂トランザクションです。 変更が all or nothing。
②Redisにおけるトランザクション
RedisではMULTI/EXECでこれを行いますが、このMULTIがBEGIN、EXECがCOMMITにあたると思って結構です。非常に簡単です。
これのどこがパフォーマンスが高いのかというと、MULTI/EXEC間で一発でCRUD操作を飛ばすのでラウンドトリップが少なくなり、パフォーマンスが上がります。
これを俗に「パイプライン処理」と言われています。
つまりは、これらMULTI/EXECはコマンドを非同期に遅延してることになります。(MULTI/EXEC間はコマンドの実行がEXECされるまで先送り)
ではここでQ&Aに行きましょう。
Q. ん?Redisでも完全に整合性が取れるってこと?
A.半分そうですが、半分違います。
どういうことかというと、Redisは単一のパイプラインコマンド間で整合性は担保できますが、「execするまで何もされませんので、その間で他の誰かによる問題が起きたときに対処不能である」
ということです。
途中で失敗するケースを考えてrollbackが張れるRDBでは、そういうときは単にRollbackすればいいですが、RedisではMULTI/EXEC間で何かおかしなことが起きてもそれによってMULTI/EXEC間の次の処理を取りやめる。とか、できません。
Redisのトランザクションはパフォーマンスを向上させるためだけにあるのです。
例) ソーシャルゲームのレイドバトル
ソーシャルゲームでよくある協力ボスの討伐なんかがすごくいい例です。AさんとBさんがコンマ数秒の差で同じボスを倒したとしましょう。ボスを倒す事で報酬を受け取ることができ、さらにこのレイドバトルをクローズする処理が走ります。
この時、ほぼ同時に討伐すると、どっちが倒した事になるんでしょう?Aさんが先の場合、Bさんは既に討伐されているレイドボスを倒そうとしてエラーになりますし、もっと悪い事に、Aさんが報酬を受け取るときにネットワークがたまたま遅れて、Bさんが割り込んだ場合、Aさんが倒したはずなのにBさんが報酬を受け取る
見たいなことになります。これはもうデータがハチャメチャになってしまいます。
NoSQl全般ですが、これ、弱いです。
Q.は?じゃあどうすんの?使えなくね?
別の方法があります。それが2フェーズコミットと言われる方法です。
Redisでこれを実装するときに、対象とするデータを監視します。誰かがそのデータにアクセスしていると気づき次第、アクセスを取りやめます。さらに、対象とするデータが既にない場合、「もうありません」的なメッセージを用意します。これをpythonっぽい疑似コードで書くと、
while true:
try:
pipe.watch('対象のコレクション')
if pipe.sismember('特定のデータ'):
return None
pipe.multi()
pipe.ある処理()
pipe.exec()
exept redis.exceptions.WatchError:
pass
監視対象のキーが変更を受けたら例外を発生させる機構がpyRedisにあります。その例外を捕まえたら無限にやり直し、既にデータが無いならもうやめる。
これでデータの致命的な破損は回避できます。これを楽観的なロックといいます。ロックしていないが、事実上ロックされてる気になれます。
楽観ロック : ロック対象のキーを監視し、変更があれば通知を受け取ってやり直す。
Q. いや、黙って一般的なロックがあればよかったんじゃね?
A.パフォーマンスに焦点を当てているのがRedisです。
一般的なロックだと、(悲観的ロック)ロックを保持しているクライアントの処理が遅い場合、他のクライアントが長い時間待たされる事になります。これはパフォーマンスの観点からはよくないですね?
次回はRedisで手でRockを実装する方法とそれによるパフォーマンスの向上についてやります。