特定の時間にリセットするようなキャッシュをHashを使って管理したい、というときはNXをつけた方が良かったです。
例
次の使い方を想定する。
- 1時間に一度、0分のタイミングでキャッシュをリセットする
- HGETでfieldを探して、すでに存在していればvalueを使う
- 存在しなければ計算してHSETでfield valueを追加し、次の0分までの秒数を有効期限としてexpireで設定する
12:00ちょうどのとき、残り3600秒間利用したいので、expireで3600を指定した。
redis> HSET myhash field1 value1 (integer) 1 redis> EXPIRE myhash 3600 (integer) 1
次に、12:30にfield2へのアクセスがあったので、計算し、追加した。
HSET myhash field2 value2 (integer) 1 EXPIRE myhash 1800 (integer) 1
このやり方だと、EXPIREに設定するときに計算した秒の値がズレていると毎時0分を超えてキャッシュしてしまう。
さきほどfield2を追加したとき、厳密には12:30:00.100だったので、13:00:00.100まで延長された。
このあと、13:00:00.000〜13:00:00.099の0分を超えたキャッシュが存在している間に同じように新しいfield valueをセットすると、0分のときに次の0分までの時間は3600秒なので、この1時間の間に作成したキャッシュをexpireせずに、1時間延長できてしまう。
NXをつける
EXPIREにはOptionsとして、NXやLTが設定できるので、これを設定することで、キャッシュを確実に期限切れ状態にできる。
- NX: 有効期限の設定がない場合のみ、有効期限を設定できる
- XX: 有効期限が設定済みの場合のみ、有効期限を設定できる
- GT: 有効期限が設定済みで現在より大きい場合のみ、有効期限を設定できる
- LT: 有効期限が設定済みで現在より短い場合のみ、有効期限を設定できる
今回の使い方だと、keyに対しての有効期限は固定なので、expireを設定するのは最初の一回だけで良い。
HSET myhash field3 value3 (integer) 1 EXPIRE myhash 600 NX (integer) 0 -- 操作がスキップされた。
こうすることで、初回以降は有効期限が上書きされなくなるので、12:00:00.300に最初のキャッシュの有効期限を3600で作ったとしても、13:00.00.300にはEXPIREされる。
(13:00.00.200にキャッシュを更新しようとしても、操作がスキップされる)
このように、ひとつのkeyに要素を何度も追加するようなときには、毎回expire設定を上書きしない方が良い。
しかし、13:00:00.000ぴったりにEXPIREしたい、という場合には、この方法は使えない。
PEXPIREでミリ秒単位の有効期限を設定すればより0秒に近づくが、ミリ秒だとしてもズレる。
したがって、ミリ秒単位の遅れも許されずにぴったりにEXPIREしたい場合には、他の方法を検討する必要がある。
他の方法
- EXPIREAT | Docsというのもあって、これだと有効期限を厳密なunix timestampで設定できるので、ぴったりのunix timestampを計算すれば、その時間ぴったりにキャッシュを切らすことができる
- 今回の例のような、ある時間までを有効期限に決めたい場合、unix timestampを計算してセットするのが素直な実装だろう
- 切り替えタイミングの時間をkeyに含めてしまう
- 時間経過でkeyが変わるので、前の時間のキャッシュは取得できない
- key単位ではなく、fieldごとに有効期限を決めたいなら、HEXPIRE | Docsもある