kakts-log

programming について調べたことを整理していきます

redis のsetのnxオプションについて

概要

redisでsetコマンドのnxオプションというものがあり setする際に、キーがまだ存在していない場合にのみ値をセットできるもので、redisによるロック処理に使われます。 このnxオプションについて調べてみます。

SET | Docs

NX -- Only set the key if it does not already exist.

setコマンドでのnxオプションをつけてみる

redis-cli を実行し、redisに対して set とNXオプションとEXによるTTLを設定し 簡単なロック機能を試してみます。

set key value NX EX 10 というコマンドを実行します。
- NX: コマンド実行時に既にキーが存在していない場合のみsetできる。 そうでない場合はnil
- EX 10: 10秒間のTTLを設定する。 キーを登録してから10秒を超えるとキーに紐づくデータが消える

上記のコマンドにより、10秒間のロック機能を実現します。

redis-cli
127.0.0.1:6379> set key value NX EX 10
OK # ここでロックがかかる
127.0.0.1:6379> set key value NX EX 10
(nil)
127.0.0.1:6379> set key value NX EX 10
(nil)
127.0.0.1:6379> set key value NX EX 10
(nil)
127.0.0.1:6379> set key value NX EX 10
(nil)
127.0.0.1:6379> set key value NX EX 10
(nil)
------ ここまでロックをかけてから10秒間の間は set nxをしてもnilとなる
127.0.0.1:6379> set key value NX EX 10
OK # 10秒経ったので新たにロックをかけることができる

これにより、キーがない状態で set key value NX EX 10 を実行してロックをかけ、その後10秒間は 同じコマンドを実行してロックを獲得しようと試みても失敗することを確認できました。

redisがシングルインスタンスの場合、このset でのNXとEXオプションをつけることで簡単なロック機構を実現できます。

set nxオプションが付いていた場合の内部実装について

nxオプションをつけた時にsetコマンドではどういう処理になっているかを説明します。

redis本体のソースコードでの実装箇所は、 src/t_string.cのsetGenericCommand 関数から辿ることができます。

redis/src/t_string.c at 7.2.5 · redis/redis · GitHub

set実行時のNXオプションがついているかの判定

set実行時のオプション引数のパース処理をみてみます。

パース処理はparseExtendedStringArgumentsOrReply 関数で実装されています。 redisに対するコマンドの引数をパースして、 NXオプションが設定されていたら SET NX用のフラグ OBJ_SET_NX を立てる処理を行っています。

...
        if ((opt[0] == 'n' || opt[0] == 'N') &&
            (opt[1] == 'x' || opt[1] == 'X') && opt[2] == '\0' &&
            !(*flags & OBJ_SET_XX) && (command_type == COMMAND_SET))
        {
            *flags |= OBJ_SET_NX;
        } 
...

redis/src/t_string.c at f60370ce28b946c1146dcea77c9c399d39601aaa · redis/redis · GitHub

OBJ_SET_NXフラグがたっている場合の setコマンドの実行

続いて、NX用のフラグが立っている場合にsetコマンドでどういう処理が行われるかをみてみます。

setコマンドの処理はsetGenericCommand 関数で実装されています。

関数の前半の方で、まず指定したキーがデータに存在しているかを確認し、存在していた場合はアーリーリターンして、コマンドの実行を終了し、その後にデータを登録する処理はスキップされます。 これにより、既にキーが存在していた場合 setでnxオプションをつけて実行された場合はデータの登録はされずにスキップされることになります。

    // set予定のキーが登録済みか確認する
    found = (lookupKeyWrite(c->db,key) != NULL);

    // NXフラグまたはXXフラグが設定されていて、キーが見つかった場合
    if ((flags & OBJ_SET_NX && found) ||
        (flags & OBJ_SET_XX && !found))
    {
        if (!(flags & OBJ_SET_GET)) {
            addReply(c, abort_reply ? abort_reply : shared.null[c->resp]);
        }
        return;
    }

// この後、setの処理が続く
...

以上となります。 EXまたはPXオプションを設定した場合、指定した時間が経ったらキーの値が削除され、ロックが解除されることになります。
その後再度ロックをかけることができます。