Redis にはハッシュというデータタイプがあり、 1 つの key に対して複数の field / value を保存することができます。
PHP / Laravel で Redis のハッシュに複数のデータを書き込んだ時に、意図しない挙動となってしまったことがあったので、紹介します。
以下のコードの前提
$this->redis
はPhpRedisConnection
のインスタンスです- 1 つの key に対して 3 つの field / value を保存する
set
関数を定義します
BAD
public function set(string $key, string $data1, string $data2, string $data3): void
{
$this->redis->hSet($key, self::DATA1_KEY, $data1);
$this->redis->hSet($key, self::DATA2_KEY, $data2);
$this->redis->hSet($key, self::DATA3_KEY, $data3);
}
GOOD
public function set(string $key, string $data1, string $data2, string $data3): void
{
$this->redis->multi();
$this->redis->hMSet($key, [
self::DATA1_KEY => $data1,
self::DATA2_KEY => $data2,
self::DATA3_KEY => $data3,
]);
$this->redis->exec();
}
何が起きたか
BAD の例だと、例えば 1 つ目の hSet
が終わった直後のタイミングでこのハッシュが参照された場合、 DATA1_KEY
しか含まれていない状態になります。
問題が起きた箇所は、ハッシュがあるなら 3つの key が全て存在する前提でコードを書いていたため、 参照時に DATA2_KEY
DATA3_KEY
が存在しないことで、意図しないエラーになってしまいました。
対処
1: HSET
ではなく HMSET
を使う
HMSET
を使う事で、ハッシュに対してアトミックに複数の field / value を同時に設定できます。
ただし、 redis-cli
では Redis 4.0.0 から HSET
で複数の field / value を指定できるようになったため、 HMSET
は現在非推奨と見なされるとのこと。
As of Redis version 4.0.0, this command is regarded as deprecated.
https://redis.io/commands/hmset/
It can be replaced byHSET
with multiple field-value pairs when migrating or writing new code.
PhpRedisConnection には hMSet
関数はありますが、 内部実装的にも HMSET 使ってるようです。 HMSET
を避けようと思うと結局コマンド数が増えてしまったりするので、ひとまずは hMSet
関数を利用するようにしました。
2: トランザクションを利用する
MULTI
でトランザクションを開始します。
その後、各コマンドを発行すると、その時点では実行されずにキューに溜まっていきます。
そして EXEC
を実行すると、キューに溜まったコマンドが実際に実行されていきます。
トランザクション中は排他処理になるため、今回のような問題は起きなくなります。
PhpRedisConnection にも multi()
exec()
があるので、GOOD の例のように利用できます。
なお、この対処だけでも当初の問題は解決しますが、 1. もやっておくとコマンド数が減るので良いと思います。
ちなみに transaction
関数を用いる方法もあるようです。
今回のケースでは callback
に use
を書くのが冗長に感じたので利用しませんでしたが、どこからどこまでがトランザクションの範囲かが明確になるので、状況によって使い分けると良さそうです。
おわりに
ILでは Redis のようなインメモリ DB を用いてアプリケーションを高速化する事に興味がある方の採用応募もお待ちしております。
今回の記事で IL に少しでも興味を持たれた方は是非、弊社採用情報ページからご応募よろしくお願いします!