Redis 6.0.0 GA アップデート内容紹介

# Redis 6.0.0 GA
ついに本日(2020/04/30)、Redis 6.0がGAになりました。
今回も今までの例にもれず、release noteベースに簡単に解説していきます。

https://redis.io/

##### 過去の記事
 - [Redis 4\.0の目玉機能解説 \- Qiita](https://qiita.com/maruloop/items/ea3b067d6d32b6dbca3e)
 - [Redis 5\.0 Update解説 \- Qiita](https://qiita.com/maruloop/items/207654bfd0ed2a453300)

##### あなたは誰
『niconico』が利用している株式会社KADOKAWA Connectedのプライベートクラウドで、Redisのマネージドサービスなどやってます。
https://engineering.kdx.co.jp/

# 過去のRedisメジャーバージョンアップ
今までもRedisでは大きな新機能が出るたびに、メジャーバージョンアップを行ってきました。

 - Redis 3系：Redis Cluster GA
 - Redis 4系：Redis Module対応
 - Redis 5系：Stream型対応

今までのメジャーバージョンアップも大規模でしたが、今回はあまりにも機能追加が激しいです。
そのため、主要なものに絞って紹介します。


## クライアントサイドキャッシュ

解説ページ：https://redis.io/topics/client-side-caching
クライアントサイドキャッシュと聞いて、おそらく皆さんが不安になるであろう、データの整合性についてどのような考え方をされているか、ここに記載します。

データの整合性の問題とは、クライアント側でusernameをAliceで記憶した後、別のクライアントからBobに更新された際に、古いAliceをキャッシュしたままのクライアントが存在することに起因する問題です。

これの最もシンプルな対応方法はTTLです。usernameがAliceからBobに更新されたとしても、TTL範囲内ではAliceを返す可能性を許容できるアプリケーションであれば、とてもシンプルに実現できます。

しかしながら、そんなことが許容されることは数少ないのではないでしょうか？
ちなみに、niconicoの一部サービスでは、configをredisに持ち、それをアプリケーションが一定期間クライアントサイドキャッシュする実装はあります。
config変更は、管理ツールから(エンジニア以外でも)出来るようになっており、反映がサーバーごとに異なる(TTL 5分程度)、といった使い方です。
更新が非常に少なく、参照が多く、config反映が同時でないケースですね。


もう少し頭の良いクライアントサイドキャッシュを考えてみます。
従来(Redis 2.8以降)では、Pub/Subチャンネルを利用した[Redis Keyspace Notifications](https://redis.io/topics/notifications)機能を利用することで
キーの更新があればクライアントに通知し、クライアントサイドキャッシュを作り直すことが可能でした。

しかし、この機能ではデータ更新を、キャッシュしている/いないに関わらず、すべての購読しているクライアントにPublishします。
この問題点は、Redis側のCPUコストです。結果として、Redisの負荷は高くなり、パフォーマンスが落ちます。


#### Redis6におけるクライアントサイドキャッシュの変更通知方法

Redis側のCPUコスト問題を解決するには、もっともっと賢く必要に応じてクライアントに通知すれば良いのです。
つまり、Redis側で「クライアントがどのキーにアクセスしたか？」を記憶しておきます。
そして、そのキーが変更された場合、アクセスしたことがあるクライアントのみに通知します。
これは当然RedisのMemoryを消費しますが、元よりRedisを使うケースというのは、Memoryに金をかけてアプリケーションをハイパフォーマンスにするためです。
これはそれをさらに推し進めた形といえます。


Redis側で保持される「クライアントがどのキーにアクセスしたか？」情報は、無効化テーブルと呼ばれます。無効化テーブルはある一定量以上のサイズになると、新しいキーを追加するたびに古いキーを押し出し、古いキーは変更されていなくても無効化イベントを通知されます。
これにより、無限にMemoryを消費する、ということは回避されています。

なお、上記がデフォルトモードと呼ばれているもので、もうひとつブロードキャストモードがあります。
ブロードキャストモードでは、クライアントはキーのprefixを予め登録しておきます。
そのprefixのキーに変更がある場合に、通知が飛ぶようになります。


#### 無効化イベントの通知チャンネル
この次に紹介しますが、Redis6.0では新規プロトコルとしてRESP3が追加されました。
RESP3では、ひとつのコネクションで上記の用途で使うような、通常クエリと無効化イベントの両方を取り扱えるような多重化の機能を有しています。
RESP2でクライアントサイドキャッシュを利用する場合は、通常クエリ用のコネクションの他に従来と同様にPub/Sub用のコネクションを確立する必要があります。

補足ですが、Redis Clusterでは通常クエリはスケールアウト出来ますが、Pub/Subはスケールアウト出来ません。そのため、大規模なRedis Cluster環境でクライアントサイドキャッシュを有効にする場合は、RESP3が推奨となるかもしれませんね。
※どなたか試された方がいましたら、教えてください


クライアントサイドキャッシュにはまだまだ語れることがございますが、6.0の他の機能も非常に豊富なため、他記事に譲ります。

# 新プロトコル(RESP3)の対応追加
https://github.com/antirez/RESP3/blob/master/spec.md

クライアントサイドキャッシュでも話が出たRESP3です。
Redis6では、RESP2とRESP3の2Protocolが対応されています。
なお、RESP3の話を今まで少し追っていた方はご存知かもしれませんが、発表された当時はRedis6はRESP3しか対応しない方針でした。
つまり、クライアントライブラリがRESP3に対応するまでRedis6を使えない可能性がありましたが、Redis6の方針が変わり、その心配はなくなりました。

#### RESPの設計思想
Redisプロトコルを読んだことがない方もいらっしゃるかもしれませんが、一度読んでみると驚くほど人間が目で見て処理が追えることがわかると思います。
つまりRESPは人が目で見てデバッグがしやすいほどシンプルでわかりやすく、それでいて高速であれ、という思想のものです。

しかし欠点も抱えています。それはシンプルかつ高速にするために、プロトコル自体に文脈情報を持たないということです。
例えば、「LRANGE」「SMEMBERS」「HGETALL」のコマンドはプロトコル的にはすべて配列で返信します。
クライアントはどのコマンドを送ったか？を記憶しておき、配列が返ってきた際に、記憶していたコマンドに応じた処理(例えばList型にするとかMap型にするとか)をする必要がありました。

また、重要なデータ型を持たないため、浮動小数点は文字列型ですし、真偽値は整数型で返します。

最後に、バイナリセーフエラーを返す方法がない、という欠点もあります。

これらを改善すべく、ゼロから再設計し、さらに前述のクライアントキャッシュで利用した多重化した通信のための機能追加を行ったのがRESP3となります。

# SSL対応

Redisにおいても通信の暗号化がようやく入りました。
https://github.com/antirez/redis/blob/unstable/TLS.md

# ACL対応

ついにRedisに、ACL機能が追加されました。
私も登壇させていただいた[【東京】LINE Developer Meetup \#36 \- Redis](https://line.connpass.com/event/86642/)にて、
プロポーザル状態だった当時に、DaeMyung Kangさんが解説されたわかりやすいスライドは下記です。
https://www.slideshare.net/charsyam2/redis-acl

公式資料：
https://redis.io/topics/acl

パスワード認証によって、クライアントの利用出来るコマンドやキーを制限出来るようになります。

# Thread I/O対応による高パフォーマンス化

シングルスレッド処理！というのが謳い文句なRedisでしたが、マルチスレッド対応されたようです。
開発中のベンチマークを開発者のantirezさんに確認した方がいました。
2スレッドで試験したところ逆に10倍ほど遅くなったようですが、
8コア程度のマシンでベンチマークする必要がある、とのことでした。
https://twitter.com/antirez/status/1111166392332681216

Redis Clusterを利用して、シャーディング&シェアードナッシングでいくか、マルチスレッドでいくか、
より設計の選択肢が増えた分、最適を目指すと難しそうですね。

# Redis Cluster Proxy
ついにRedis ClusterにもProxyが生まれました。
https://github.com/RedisLabs/redis-cluster-proxy

https://redis.io/topics/partitioning にもあるように、従来Redisにおけるシャーディング(パーティショニング)戦略では、クライアントサイドパーティショニングを採用していました。
つまり、Redis Cluster対応とは、クライアント側で適切なシャードに対してクエリを投げることを前提にしており、
Redis側でリシャーディングが行われた場合も、それに追従する責務はクライアント側が持っていました。

このクライアントへの重い責務というのが一つ問題であり、言語ごとにCluster対応に濃淡が出来ている状態でした。
※特にエラー処理やクロススロットなコマンドへの対応などのエッヂケース

Redis Cluster Proxyでは、Proxy Side Partitioningの考え方となりますので、それらの責務からクライアントは開放されます。
対応するコマンドは下記の通りです。
https://github.com/RedisLabs/redis-cluster-proxy/blob/unstable/COMMANDS.md

# その他の機能強化

 - 今までexperimentalだったDiskless ReplicationとPSYNC2の改善により、レプリケーションが改善
 - RDBファイルのload高速化
 - Redis Cluster toolsの充実(benchmarkが出来るようになった)

# まとめ
とにかく大きな変更が多いという印象のRedis6です。
一通りの変更点を頭に入れたら、次は実際に手を動かして試してみましょう！

良いRedisライフを。

(KADOKAWA Connectedでは、[Redisをマネージド・サービスとして一緒に提供する仲間](https://herp.careers/v1/kadconnected/WBlN7P3bH4Vu)を募集しています！)