redisのmaster-slave構成で考えるべきことの話
結論
- Redisは2.6を使おう
- master-slave構成を取る場合はclient-output-buffer-limitをちゃんと意識するべき
概要
redisはエッジトリガ型のnon-blocking I/Oを用いてシングルスレッドでソケットの読み書きをぶん回す構造で書かれています。 よってclientやslaveへ対してのreplyを行う際も、ソケット自体の送信バッファが溢れた際(EAGAINが帰った際)には一度イベントループに処理を戻し、またソケットが書き込み可能になってイベントループが自分を呼んでくれた時に続きをwriteします。 まあnon-blocking I/Oなんだから当たり前なんですが、送信処理を再入可能にするためにredisはアプリケーションレベルで出力バッファを持っています。
これは送信が終わり次第適宜解放されるものの、write自体が間に合わなくなると詰まって開放されなくなります。 これも当たり前で、書き込み自体が間に合わないなら諦めるか待ち続けるか後回しにするかしか無いよねっつー話です。
結局何が起こるかっつーと、masterから見てslaveのコネクションが高負荷なので応答不能になりかつコネクション自体が切断されない状況に陥った時(writeが間に合わなくなった時)、該当のコネクションへのレプリケーションの出力バッファは溜まり続けます。masterからのレプリケーションの出力バッファが溜まり続けるとどうなるかというと、(適切な設定をしていないと)当然メモリを無限に食い続けます。
よく考えたら当たり前なんですけど、まあそうなります。
フロントからのclient接続もまあおんなじ事情ではありますが、1コネクションで持続的にデータ送り続けかつ圧倒的なデータ量があるレプリケーション(あと使ってればpubsubもか)が一番これの被害を受けやすいと思います。
どうするか
client-output-buffer-limitという設定がRedis 2.6以降にはあり、slave/pubsub/client毎に出力バッファの最大サイズを定義してそれを超えたソケットをぶった切ることが出来るようになっています。使い方はググって下さい
client-output-buffer-limit自体は2.6を入れればデフォルトでredis.confには入ってます。逆に言うとそのまま使うとデフォルト設定のしきい値を超えて出力バッファが積まれると、いきなりスレーブはレプリケーションのコネクション切断されることになりやばいです。
というわけでまあ、Redisは2.6を使いつつ、こういう事情があることは意識はしときましょうという話でした。