Redis について

Redis の知見が無かったので学習を含めてメモを記載します。

#特徴

* Redisはインメモリデータベースとして利用される BSD です。

  * 一般的なデータベースでは、ディスク(ストレージ)にデータを格納し、ディスク上のデータをメモリ上から読み込み、メモリ上のデータをCPUで処理するという流れで動作します。この動作プロセスに対比してインメモリデータベースは、データストレージ用のメインメモリ上にデータを保持し、メモリ上のデータをCPUで処理します。一般的には、RDB よりも Redis は高速であると言われているようです。
  * RDBのバッファプールのようにキャッシュしたデータに参照データが無いものはディスクから読み取りする訳ではない為、メモリの容量内に全てのデータを収める必要性があります。


* NoSQL データベースであり KVS です。

  * NoSQL (非リレーショナル) データベースは RDB(リレーショナルデータベース)の対義で、RDB のように表形式の関係モデルではないデータベースを NoSQL として総称しています。また、KVS(Key-Value-Store) は、キーに対して値(value)を書き込み、キーを指定することで値を読み出す事が出来るデータベース管理ソフトウェアです。なので、NoSQL の一種である KVS を採用しています。が適語かと思います。

  * KVS はキー同士に関連性がない分、分散化(複数台のサーバを使用）するコストが低くなる特性があります。なお、耐障害性としてのレプリケーションがありますが、分散化とはデータの配置先を表現しており、aサーバとbサーバには別々のデータが格納されている事を示します。後述に挙げる Redis cluster、Redis Replication がこれに該当します。

* client/serverモデルを使用します。

  * 複数のクライアントからのアクセスが可能です。client からTCP/IP (defalut port:6379) のリクエストを送り、サーバー側の Redis が結果をレスポンスとして client(アプリケーション側)に返します。

* 永続的にデータを保持する機能があります。

  * KVS には、データを保持する観点から揮発性KVSと永続性KVSに分かれます。
Redis は、メインメモリのデータが揮発性なので、時間が経つとデータが消えてしまう 揮発性KVS に分類されるようですが、Redis はメモリ上のデータをストレージに格納してデータを永続的に保持する機能があります。

#構成と仕様

###オンメモリの使用
Redis はメモリ上で処理をするため、使用する物理メモリの上限値を決め、超過する場合の挙動を設定する事が出来ます。

| ポリシー | 挙動 |
|:-:|:-:|
| noeviction  | メモリ使用量が制限に達しており、クライアントが追加のメモリを要求するコマンドを実行しようとした場合はエラーを返す。 |
| allkeys-lru  | 新しいデータのスペースを空けるため、もっとも最近使われていない(LRU)キーから削除する。|
|　volatile-lru   |  新しいデータのスペースを空けるため、expire set が指定されたキーが指定されたもっとも最近使われていない(LRU)キーから削除する。  |
| volatile-random  | 新しいデータのスペースを空けるため、expire set が指定されたランダムなキーを選んで削除する。|

###データ構造

Redis がサポートするデータ構造は以下です。
データ構造とはデータを効果的に使用するために、一定の方式に沿ってデータを格納する形式を表現しています。

| データ型 | 概要 |
|:-:|:-:|
| Strings  | Key と Valueが 1対1で管理されるデータ型。  |
| Lists  |  1つのKeyに対してValueを複数管理し、順番を付ける型。リストの先頭(Left)または末尾(Right)から値を挿入と取り出しを行うことができる。 |
| Sets  |  1つのKeyに対してValueを複数管理できる点はList型と同様ですが、順番が無く、異なるKeyで管理されたデータ群同士の、和・差・積を算出できる。 |
| Hashes  | 親Key、子Key、Valueで管理されるデータ型。  |
| Sorted sets  |  List型に追加して特定の順番に並んだ形でデータ管理を行うことができる。 |
|  Bitmaps and HyperLogLogs | Stringに基づくデータ型であるが、独自のセマンティクスを持つビットマップとHyperLogLogもサポートされる。
  |

###シングルスレッド
Redis はシングルスレッドのため、他のRDB のように並列処理が不可能です。1スレッド毎に処理を実行します。

###データベース
1つの Redis サーバ上に複数のDBを作成出来ます。
このDBの内部に key-value 形式のデータを保持します。

###Redis serverの複数台構成

- Redis Cluster
  - シャーディングにより、データを複数のRedisサーバに自動的に分散する事が出来ます。
また、フェールオーバー機能として、自動的にスレーブノードがマスターノードに昇格する機能があります。
なお、redis clusterはスロットと呼ばれる番号でデータを格納し、スロットの番号に沿っていずれかのノードにデータが格納されます。
  - 負荷分散や可用性に特化した構成は、 Cluster構成を選びますが、ノードダウン時の一時的なデータ参照不可が可能性としてあります。

- Redis Replication
  - Redis のレプリケーションはマスターレプリカの構成で非同期レプリケーションですが、読み取り側は、マスターと定期的に受信したデータの量を非同期的に確認します。ただし、Redis Replication は自動のフェイルオーバー機能を保持していないため、手動での切り替えになります。
  - Redis がダウンしても急いで復旧するシステムではない問題ない場合には Replication のシンプル構成を選びます。

- Redis Sentinel 
  - サーバの死活監視や通知機能提供されており、監視対象のサーバが落ちた場合に自動フェイルオーバーなどが行うことができます。
監視対象のマスターに対して複数のSentinelプロセスを起動させておき、マスターの情報を共有しあいます。マスターがダウンした際に、投票方式で過半数以上のSentinelプロセスがダウンしたと判断すると自動的にフェイルオーバーします。
  - フェールオーバーの機能と、フェールオーバーの復旧時間が許されるのであれば、Sentinel 構成などを選びます。 

###データのバックアップ

コマンドや設定値により、指定された時間間隔で.rdb ファイル形式としてRedisサーバ全てのデータをスナップショット(point-in-time)で保存出来ます。
別の方法としてAOF (Append Only File)の設定を有効化する事により、受信された全ての書き込み操作をファイルに記録し、サーバの起動時にリストアを行い、元のデータセットを再構築する方法もあります。

検証
=====
上記の内容を踏まえて Redis サーバーを導入して触ってみます。
AWS の EC2(ubuntu)にRedis をインストールしてみます。

```
sudo apt install redis-server
redis-server --version
> Redis server v=5.0.7 sha=00000000:0 malloc=jemalloc-5.2.1 bits=64 build=636cde3b5c7a3923
```

Redisの設定は `/etc/redis/redis.conf` で管理されています。
そのまま見ると長いので有効化された設定のみ確認します。

```
cat redis.conf | grep -v "#"

bind 127.0.0.1 ::1
protected-mode yes
port 6379
tcp-backlog 511
timeout 0
tcp-keepalive 300
daemonize yes
supervised no
pidfile /var/run/redis/redis-server.pid
loglevel notice
logfile /var/log/redis/redis-server.log
databases 16
always-show-logo yes
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir /var/lib/redis
replica-serve-stale-data yes
replica-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
replica-priority 100
lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
replica-lazy-flush no
appendonly no
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
aof-use-rdb-preamble yes
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
stream-node-max-bytes 4096
stream-node-max-entries 100
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
dynamic-hz yes
aof-rewrite-incremental-fsync yes
rdb-save-incremental-fsync yes
```

重要な設定を少しだけ確認します。

| パラメーター | 意味 |
|:-:|:-:|
| bind  | バインドするネットワークのインターフェースを限定できる。デフォルト 127.0.0.1 はローカルループバックで、複数指定はスペース IP で可能 |
| port  | クライアント向けポート、デフォルトのポートは 6379  |
| daemonize  | デーモンとして起動する場合に yes |
| pidfile  | プロセスIDのファイルパス /var/run/redis/redis-server.pid  |
| databases  | databaseの個数。デフォルトは16個。CONFIG GET databases で数を確認出来る  |
| dbfilename  | バックアップ用のRDBファイルのパス /var/lib/redis  |
| save  | バックアップ用のRDB ファイルを自動的にディスクに保存するタイミングを設定します。デフォルト値の見方としては 1回の更新があると900秒以内に保存、10回の更新があると、300秒以内に保存、10000回の更新があると、60秒以内に保存する  |
| appendonly  | AOFバックアップの設定です。デフォルトは無効化  |
| appendfilename  | AOFバックアップファイルの名前  |
|  appendfsync | fsyncの実行を示すものです。fsyncはメモリ上のファイルの状態とディスク上の状態を同期させるコマンドです。デフォルトは 1秒毎にfsyncします。  |
| aof-load-truncated  | AOFから復旧した場合、読み終わったAOFファイルを消すかどうかを指定  |
| slowlog-log-slower-than  | クエリが10秒(10000ミリ秒)より遅かった場合にログに記録  |
| hash-max-ziplist-entries  | Hash 型のデータ構造において 1バケットあたりのフィールドの数。ハッシュバケットとは、データ項目を割り当てる場合に使用される一意の番号
  |
| hash-max-ziplist-value  |  フィールド(子キー)の最大サイズ(byte) |
|  client-output-buffer-limit |  クライアントとの接続を切断する容量しきい値を示します。nomal は通常のクライアントを示し、0は無効化。値を入れる場合は、mb(メガバイト) 形式で入れる|
| replicaof | これはデフォルト無効化となっている設定ですが、replicaof はredisをreplicaとして設定する設定値 |


続いてサーバ上でコマンドを打ってみます。

```
$ redis-cli
```

まずはデータベースが 16個存在する事を確認します。

```
127.0.0.1:6379> CONFIG GET databases
1) "databases"
2) "16"
```

続いて、データベースのデータ数を表示します。
この場合、db0 に key は 1つ存在する事を示します。

```
127.0.0.1:6379>  INFO keyspace
# Keyspace
db0:keys=1,expires=0,avg_ttl=0
```

データベース0 の　keyを表示すると test が表示されました。

```
127.0.0.1:6379> select 0
OK
127.0.0.1:6379> keys *
1) "test"
```

`Strings`の key:value を`mset`で保存します。
key:test test2 test3 value:それぞれに1 を返します。

```
mset test 1
mset test2 1
mset test3 1
mget test test2 test3
127.0.0.1:6379> mget test test2 test3
1) "1"
2) "1"
3) "1"
```

続いて `list` の key:value を `lpush` で保存します。
`llen` はリストの長さを返し、lpop はリストの先頭の値を取り出します。

```
127.0.0.1:6379> lpush test4 1 2 3
(integer) 3
127.0.0.1:6379> llen test4
(integer) 3
127.0.0.1:6379> lpop test4
"3"
127.0.0.1:6379> llen test4
(integer) 2
```

続いて `sets` の key:value を `sadd` で保存します。
`smembers` はvalue の値を返し、`scard` は件数を取得します。

```
127.0.0.1:6379> sadd test6 1 2 3
(integer) 3
127.0.0.1:6379> smembers test6
1) "1"
2) "2"
3) "3"
127.0.0.1:6379> scard test6
(integer) 3
```

最後に `Hashes` の親key:子キー:value を `hset` で保存します。
`hgetall` で 子キー(フィールド)と値をすべて取得します

```
hset test7 a 1
hset test7 b 1

hgetall test7
127.0.0.1:6379> hgetall test7
1) "a"
2) "1"
3) "b"
4) "1"
```

続いてバックアップリストアを見ます。

現状のキー数を確認し、`bgsave` によりバッググランドで実行されます。
完了後に RDBのファイルパスに `dump.rdb` が生成されますので、これをバックアップファイルとして利用します。
redisの停止を行いバックアップファイルをリネームして退避します。起動を行い、キーを削除し再起動して復元を確認します。

```
127.0.0.1:6379> dbsize
(integer) 7

127.0.0.1:6379> bgsave
Background saving started

127.0.0.1:6379> lastsave
(integer) 1633911531

sudo service redis stop
sudo mv /var/lib/redis/dump.rdb /var/lib/redis/dump.rdb.bak
sudo cp /var/log/dump.rdb /var/lib/redis/dump.rdb
chown redis:redis /var/lib/redis/dump.rdb
sudo service redis start

127.0.0.1:6379> del test4 test5
(integer) 2

sudo service redis stop
sudo service redis start

127.0.0.1:6379> dbsize
(integer) 7
```

最後に Client からの処理です。

Windows から Redis への処理を実行します。
`redis` のモジュールを `pip` でインストールします。

```
pip install redis
```

以下 test.py

```test.py
import redis

r = redis.Redis(host='接続IP', port=6379, db=0)

r.set('test10', '1')

1 = r.get('1')
print(1.decode())
```

redis サーバー側では セキュリティグループのポート`6379`を開放
`/etc/redis.conf` の `protected-mode` および `bind` にクライアントのIPを追加します。

```
protected-mode no
bind 127.0.0.1 <client-IP>
```

これで接続が完了します。