Redisの挙動を実験

# はじめに
RailsでRedisを使うために挙動を確認しました。
Railsで使ってはいるのですが、薄めのラッパー関数でRedisのコマンドをほぼそのまま呼び出すだけなので、Redisに直接実行してるのと変わらないかと思います。

Redisはdockerで動かしています。

#### バージョン
- Redis: v7.2
- Rails: v7.0
- redis-client（gem）: v0.22.2

# 初期化
initializerでredisインスタンスを作ります。

```ruby
redis_config = RedisClient.config(host: "redis", port: 6379, db: 1)
REDIS_DATA_STORE = redis_config.new_pool(timeout: 3, size: Integer(ENV.fetch("RAILS_MAX_THREADS", 5)))
```

# 基本構文

```shell
pry(main)> REDIS_DATA_STORE.call("SET", "id1", "some value")
=> "OK"
pry(main)> REDIS_DATA_STORE.call("GET", "id1")
=> "some value"
```

# ハッシュ

#### 基本的な使い方
```shell
pry(main)> hash1 = { "key1" => "value1", "key2" => "value2" }
=> {"key1"=>"value1", "key2"=>"value2"}

pry(main)> REDIS_DATA_STORE.call("HSET", "hid1", hash1)
=> 2

pry(main)> REDIS_DATA_STORE.call("HGETALL", "hid1")
=> {"key1"=>"value1", "key2"=>"value2"}
```


#### キーにシンボル使った場合、自動でstringのキーになる。

```shell
pry(main)> hash2 = { key1: "value1", key2: "value2" }
=> {:key1=>"value1", :key2=>"value2"}

pry(main)> REDIS_DATA_STORE.call("HSET", "hid2", hash2)
=> 2

pry(main)> REDIS_DATA_STORE.call("HGETALL", "hid2")
=> {"key1"=>"value1", "key2"=>"value2"}
```

#### 一部を指定すれば、一部だけ更新される。
```shell
pry(main)> REDIS_DATA_STORE.call("HSET", "hid1", {"key1" => "new value1"})
=> 0

pry(main)> REDIS_DATA_STORE.call("HGETALL", "hid1")
=> {"key1"=>"new value1", "key2"=>"value2"}
```

#### ハッシュの中にハッシュは入れられない。
リストも同様に使えないので、使いたい場合はjsonにしてstringとして保存するか、内側のハッシュを別のキーで保存して、そのキーをトップのハッシュのvalueに保存する。
```shell
pry(main)> sub_hash = { subkey1: "sub value" }
=> {:subkey1=>"sub value"}

pry(main)> main_hash = { key1: "value1", key2: sub_hash }
=> {:key1=>"value1", :key2=>{:subkey1=>"sub value"}}

pry(main)> REDIS_DATA_STORE.call("HSET", "hid3", main_hash)
TypeError: Unsupported command argument type: Hash
```

# Expire
### 3秒後に消える設定をした場合
```ruby
def expire_test
  REDIS_DATA_STORE.call("SET", "expire_key1", "value1")
  REDIS_DATA_STORE.call("EXPIRE", "expire_key1", 3)

  4.times do |i|
    p "#{i} sec passed"
    p REDIS_DATA_STORE.call("GET", "expire_key1")
    sleep 1
  end
end
```

```shell
pry(main)> expire_test
"0 sec passed"
"value1"
"1 sec passed"
"value1"
"2 sec passed"
"value1"
"3 sec passed"
nil
```

### 途中でデータupdateした場合
EXPIREされないので、新たに設定が必要

```ruby
def expire_test2
  REDIS_DATA_STORE.call("SET", "expire_key1", "value1")
  REDIS_DATA_STORE.call("EXPIRE", "expire_key1", 3)

  8.times do |i|
    p "#{i} sec passed"
    p REDIS_DATA_STORE.call("GET", "expire_key1")
    sleep 1

    REDIS_DATA_STORE.call("SET", "expire_key1", "updated value1") if i == 1
  end
end
```

```shell
pry(main)> expire_test2
"0 sec passed"
"value1"
"1 sec passed"
"value1"
"2 sec passed"
"updated value1"
"3 sec passed"
"updated value1"
"4 sec passed"
"updated value1"
"5 sec passed"
"updated value1"
"6 sec passed"
"updated value1"
"7 sec passed"
"updated value1"
```

### ハッシュをupdateした場合
ハッシュをupdateした場合は、expireは更新されず、削除もされない。

```ruby
def expire_test3
  hash = { "key1" => "value1", "key2" => "value2" }
  REDIS_DATA_STORE.call("HSET", "expire_key2", hash)
  REDIS_DATA_STORE.call("EXPIRE", "expire_key2", 3)

  5.times do |i|
    p "#{i} sec passed"
    p REDIS_DATA_STORE.call("HGETALL", "expire_key2")
    sleep 1

    REDIS_DATA_STORE.call("HSET", "expire_key2", {"key1": "updated value1"}) if i == 1
  end
end
```

```shell
pry(main)> expire_test3
"0 sec passed"
{"key1"=>"value1", "key2"=>"value2"}
"1 sec passed"
{"key1"=>"value1", "key2"=>"value2"}
"2 sec passed"
{"key1"=>"updated value1", "key2"=>"value2"}
"3 sec passed"
{}
"4 sec passed"
{}
```

# 同じキーに違うデータタイプを割り当てる
違うタイプに同じキーは使えない。
```shell
pry(main)> REDIS_DATA_STORE.call("SET", "id4", "value4")
=> "OK"
pry(main)> REDIS_DATA_STORE.call("HSET", "id4", {"key4" => "hash value4"})
RedisClient::WrongTypeError: WRONGTYPE Operation against a key holding the wrong kind of value
```

# まとめ
バージョンによって挙動が異なるかもしれないので、実験してみるのが大事です。