redis入門+トランザクション周りの覚書
サーバサイドエンジニア3年目にして先日初めてredisとやりとりするような処理を書きました。
せっかくなので調べたことを残しておこうと思います。
基本的な操作(cli)
接続
$ redis-cli 127.0.0.1:6379>
-h {HOST}
、-n {db}
など
redisのdbについて
- 選択可能な名前空間の形式。0~15などのidを指定する形で使う
- 未指定の場合は
0
になる - redis clusterでは、0のみがサポートされる
127.0.0.1:6379> SELECT 1 OK 127.0.0.1:6379[1]> 127.0.0.1:6379[1]> SELECT 15 OK 127.0.0.1:6379[15]> SELECT 16 (error) ERR DB index is out of range 127.0.0.1:6379[1]> SELECT 0 OK 127.0.0.1:6379>
- database_idの数は
databases
で確認でき、この設定を変更すれば増減できる
127.0.0.1:6379[15]> CONFIG GET databases 1) "databases" 2) "16"
set/get
127.0.0.1:6379> SET count 1 OK 127.0.0.1:6379> GET count "1"
key一覧を見る
- KEYS {pattern} でpatternに一致する全てのキーを返す。
127.0.0.1:6379> KEYS * 1) "key1"
大規模なデータベースで実行するとパフォーマンスに影響が出る可能性があるため注意。
キーに格納されている値を増やす
127.0.0.1:6379> INCR count (integer) 2 127.0.0.1:6379> GET count "2"
キーに有効期限を設ける
- EXPIRE {key} {秒}
- 有効期限を設定
- TTL {key}
- 残り時間を確認
# keyを設定 127.0.0.1:6379> SET count 3 OK # timeoutを20sに設定 127.0.0.1:6379> EXPIRE count 20 (integer) 1 # 参照できる 127.0.0.1:6379> GET count "3" # 残り時間を確認 127.0.0.1:6379> TTL count (integer) 13 127.0.0.1:6379> TTL count (integer) 12 ... 127.0.0.1:6379> TTL count (integer) 1 127.0.0.1:6379> TTL count (integer) 0 127.0.0.1:6379> TTL count (integer) -2 # keyが存在しなくなっている 127.0.0.1:6379> GET count (nil)
TTLの返り値に関して、-1
はタイムアウトが設定されていない値、 -2
はkeyが存在しないもの。
トランザクションを貼る
トランザクション成功例
127.0.0.1:6379> MULTI OK 127.0.0.1:6379(TX)> 127.0.0.1:6379(TX)> SET count 1 QUEUED 127.0.0.1:6379(TX)> INCR count QUEUED 127.0.0.1:6379(TX)> EXEC 1) OK 2) (integer) 2 127.0.0.1:6379> GET count "2"
トランザクション失敗例
127.0.0.1:6379> SET count 1 OK 127.0.0.1:6379> WATCH count OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379(TX)> INCR COUNT QUEUED # このタイミングで別ターミナルで INCR countを実行し、count keyに変更を加える 127.0.0.1:6379(TX)> INCR COUNT QUEUED 127.0.0.1:6379(TX)> INCR COUNT QUEUED 127.0.0.1:6379(TX)> EXEC (nil) 127.0.0.1:6379> GET count "2" # TX内のincr結果が反映されていない
基本的な操作(redis-py)
import redis def main(): client = redis.Redis( host="localhost", port=6379, db=1, ) client.set('count', 1) client.incr('count', 3) print(client.get('count')) # b'4' if __name__ == "__main__": main()
pipelineメソッド
単一のリクエストでサーバーに複数のコマンドを実行させる事ができる。
import redis def main(): client = redis.Redis( host="localhost", port=6379, db=1, ) pipe = client.pipeline() pipe.set('count', 1) pipe.incr('count', 3) pipe.get('count') print(pipe.execute()) # [True, 4, b'4'] if __name__ == "__main__": main()
pipeline()はtransactionオプションがあり、デフォルトでonになっているが明示的にoffにして使うこともできる。
transactionメソッド
pipelineとは別に、transactionメソッドというものもあり、それを使うこともできる。 transactionメソッドを使うと、watchや監視対象keyに変更が加えられたときのWatchErrorハンドリングを書かなくても済むが、少しわかりにくかった。
import redis def main(): redis_client = redis.Redis( host="localhost", port=6379, db=1, ) def incr_req_cnt(p): p.multi() p.incr(key, amount=2) p.incr(key, amount=2) key = "count" tx_result = redis_client.transaction(incr_req_cnt, *key, value_from_callable=True) print(tx_result) if __name__ == "__main__": main()
transaction関数の中身は次のようになっている。
def transaction(self, func, *watches, **kwargs): """ Convenience method for executing the callable `func` as a transaction while watching all keys specified in `watches`. The 'func' callable should expect a single argument which is a Pipeline object. """ shard_hint = kwargs.pop("shard_hint", None) value_from_callable = kwargs.pop("value_from_callable", False) watch_delay = kwargs.pop("watch_delay", None) with self.pipeline(True, shard_hint) as pipe: while True: try: if watches: pipe.watch(*watches) func_value = func(pipe) exec_value = pipe.execute() return func_value if value_from_callable else exec_value except WatchError: if watch_delay is not None and watch_delay > 0: time.sleep(watch_delay) continue
WatchErrorが発生した場合はリトライされる(watch_delay
を指定することで再実行までの時間を指定できる。リトライ回数は指定できない模様)
value_from_callable
変数を指定することで、transactionの返り値を設定できる。
例えば上記の incr_req_cnt()
に return 'success'
を入れたとして、 value_from_callable=True
にするとprintの中身は'success'
になり、Falseにすると、 pipe.execute()
の返り値(上記コードではcountキーが0だった場合 [2, 4]
)が出力される。