Redisのメモリが100%を超えたときの挙動の調査・検証

## はじめに

社内のプロジェクトでAWSのElasticCache for Redisを利用する機会がありました。
その際、Redisメモリ利用量が100%に達する可能性があったので、そのあたりの挙動について調査・検証してみてみました。

## 調査

### メモリが100%を超えようとする時どうなるのか

Redisはメモリが100％になったとしてもいきなり落ちることはありません。
Redisのメモリが100%になるかmaxmemory設定を超えた時、**Eviction** という事象が発生します。
**Eviction** とは直訳すると「立ち退き、明け渡し」的な意味で、既存のキーを削除しメモリを確保する挙動です。



### Evictionの設定
Eviction発生によるキーの削除ルールはRedisの`maxmemory-policy`という設定で変更することができます。
(ElasticCacheのRedisだとパラメータグループの設定で確認・編集できます)

`maxmemory-policy` の種類には以下のようなものがあります。キーに有効期限(TTL)があるかどうかなども重要になってきます。

|  maxmemory-policy  |  ルール  |
| ---- | ---- |
|  **noeviction**  |  メモリ制限に達したときに新しい値は保存されず、エラーを返す  |
|  **allkeys-lru** |  一番最後にアクセスされたキーを削除してメモリを確保  |
|  **allkeys-lfu**  |  最も使用頻度が少ないキーを削除してメモリを確保  |
|  **volatile-lru**  |  有効期限のある、一番最後にアクセスされたキーを削除してメモリを確保  |
|  **volatile-lfu**  |  有効期限のある、最も使用頻度が少ないキーを削除してメモリを確保  |
|  **allkeys-random**  |  ランダムにキーを削除してメモリを確保  |
|  **volatile-random**  |  有効期限のあるキーをランダムに削除しメモリを確保  |
|  **volatile-ttl**  |  有効期限が短いキーから削除してメモリを確保  |

ElastiCacheのRedisでは`volatile-lru` がデフォルトの設定になっています。

LRUやLFUアルゴリズムについて、詳しく知りたい方はRedisのドキュメントを見てみてください。

https://redis.io/docs/manual/eviction/

https://docs.aws.amazon.com/whitepapers/latest/database-caching-strategies-using-redis/evictions.html




## 検証
というわけで、次はDocker上でRedisを動かして検証してみます。

### 検証環境

・Windows 11
・Docker Desktop ver.4.6.1    Engine: 20.10.13
・Redis ver.6.2.6

### Redisの準備手順
まず、Dockerを使ってRedis検証用のコンテナを作成します。
```shell
// redisのイメージをpull
docker pull redis

// redis検証用のコンテナを作成及び起動
docker run --name redis-inspection -d redis:latest

// redis検証用のコンテナに入る
docker exec -it redis-inspection bash

// redisに接続する
/data#  redis-cli  
127.0.0.1:6379>
```

次にRedisの設定を確認し、`maxmemory-policy`と`maxmemory`を確認します。

```
/data#  redis-cli  
127.0.0.1:6379> INFO memory
.
maxmemory:0
maxmemory_human:0B
maxmemory_policy:noeviction
.
```
DockerのRedisイメージのデフォルトだとmaxmemoryが0なので無限に保存できてしまいます。
また、`noeviction`なのでメモリが溢れても自動削除されない設定になっています。

今回は検証なので、わざとメモリがあふれるように一時的にmaxmemoryを1MBくらいに設定しておきます。
(RedisのkeyのSET自体にもメモリが900KB前後ほど必要な模様)

```
127.0.0.1:6379> CONFIG SET maxmemory 1MB
OK
127.0.0.1:6379> INFO memory
maxmemory:1048576
maxmemory_human:1.00M
maxmemory_policy:noeviction
```


### 実験：メモリを溢れさせてみる
ここでは`noeviction`と`volatile-lru`のパターンで検証してみます。
方法として、3000回ループして次から次へとkey valueのSETを試みます。

#### `noeviction` の場合

```
:/data# for i in {1..3000} ; do echo ${i}; redis-cli SET ${i} test ; done
.
.
OK
2570
OK
2571
(error) OOM command not allowed when used memory > 'maxmemory'.
2572
(error) OOM command not allowed when used memory > 'maxmemory'.
.
.
```
途中からエラーが発生するようになりました。
```
(error) OOM command not allowed when used memory > 'maxmemory'.
```
2570回を超えた段階でメモリの上限に達し、2571回目からは書き込みエラーとなりました。

#### `volatile-lru`の場合

まずは`maxmemory-policy`を`volatile-lru`に変更しておきます。
```
127.0.0.1:6379> CONFIG SET maxmemory-policy volatile-lru
OK
```

(有効期限付けないver)
```
:/data# for i in {1..3000} ; do echo ${i}; redis-cli SET ${i} test ; done
.
.
OK
2570
OK
2571
(error) OOM command not allowed when used memory > 'maxmemory'.
2572
(error) OOM command not allowed when used memory > 'maxmemory'.
.
.
```
volatile-lruは有効期限のあるキーが削除対象なので、こちらも同様のタイミングで書き込みエラーが発生しました。

(有効期限付きver)
次は全てのkeyに3600秒のTTLを設定して実行してみます。
```
:/data# for i in {1..3000} ; do echo ${i}; redis-cli SET ${i} test EX 3600 ; done
.
.
2999
OK
3000
OK
```

書き込みエラーが発生することなく、3000回keyのSETを実行することができました。
メモリが100%超えた後のSETでは、既存のキーを削除しメモリを確保している挙動が確認できました。


## まとめ

・Redisはメモリが100％になったとしても、落ちることはない(Evictionが発生する)。
・max-memory-policyの設定によってEvictionの発生ルールを変えることで、100%になった際に上書きするかや受け付けないなどの設定することができる。
・ポリシーによっては、TTL(有効期限)の付け忘れによってキーが削除されないなど予期せぬエラーを生んでしまう可能性があるので注意しておく。