GAE/pyでcloud sqlに繋ぎに行っていたんですが、
データ参照をread replicaに分散させないと高負荷に耐えられないケースが稀にあるので。
Cloud Spannerを使うだけの予算があったり、
上手くDataStoreで実装できればそれで済むこともあるんですが……
キャッシュを使っていてもなおスペックが足りなかったりと、世の中はままならないので。
DBを参照する際にread replicaなサーバにランダムで繋ぎに行った時の問題と解決方法の備忘録でも。
もっといい方法があるよ!という方がいれば是非とも。
Django公式の説明
この通り複数台構成の場合の説明はあります。
db_for_readで参照先を、db_for_writeで書き込み先を指定してあげればいいだけのお手軽設定です。
さすがに書き込み先を分散させる……ということは早々ないはずなので、
下のような感じで、db_for_readで接続先をランダムに指定してあげれば、
一つのDBサーバに接続が偏らずに、無事にスケールできそうな予感がします!
def db_for_read(self, model, **hints):
random.choice(settings.DATABASES.keys())
実装の問題点
- selectが走る度にrandom.choiceで接続先がランダムで選択される
- 一度のリクエストでN台のDBサーバへのコネクションが発生してしまった!
- スケールどころではなく一瞬で死亡!
- トランザクション中でも、read replicaを参照してしまう!
- begin -> update -> select -> あれ……!?
解決方法
- 一度ランダムで選択したものを、保持するようにする
- db_for_write....つまり、デフォルトの接続先がトランザクション実行中であれば、参照先をそこに切り替えるようにする
- haproxyを使えない環境なので、read replicaが落ちている場合は自動で他のreplicaに切り替えられるようにpython側でどうにかする
実装方法
という訳で下の内容で実装してみました。
db_for_readの中でトランザクションの実施判定、一度設定した接続先を記憶するようにしています。
from django.db import connections
from django.db.transaction import DEFAULT_DB_ALIAS
class FailOverRouter(object):
def __init__(self):
self.default = DEFAULT_DB_ALIAS
self.read_connection = None
def is_transaction(self):
has_connection = hasattr(connections._connections, self.default)
if has_connection:
return connections[self.default].in_atomic_block
return False
def db_for_read(self, model, **hints):
if self.is_transaction():
return self.default
if self.read_connection is None:
self.read_connection = self.default
# read replicaの一覧をshuffleしたものを取得
read_instance_names = get_read_instances()
for read_name in read_instance_names:
# 接続先がダウンしていないかどうかの確認
if test_connection_to_db(read_name):
self.read_connection = read_name
break
return self.read_connection
def db_for_write(self, model, **hints):
return self.default
def allow_migrate(self, db, app_label, **hints):
return db == self.default
実装してみて
これを利用して、高負荷サービスでも(read replicaが耐えられる範囲までは)問題なくさばけるようになりました。
データベースを追加すれば追加するほど、無限にさばける人数を増やせるようになったかなと。
まぁ、増やせば増やすほどコストも無限にスケールするんですが……
最後に
株式会社ネコカリでは高い負荷を求められるサービスでも、求められないサービスでも、
猫の手をかせるお仕事を合わせて募集しています!
一緒に働くメンバーも募集していますので、よかったら是非!