レプリケーション — 影武者が書き写すのを待つか、待たないか
データベースを1台で動かしていると、その1台が落ちた瞬間にサービスが止まります。そこで同じ中身のコピーを別のサーバーにも持たせるのがレプリケーションです。王様(プライマリ)が出した命令を、影武者(レプリカ)がそのまま書き写す、というイメージです。
問題は「影武者が書き写し終わるのを待つかどうか」。待てば確実だが遅い。待たなければ速いが、影武者はしばらく古いままです。下の図1で、モードを切り替えながら書き込み直後にレプリカを読んでみてください。
非同期の代償 — 「自分が書いた値が見えない」
非同期レプリケーションでは、プライマリは自分が書き終わった時点ですぐ完了を返します。コピーの送信はそのあと、裏で進む。だから書き込みは速い。しかしその数ミリ秒〜数秒のあいだ、レプリカは古い世界を見せ続けます。これがレプリケーション遅延です。
実害が出るのは書いた直後に読む場面です。プロフィールを更新して画面が再読み込みされ、その読み取りがたまたまレプリカに振られると――更新前の名前が表示されます。ユーザーからすれば「保存できていない」としか見えません。図1で非同期のまま書き込み直後に読むと、まさにこれが起きます。
対策の定番は「自分の書き込みだけはプライマリから読む」ことです(read-your-writes)。更新直後の数秒間はその利用者の読み取りをプライマリに向ける。全体の負荷分散は保ちつつ、いちばん困る場面だけを避ける、という現実的な折衷です。
同期の代償 — 一番遅い1台に、全体が引きずられる
では同期にすればいいかというと、そう単純でもありません。同期レプリケーションの応答時間は「往復 × 2回分」になります。図1で遅延スライダーを右に振ると、書き込みが目に見えて重くなるのが分かります。レプリカを遠隔地に置くほど、書き込み全体が遅くなるのです。
もっと厄介なのは可用性です。「全員の書き込み完了を待つ」ということは、レプリカが1台でも応答しなくなったら、書き込みが通らなくなるということ。冗長化のために増やしたはずのレプリカが、逆に止まる理由を増やしてしまう。この落とし穴を避けるため、実務では「N台のうち1台が返せばOK」という準同期(semi-synchronous)を選ぶことがよくあります。
- プライマリ / レプリカ
- 書き込みを受け付ける正本と、その写しを持つ複製。読み取りはレプリカにも振れる。
- レプリケーション遅延
- プライマリの変更がレプリカに反映されるまでの時間差。非同期では避けられない。
- read-your-writes
- 自分が書いた内容は必ず自分には見える、という保証。更新直後だけプライマリを読ませて実現する。
- 準同期
- 全レプリカではなく、一定数の応答が返れば完了とする方式。速度と安全性の中間を取る。
- フェイルオーバー
- プライマリが落ちたとき、レプリカの1台を新しいプライマリに昇格させること。
まとめ
レプリケーションは「コピーを待つか、待たないか」の一択に集約されます。非同期は速いが、レプリカは一時的に古い。同期は古くならないが、遅く、しかも1台の不調が全体を止める。どちらが正しいかではなく、そのデータは何秒古くても許されるのかで決めるものです。残高や在庫は許されない、閲覧数やタイムラインは数秒古くても困らない――データごとに答えが違う、というのがこのテーマの本質です。