コンピューターサイエンス:アルゴリズム / OS / ネットワーク / DB
レプリケーション
重いのは読みか、書きか
DB が悲鳴を上げているとき、最初に分けるのはここです。どちらかで打ち手がまったく変わります。
一覧、検索、詳細表示のような読み取りが大半を占めているなら、同じ内容のコピーを何台か用意して、読み取りだけをそちらへ回せば台数ぶん楽になります。これがレプリケーションです。書き込みを受け付ける 1 台を Primary、コピー側を Replica と呼びます。
注意すべきは、これで軽くなるのが読み取りだけだということです。書き込みは相変わらず Primary 1 台に集まります。Replica を 10 台に増やしても、書き込みの限界は 1 ミリも動きません。
測り方は難しくありません。単位時間あたりの SELECT の本数と、INSERT や UPDATE の本数を比べるだけです。一般的な Web サービスは読みが 9 割を超えることが多く、その場合はコピーを増やす効果が素直に出ます。逆に、計測値やログの蓄積が負荷の中心なら、読みをいくら分散させても何も変わりません。
コピーは必ず、少し遅れて届く
Primary の変更は、変更内容のログとして Replica へ送られ、向こう側で適用されます。送って適用するまでに時間がかかるので、コピーは常に少し過去です。
プレーンテキスト
Primary INSERT INTO users ... ('Alice') -- 成功
Replica SELECT * FROM users WHERE ... -- 0 件会員登録の直後にマイページへ飛ばしたら「ユーザーが見つかりません」と出る、という形で表に出ます。バグに見えますが、読み書きを分けた時点で必ず生まれる性質です。
対策は、遅れをなくすことではなく、遅れても困らない経路を作ることです。書き込んだ直後の読み取りだけ Primary に向ける、というのがいちばん素直な形です。
JavaScript
const rows = justWrote
? await primary.query("SELECT ...")
: await replica.query("SELECT ...");COMMIT を返す前に、どこまで待つか
遅れをどこまで許すかは、Primary が COMMIT を返すタイミングで決まります。
送っただけで返すなら、Primary の速度はほぼそのままですが、Primary が壊れた瞬間に、まだ届いていなかったぶんが失われます。Replica が受け取ったのを確認してから返すなら、失われるぶんは減りますが、その往復のぶん毎回遅くなります。全台の反映を待つなら、1 台の不調がそのまま全体の停止になります。
既定はたいてい、送っただけで返す形です。速い代わりに、Primary の故障時に直近の数件が消えることを受け入れています。決済のように 1 件も消せない処理があるなら、その系統だけでも確認を待つ設定にできないかを考えます。Primary が落ちた後は Replica の 1 台を昇格させて再開しますが、切り替えには数秒から数分かかり、その間の書き込みは止まります。
レプリカの遅れは「ある」前提で設計する。「ない」前提のコードが事故になる。