【データ指向アプリケーション要約】第5章 レプリケーション
『データ指向アプリケーションデザイン』第5章は、**「レプリケーション(複製)によってどうやって高可用性やスケールを実現するか」**がテーマになっている。
データベースのレプリケーションは、一見シンプルに見える(「コピーを増やしておけばOKやろ?」)けど、実は障害やネットワークの遅延、同時書き込みなどいろんな問題があって奥が深いらしい。
ここでは、「5章 レプリケーション」の内容を要点を抑えながら簡潔にまとめていく。
1. なぜレプリケーションが必要?
1.1 高可用性 (High Availability)
- 1台のマシンが落ちても、他のマシンにデータのコピーがあれば、サービスを継続できる
- 大規模サービスでは、物理マシンやネットワークの障害は**“いつか必ず起きる”**ものなので、「1台が落ちても困らない」構造は必須
1.2 読み取り負荷の分散
- 同じデータを複数のレプリカに持つことで、読み取りのリクエストをいろんなノードに振り分けられる
- 書き込みは難しいけど、読み取りなら複数ノードに分散可能 → スケールアウトになる
1.3 地理的配置・レイテンシ低減
- レプリカを世界中のリージョンに配置すれば、ユーザーに物理的に近いサーバーからデータを返せるので、レイテンシを下げられる
- ただし、遠距離間での通信遅延や同期の問題をどう扱うかが課題
2. レプリケーションのアプローチ3種類
2.1 シングルリーダーレプリケーション
-
仕組み
- 1つのリーダーレプリカ が書き込みをすべて受け付ける
- リーダーが更新をコミットすると、「更新ログ」をフォロワーレプリカ に配信
- 読み取り要求は、リーダーまたはフォロワーに送れる(フォロワーは最新版を必ずしも持っていないかもしれない)
-
メリット
- 設計がシンプル: 書き込みは常に1つのノードが担当するため、同じデータを同時に更新したときの衝突処理が基本的に不要
- 多くのRDBやNoSQLでもこの方式を採用していて、運用ノウハウが豊富。
-
デメリット
- リーダーがボトルネック: 書き込みは1台に集中するため、更新量が増えるとリーダーが耐えられなくなる可能性がある
- リーダー障害時のフェイルオーバー: リーダーが落ちたらフォロワーのうち誰かを新リーダーに昇格させるが、そのときに最新データが反映されてないフォロワーを昇格させるとデータが失われる恐れ(非同期レプリケーションの場合)
2.2 マルチリーダーレプリケーション
-
仕組み
- 複数のリーダー を用意して、それぞれが書き込みを受け付ける
- リーダー同士で更新内容を相互に共有し、さらにフォロワーへも伝播させる
-
メリット
- どのリーダーでも書き込みOK なので、地理的に分散した拠点ごとにローカルリーダーを置くことができる(ネットワーク遅延を低減)
- リーダーの1台が落ちても、別のリーダーが書き込みを続けられるので、可用性が高い
-
デメリット
- 同じデータを同時に更新する衝突が起きやすい: 例えばユーザーID=123のレコードをAリーダーとBリーダーがほぼ同時に更新したら、どうする?
- 衝突解決ルールを決めなきゃいけないので、アプリケーションロジックが複雑になりがち
- 一貫性の制御がシングルリーダーより難しい
2.3 リーダーレスレプリケーション
-
仕組み
- 明確なリーダーを置かず、書き込み要求を複数ノードに並列送信
- 読み取りも複数ノードから取得して「最新のデータはどれ?」と突合する。(いわゆる N, R, W の設定: 全ノード数N、書き込み成功に必要な数W、読み取りに必要な数R など)
-
メリット
- どのノードが落ちても、一定数のノードが応答すればOK という可用性の高さ
- 書き込み・読み取り両方を分散できるので、スケーラビリティ面でも強力
-
デメリット
- いつ最新状態になるのかが不定: 書き込みがあるノードと、読み取りに応答したノードがずれていると古いデータが返る場合がある
- 衝突解決や、複数ノードに跨ったread repair(読み取り時に古いノードを修正)などの処理が複雑
- アプリケーションが最終的に整合することを前提に設計しなきゃいけない場合が多い
3. 同期と非同期の違い
- 同期レプリケーション: リーダー(または書き込み先)が「他レプリカへの書き込みが完了するまで待って」からクライアントに返す → 一貫性は高いが書き込みが遅くなる
- 非同期レプリケーション: リーダーが即コミットしてクライアントに返し、バックグラウンドでフォロワーに伝播 → 速いけど、障害時に最新データが失われるリスクがある
4. レプリケーションラグ
- 非同期レプリケーションだと、リーダーで書き込みが成功しても、フォロワーに反映されるまでタイムラグがある
- その間にフォロワーを読み取りに使うと、古いデータが返るかもしれない
5. 一貫性モデル
以下では、分散システムにおける代表的な「一貫性モデル」をまとめます。アプリケーションの要件や許容できる整合性レベルに応じて、どのモデルを採用するかを検討する
5.1 強い一貫性(Strong Consistency)
-
概要
書き込みが完了した直後の最新データが、全ノードで常に即座に反映されるという理想的なモデル。
“データベースが単一のマシンのように振る舞う”とも言われる。 -
メリット
- ユーザーは、いつ・どのノードにアクセスしても最新のデータを必ず取得できる。
- アプリケーション側で一貫性崩れへの対策をあまり考えずに済む。
-
デメリット
- 分散環境だと実現コストが高く、ネットワーク遅延・ノード障害時にはシステムが応答不能になりやすい(可用性が低下する)。
- CAP定理において、分散環境で強い一貫性(C)を重視すると可用性(A)の低下につながりやすい。
5.2 Read-after-write 一貫性(あるいは Read-your-own-writes Consistency)
-
概要
ユーザーが自分自身で書き込んだデータを、直後の読み取りで必ず反映させることを保証するモデル。 -
メリット
- ユーザー視点で「書いた内容がすぐに見える」という直感的な振る舞いを実現できる。
- SNSの投稿やブログ記事など、自分の操作が反映されないストレスを軽減。
-
デメリット
- 他ユーザーが行った書き込みについては、強い一貫性が保証されるわけではない(あくまで「自分の書き込み」が読める保証)。
5.3 モノトニックリード(Monotonic Reads)
-
概要
あるユーザーがデータを読み取る際、一度新しい状態を見たら、その後の読み取りで古い状態を再び見ることはないことを保証するモデル。 -
メリット
- 一度見た情報が時間的に逆行しないので、ユーザー体験が安定する(フィードなどで古い投稿に戻らない)。
- データが時間とともに単調に進む、という自然な操作感を保てる。
-
デメリット
- レプリケーションラグが存在する環境では、ユーザーのアクセス先によって時系列が前後する可能性があるため、実装に工夫が必要。
5.4 一貫性のあるプレフィックス読み取り(Consistent Prefix Reads)
-
概要
因果関係の順序を守りながらデータを読み取れることを保証するモデル。
たとえば、「質問 → その質問への回答」という依存関係を正しい順序で読むことができる。 -
メリット
- ログやメッセージの時系列を崩さずに利用したいケース(チャットのやり取り、イベントログなど)で重要。
- ユーザーが矛盾のない時系列でデータを把握できる。
-
デメリット
- 複数の書き込み間に因果関係が多いほど、その順序を保証するための仕組みやプロトコルが複雑化する。
5.5 最終的一貫性(Eventual Consistency)
-
概要
遅延があっても、いずれ(十分な時間が経過すれば)すべてのノードが同じ状態に収束することを保証するモデル。
“いつかは一致するが、いつ最新状態を返せるかは保証しない”という緩い整合性。 -
メリット
- 高い可用性(A)を確保しながらシステムを大きくスケールしやすい。
- ノードが部分的にオフラインでも、復旧後に同期を行うことで最終的に合致する。
-
デメリット
- 更新直後に他のノードから古いデータを返す可能性がある。
- 「最新データをいつ読めるか」が明確に保証されないため、アプリケーションが整合性崩れに対応するロジックを持つ必要がある。
5.6 因果一貫性(Causal Consistency)
-
概要
**因果関係のある操作(例: ある書き込みが別の読み取りに依存している場合)**のみを正しい順序で適用し、因果関係がない操作同士には順序を厳密に課さないモデル。
従来、「シーケンシャルに全操作の順序を保つ」ほど強くはないが、少なくとも因果関係があるものについては順序を崩さない。 -
メリット
- 完全なシリアライズは不要なので、強い一貫性よりは高可用性を得やすい。
- プレフィックス読み取りなどと組み合わせると、ユーザー間のやり取りなどで自然な動作を保ちやすい。
-
デメリット
- ネットワーク状況によっては、因果関係の伝播を追跡するメタデータ(バージョンなど)を持たせるための実装が複雑。
- 強い一貫性が必要な場面では不十分。
6. マルチリーダーやリーダーレスの衝突
6.1 衝突とは?
- 同じキー/レコードに対して、ほぼ同時に異なる更新が行われると、最終的に「どの値が正しいの?」という問題が起こる。
- シングルリーダーなら「リーダーでの更新」が常に正とできるけど、マルチリーダーやリーダーレスでは「どっちも正しい」となり、データが分岐する可能性あり。
6.2 衝突解決のパターン
-
1. 「最後に書かれた方を優先(Last Write Wins)」方式
- タイムスタンプやバージョン番号を使って、より新しい方を正として上書きする方法。
- メリット: 実装が簡単で早い。
- デメリット: 先に書いた重要な変更が雑に消される可能性がある。
-
2. 「アプリでマージする」方式
- 衝突したら、アプリケーションのロジックで両方の更新をうまく合体させる。
- 例えば、テキスト同士なら差分を取ってマージするとか、在庫数は「加算」して整合性を取る、とか。
- メリット: ドメインに沿った自然な解決ができる。重要なデータを損なわない。
- デメリット: マージの仕組みをアプリごとに作り込む必要があって、運用・開発が大変。
- 衝突したら、アプリケーションのロジックで両方の更新をうまく合体させる。
-
3. 「ユーザー(人間)に判断をゆだねる」方式
- 衝突が発生した場合、システムがユーザーに“どっちが正しい?”って選択させるフローにする。
- 例えば、バージョン管理システム(Gitみたいな)のコンフリクト解消とイメージが近い。
- メリット: 自動化しにくい複雑な衝突でも、人が見て最適解を出せる。
- デメリット: ユーザーの手間が増えるし、リアルタイム性が落ちる。
- 衝突が発生した場合、システムがユーザーに“どっちが正しい?”って選択させるフローにする。
7. まとめ
- レプリケーションはシステムの高可用性や読み取りスケールを実現するための重要な仕組み。
- しかし、障害やネットワークの遅延、一貫性のズレ、衝突処理など考慮すべきことが多く、簡単にはいかない。
- シングルリーダーはシンプルだが、書き込み負荷の増大やリーダー障害がボトルネック。
- マルチリーダーは地理的に分散しやすく可用性も高いが、衝突解決の複雑さを伴う。
- リーダーレスは可用性とスケーラビリティに優れるが、一貫性の保証が弱く、衝突解決をアプリ側で考える必要が大きい。
最終的には、アプリケーションの要件(書き込み頻度、許容レイテンシ、故障に対する耐性、衝突時の対応など)を踏まえて、
「どのレプリケーション方式を採用するか?」「同期/非同期どちらを選択するか?」「一貫性モデルはどうするか?」を決めるのが大事。
これらを踏まえて、「障害に強い&大規模アクセスに耐えられる&データを正しく保てる」システムをどう実現するか考えていくのが本物。
次章の要約はこちら
Discussion