production環境で41個のRedis Clusterを4年間運用してみて

本記事はドワンゴアドベントカレンダーの12月11日分です。
https://qiita.com/advent-calendar/2018/dwango

一昨年・昨年ともにRedis Clusterの運用の所感について投稿したので、今年もそのような感じです。
[プロダクションで2年間Redis Clusterを運用してみて \- Qiita](https://qiita.com/maruloop/items/18932dfc4be8778434d2)
[プロダクションでRedis Clusterを3年間運用し続けた所感 \- Qiita](https://qiita.com/maruloop/items/a67b62d2a6a239c8fb67)

(タイトル補足：4年間、41個を運用し続けてるのではなく、4年間運用していたら41個になっていたという意)

# TL;DR
- development環境で61個、production環境で41個のClusterを運用しています
- 安定性を求めたら、Redis Diskless Architectureにたどり着きました
- すべてRedis 3.2系です
- Redis3系から3.2系への移行を30 Clustersほど実施しましたが、少しハマりました
- Redis 5系への移行を計画しています(4系を飛ばします)

# ドワンゴでのRedis利用について
2018年に、ドワンゴのRedisを取り巻く環境・アーキテクチャも大きく代わりましたので、時系列で簡単にご紹介します。
#### (2011年頃～)各サービスが独自に利用
各サービスで独自にRedisを利用し始めました。
当時、ニコニコ生放送での利用について、特集が組まれていたようです(当時の詳細は知りません)
[ニコニコ生放送に見る Redis 活用ノウハウ：特集｜gihyo\.jp … 技術評論社](http://gihyo.jp/dev/feature/01/redis)

#### (2015年頃～)Redisを社内サービスに提供する基盤開始
メモリ増々に積んだサーバー5台を利用して、redis(on docker)をプロビジョニングし
社内サービスにRedis Clusterを提供する基盤サービスが作られました。

利用サービスは、3営業日以内に最大100GBメモリのRedis Clusterを利用出来る状態となりました。

#### (2018年～)Redisを社内サービスに提供する基盤その2開始
オンプレミスのインフラ改革が進んだ結果、今までベアメタルマシン上に直dockerで管理していた上記の基盤を、新インフラに移すことになりました。
その際、ただ移行するのではなく新インフラに合わせて、いくつかのアーキテクチャ変更を行い、新しい基盤サービスとしてリリースしました。

今、この新しい基盤サービスでは、毎月100 Redisが増えており(6ヶ月連続)、
development環境で61 Cluster, production環境で41 Clusterが稼働しています。

なお、2015年頃に作られた基盤サービスのRedis Clusterはすべてオンラインで、新しい基盤サービスへ移行をし、古い基盤サービスは役目を終えました。

# 新しいRedis Cluster基盤の設計について
[プロダクションで2年間Redis Clusterを運用してみて](https://qiita.com/maruloop/items/18932dfc4be8778434d2)でも記載した通り、
Redisの運用にあたって、一番頭の痛い問題はDisk I/O負荷となります。

Redisには、追記型ファイル永続化のAOFとフルダンプ型ファイル永続化のRDBがあります※。
※Redis4からは、RDB-AOF mixedとなっているようです

|種類 |特徴 |
|---|---|
|AOF  |追記型ファイル永続化。<br>更新系クエリを追記していき、ある閾値を超えたらコンパクションを行う。<br>コンパクションの瞬間のDisk I/O負荷が高い。<br>更新系クエリをほぼリアルタイムで追記するため、データロストは少ない。  |
|RDB  |フルダンプ型ファイル永続化。<br>ある一定期間に閾値以上の更新クエリが来たら、メモリフルダンプをファイルに取る。<br>一度のDisk I/Oはそこそこ大きいが、頻度はAOFに比較すると少ない。<br>そのため、ダウン時にデータロストはある程度発生。  |

AOFのほうがデータロストは少ないですが、コンパクション負荷が高く、Redisへのパフォーマンスインパクトが無視出来ません。
RDBのほうはコンパクション負荷に比較するとDiskに優しいですが、それでも定期的なフルダンプはなかなか厳しいです。

**サービスの安定性とデータ堅牢性、コストを天秤にかけた結果、ファイル永続化をしないアーキテクチャとすることにしました。**

### Redis Diskless Architectureについて
RedisをDisklessで運用する場合、以下の4点に気をつけなければなりません。

1. ファイル永続化設定(RDB,AOF)を無効化する
2. slave追加時などの完全同期の際、RDBを利用した同期を無効化する
3. masterとslaveが同時に落ちることを避けるため、常に別の物理マシンに配置されるようにする
4. masterのRedisプロセスが再起動した時、slaveに空のデータが伝搬しないようにする


#### ファイル永続化設定(RDB,AOF)を無効化する
redis.confにて、以下の設定を変更するだけです。

```
(省略)
# https://github.com/antirez/redis/blob/3.2.12/redis.conf#L202-L204を無効にする
save ""
(省略)
# https://github.com/antirez/redis/blob/3.2.12/redis.conf#L593をnoにする(デフォルト)
appendonly no
(省略)
```

#### slave追加時などの完全同期の際、RDBを利用した同期を無効化する
新規にslaveを追加した時や、障害時のfailover発生時など、完全同期が走るタイミングでは
一度masterではRDBファイルが出力され、それをslaveで再生した後、レプリケーションが組まれます。
この時のRDBファイルの出力を抑え、すべてメモリ上で実施するオプションを有効にします。

```
# https://github.com/antirez/redis/blob/3.2.12/redis.conf#L332 をyesにする
repl-diskless-sync yes
```

#### masterとslaveが同時に落ちることを避けるため、常に別の物理マシンに配置されるようにする
ファイル永続化はせず、メモリ上で保持するのみですので、masterとslaveの同時ダウンによってデータロストしてしまいます。
それを少しでも避けるため、ホスティングするインフラ環境のアンチアフィニティ的なルールor運用に任せて、
masterとslaveが同じマシンに同居しないようにしています。

#### masterのRedisプロセスが再起動した時、slaveに空のデータが伝搬しないようにする
masterプロセスが瞬間的に再起動し、それが検知されずfailoverが走らなかった場合
ファイル永続化がされていないため空のmasterが出来上がり、それがslaveに伝搬して、全データロストに繋がります。

それを回避するために、
Redisプロセスがダウンした時、プロセスを即座に自動起動するようなものはすべて無効化にしました。

また、systemdなどでstop/restartをする場合には、直前にsaveコマンドでファイルダンプを吐くようにし、オペレーションミスもケアしています。


# 古い基盤サービスから新しい基盤サービスへ移行する
上記のようにDiskless Architectureを採用した新しい基盤サービスですが、古い基盤サービスを巻き取る必要もありました。

移行スピードが求められたため、データ移行も含めてオンラインで移行出来ることを要件としました。
古い基盤サービスはRedis3系であり、Redisは3系と4系を混ぜてClusterを組めないことから、新しい基盤サービスでもRedis 3.2系を採用することとしました。

これにより、新しい基盤サービスと古い基盤サービスを跨って、一つの大きなRedis Clusterを作り、フェイルオーバーを駆使して移行が出来るようになりました。

#### 移行時に発生した問題
Redis3系と3.2系は、Cluster用のProtocol自体は同じため、大きなClusterを組んで移行することは出来たのですが
RDBファイルのフォーマットバージョンが異なっていました。
参考：https://github.com/antirez/redis/blob/3.2.12/00-RELEASENOTES#L2210

そのため、レプリケーションを組む段階で問題が発生しました。

:o: 3系→3.2系のレプリケーション
:x: 3.2系→3系のレプリケーション

つまり、3.2系にバージョンを上げて問題が起きた場合、オンラインでの巻き戻しが出来ないことを意味します。
が、Redis3系と3.2系の差異は些細なものが多いので、強行しました。(無事成功しました)
※最悪、リシャードによるスロット移動で3系へ戻るプランもありました(時間はかかりますが、オンラインで出来ます)


# 次にやること
10月にRedis5がGAとなったため、Redis 5化を進める予定です。
[Redis 5\.0 Update解説 \- Qiita](https://qiita.com/maruloop/items/207654bfd0ed2a453300)

Redis4と比較しても、以下のような魅力があります。

- stream型が追加された
- cluster操作コマンドがrubyではなくredis-cliに取り込まれた
- いくつかの性能面での向上があった(redis4にもありましたが)

Redis3→Redis4に行くのも、Redis3→Redis5に行くのも、
どちらにせよオンラインでデータ移行は出来ないため、大して大変さは変わらないはずなので、
Redis4を飛ばして、Redis5にアップデート予定です。

なお、Redis6は、Redisのプロトコル自体が変わるそうなので、少しの間様子見を予定しています。
(クライアントライブラリの対応を見ながら進める予定です)

<blockquote class="twitter-tweet" data-lang="ja"><p lang="en" dir="ltr">Just blogged &quot;Why RESP3 will be the only protocol supported by Redis 6&quot; =&gt; <a href="https://t.co/LxkdWplRTR">https://t.co/LxkdWplRTR</a></p>&mdash; ANTIREZ (@antirez) <a href="https://twitter.com/antirez/status/1060920361104093184?ref_src=twsrc%5Etfw">2018年11月9日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# さいごに
このようなRedisの基盤サービスなどを一緒に構築・管理するエンジニアを募集しています！
[【niconico】データストア系ミドルウェア リードエンジニア（正社員）｜キャリア採用｜ドワンゴ採用情報サイト](https://progres05.jposting.net/pgdwango/u/job.phtml?job_code=1545&_type=&business=&job_field=001)