はじめに
この記事は朝日新聞社 Advent Calendar 2023の12日目の記事です。🎉
Redis一般の話をしますが、ここではAWS EalastiCache for Redisを例にお話しします。
一般的なElastiCache for Redisの構成
ElastiCacheをクラスターモードで運用する場合、writerインスタンスに対し5台までのreadインスタンスを配置することが一般的と思います。
Redis構成の課題
この構成の際、レプリカインスタンス間、プライマリとレプリカインスタンス間でそれぞれ負荷分散が行われることを想定すると思います。
しかし、実際には単純にget
コマンドを発行するだけだと、クラスターエンドポイントをアプリケーションで読み込んでも負荷分散は行われません。
readインスタンスへ送られるクエリは、全てwriteインスタンスにリダイレクトされます。
使用しているElastiCacheをAmazon Cloudwatch上でメトリクスを確認すると、GetTypeCmds
がprimaryの数しか存在していないことがわかります。
メトリクス上でも、読み取りのクエリは全てプライマリインスタンスに向けて発行されていそうです。
※GetTypeCmdsメトリクスは、クラスター内で発行された読み取り系コマンドの合計数を出すメトリクスです。
原因
Redisをクラスターモードで動作させる場合、クエリは全て各種シャードのプライマリに対して送られます。
そのため、レプリカに対してクエリが発行された場合はリダイレクトによってプライマリインスタンスに対して送られます。
解決策
Redisリファレンスのクラスターモードの解説にある通りですが、この挙動はREADONLY
コマンドを発行することで無効化することができます。
例えば、goアプリケーションでgo-redisでの実装の場合、cluster.go
のClusterOptions
構造体にReadonly
のプロパティがあるのでこちらを有効にしましょう。
client := redis.NewClusterClient(&redis.ClusterOptions{
Addrs: []string{addr},
ReadOnly: true, // READONLYコマンドの発行
})
なお、余談ですが、go-redisにはRouteByLatency
とRouteRandomly
のオプションが用意されていて、レプリカの優先度をレイテンシ優先かランダムアクセスかで制御できます。
client := redis.NewClusterClient(&redis.ClusterOptions{
Addrs: []string{addr},
ReadOnly: true, // READONLYコマンドの発行
// RouteByLatency: true, // READONLYコマンド発行時、レイテンシ低いレプリカにアクセス
// RouteRandomly: true, // READONLYコマンド発行時、ランダムにレプリカにアクセス
})
まとめ
Redisのドキュメントを読めば書いてあることではありますが、携わるプロダクトでRedisの負荷分散ができていないものがあったので、気になって調べました。
この案内はAWSのre:Postにも投稿されていましたが、私は調査して初めて知りました。
https://repost.aws/ja/knowledge-center/elasticache-redis-client-readonly
クラスターモードを有効にする際、レプリカで負荷分散したい場合はREADONLY
を有効にしてみてください。
参考
おまけ
本項ではレプリカにも読み取りクエリを発行することでプライマリとの負荷分散を意図するものですが、逆にプライマリにしか意図的にクエリを発行させないコマンド(既存のREADONELYを無効化するもの)として、READWRITE
コマンドも存在します。