こんにちは!株式会社GROWTH VERSEに所属しております川田剣士です。最近分散システムに関してしっかり理解したいなと思い、個人的に学習し技術記事で体系的にまとめようと思いました。現代のWebアプリケーションは、膨大なユーザー数やデータ量を処理することが求められています。また、モノリシックな構成では処理能力やスケーラビリティに限界があり、可用性や耐障害性の観点からも課題が顕在化しやすくなります。こうした背景の中で、分散システムの導入は、スケーラブルかつ高可用なシステムを構築する上で欠かせないアプローチとなっています。そのため、分散システムについてしっかり理解することはシステム設計をする上で重要かなと思います。本記事を読むことで分散システムの基礎であるCAP定理・シャーディング・レプリケーション・マスタースレーブなどについて理解していただけることを目的としています。記事に対する質問・コメント大歓迎です!
目次
ノード・クラスター・CAP定理とは
分散システムを理解するためには、まず「ノード」「クラスター」「CAP定理」という3つの基本概念を押さえる必要があります。これらは、分散データベースやスケーラブルなシステムを設計する上での前提となる知識です。
Node(ノード)
ノードとは、分散システムを構成する個々のサーバーやプロセスのことを指します。
1つのノードはCPU・メモリ・ストレージを持つ独立したコンピュータであり、クライアントから見ればシステムの一部を構成する単位です。
例:1台のデータベースサーバー、Kubernetesクラスターにおける1つのワーカーノードなど
Cluster(クラスター)
クラスターは、複数のノードが協調して1つのシステムとして動作する論理的なグループです。
クラスターは可用性(障害時の自動切り替え)やスケーラビリティ(負荷分散)を実現するために用いられます。
クラスター内のノードはネットワークで接続され、協調動作します。
通常、1つの最小単位のネットワーク障害ドメイン(例:AZやデータセンター)内に構築されるが、マルチリージョン構成(リージョンごとにクラスターを置く構成のこと)も可能です。
CAP theorem(CAP定理)
分散システムの設計を考える際に避けて通れないのがCAP定理です。
CAP定理は、ネットワーク分断が起きる環境では、以下の3つを同時に満たすことはできないとする理論です。
- Consistency(一貫性):すべてのノードが同じデータを保持しており、どこにアクセスしても最新の正しいデータが得られる状態
- Availability(可用性):どのノードにリクエストしても、必ず応答が返る状態
- Partition Tolerance(分断耐性):ネットワーク障害によって一部のノード間が通信できなくなっても、システムが動作を続ける性質
上の画像が分散システムの通信のイメージです。複数ノード間で通信をして機能を実現しています。Partition Toleranceに関して、ネットワーク通信障害が起きないことは現実的にあり得ないのでPartition Toleranceは必ず分散システムの性質で使用する必要があります。Partition Toleranceを使用しないようなシステムは何かと考えたときに、自分は単一ノードの構成のみであると思いました。単一ノードの場合だと、ノード間で通信が発生しないのでPartition Toleranceを使用しないこととなり、かつConsistencyとAvailabilityを両方満たせることになります。つまり、Partition Toleranceを採用しないということは分散システムの概念から外れてしまうため、必ずPartition Toleranceの性質は必要になるということです。
CP型:一貫性重視 (Consistency + Partition tolerance)
CP型のシステムは、ネットワーク分断(Partition)が発生した際に「可用性」を犠牲にしてでも、一貫性(Consistency)を守ることを優先するアーキテクチャです。
挙動
ネットワーク障害でクラスタ内のノードが分断されると、少数派ノード(マイノリティパーティション)では書き込みや一部の操作を拒否します。多数派ノード(マジョリティパーティション)だけが処理を継続し、整合性を保った状態を維持します。CP型の書き込み処理の成功はノード数の過半数以上が処理を成功した場合に終了します。残りの分に関しては非同期処理でレプリケーションされます。書き込み処理や読み込み処理をする際、必ずリーダーという代表ノードを経由することで、常に一貫した状態を実現しています。
メリット
どのタイミングでアクセスしても、常に整合性の取れたデータが得られる。
デメリット
ネットワーク分断時やノード障害時、可用性が低下(操作拒否や一時的なサービス停止が起きる)。
高頻度書き込みや低レイテンシが求められるユースケースには不向き。
ユースケース
在庫管理システム:あるノードでは商品Aの在庫切れ(この結果を正とする)である一方、別のノードでは商品Aが在庫切れでないといったケースが発生すると、商品Aの商品を購入できてしまい、バグの発生につながる。そのため、在庫管理システムのような一貫性が強く求められる場合はCP型のシステムを使用するのが望ましい。
AP型:可用性重視 (Availability + Partition tolerance)
AP型のシステムは、ネットワーク分断時に「一貫性」を一時的に犠牲にしても可用性を維持します。つまり、全ノードが応答し続けることを最優先します。
挙動
ネットワーク分断時、どのパーティションでもリクエストを受け付け続ける。
結果として、一時的に古いデータを返す可能性があるが、時間が経つと最終的整合性 (Eventual Consistency) に収束する。
メリット
ノード障害やリージョン障害が発生しても、サービス全体が止まらない。高いスループットと水平スケーラビリティを実現可能。
デメリット
ネットワーク分断やレプリカ間の同期遅延により、一時的なデータ不一致が発生する。
強い一貫性が求められる金融システムなどには不向き。
ユースケース
ニュースアプリ: 一時的に技術記事の更新をした際に更新がまだ完了していないノードにアクセスしても大きな問題にならない。
グローバル分散システム(DynamoDB): 世界中の複数リージョンに分散配置し、可用性重視で運用。
分散システムの一貫性レベル
分散システムの代表的な一貫性レベルは以下の4種類あります。
-
強整合性 (Strong Consistency):すべての読み込みは常に最新の書き込み結果を反映する。書き込みが完了した直後の読み込みでも、必ず最新の値が取得できる。
挙動例- ユーザーAがデータX=10に更新
- 直後にユーザーBがどのノードにアクセスしてもX=10が取得可能
メリット
- 常にデータの正しさを保証できるため、アプリケーション実装が単純
デメリット
- 書き込みの承認に時間がかかる(複数ノードへの同期が必要)
- ネットワーク分断時に可用性が低下
ユースケース
- 金融システム、Kubernetesのメタデータ管理(etcd/Zookeeper)
- データの完全な正確性が必須な場面
-
読み取り自身の書き込み整合性 (Read-your-writes Consistency):自分が直前に書き込んだデータは、その後の読み込みで必ず反映されるが、他ユーザーの書き込みは即座に見えない場合がある。
挙動例- ユーザーAがX=10に更新 → 直後にAが読み込むと必ずX=10
- しかしユーザーBが同時に読み込むと、旧値X=5を取得する可能性あり
メリット
- 自分の操作結果が即座に確認できるため、ユーザー体験が安定する
デメリット
- 他ユーザー間では整合性が遅れる可能性あり
ユースケース
- SNS投稿(自分の投稿がすぐに見えるが、他人のクライアントでは反映が遅れる)
- ECサイトの「注文確定画面」などユーザー単位の操作確認
-
因果整合性 (Causal Consistency):因果関係のある操作(A→B)では順序を守るが、因果関係のない操作は順序保証しない
挙動例- ユーザーAがコメントを投稿 → ユーザーBがそれに返信
- 他のユーザーは必ずAのコメント→Bの返信の順に見える
- しかし、無関係な別の投稿Cは順序保証なし
メリット
- ユーザー体験として自然な順序(会話の流れなど)を保証できる
デメリット
- 強整合性ほど厳密ではないため、因果関係がないデータの順序は保証されない
ユースケース
- SNSやチャット(会話の前後関係は崩さないが、無関係なスレッドは順不同でも可)
- 協調編集(Google Docs など)
-
最終的整合性 (Eventual Consistency):すべてのノードは時間が経てば最終的に同じ状態になるが、短時間はデータ不一致が発生する
挙動例- ユーザーAがノード1にX=10を書き込み
- ユーザーBが直後にノード2にアクセス → 旧値X=5を取得
- 時間が経過してレプリケーションが完了すると、全ノードでX=10に収束
メリット
- 高可用性・低レイテンシを実現しやすい
デメリット - 読み込みタイミングによって古いデータが返る可能性あり
ユースケース
- AP型DB(Cassandra, DynamoDB)、キャッシュシステム(CDN)
- 高可用性重視のWebサービス、IoTデータ
CP型は強整合性(Strong Consistency)のみ、AP型は最終的整合性 (Eventual Consistency) を基本とし、必要に応じて「強めの一貫性」へ調整可能な仕組みを提供することがある。(最終的整合性を少しずつ強整合性に近づける方向への調整であり、Read-your-writesやCausal Consistencyといった整合性モデルそのものを直接的に切り替えるわけではない。)
例:CassandraのConsistency Level
- ONE:1ノードのみ確認(最終的整合性寄り)
- QUORUM:過半数ノードで確認(整合性を強める)
- ALL:全ノード確認(強整合性に近いが、可用性は低下)
シャーディングとは
シャーディングとは、データベースやストレージを複数のサーバー(ノード)に分割して配置し、処理負荷を分散させる手法です。1台のサーバーで処理・保存できるデータ量やトラフィックの限界を超えた際に用いられる、代表的なスケーリング手法です。
方式
- 水平分割 (Horizontal Sharding)
- 行単位で分割し、異なるサーバーに格納する方式
- 例:ユーザーIDに基づいて分割。ID 1〜1000 → Shard 1。ID 1001〜2000 → Shard 2
- 主に大規模ユーザーデータやトランザクション系データに有効
- 垂直分割 (Vertical Sharding)
- テーブルや機能ごとに分割し、それぞれを異なるサーバーに配置
- 例:ユーザープロフィールはDB A、決済情報はDB B
- マイクロサービス化と相性が良く、機能単位でスケールできる
利点
- スケーラビリティ向上:データ量やアクセス負荷を複数ノードに分散し、1台あたりの負荷を軽減できる
- 可用性の向上:1シャードの障害が全体に波及しにくい構造になる
課題
- シャード間クエリの複雑化:複数シャードに跨る集計やJOINが困難。アプリケーション側で制御が必要
- データ再分割(リシャーディング)の難しさ:データ量増加やアクセス偏りがあると、シャード再配置が必要になる
- 一貫性管理の複雑化:トランザクションをシャード間でまたぐ場合、分散トランザクションとなるため、分散トランザクションで整合性を保つために2PC(2 Phase Commit)やSaga Patternなどが必要。2PCの採用であれば、ミドルウェアが対応しているため簡単に実装できるが、非同期処理でのロールバックによる可用性やロック防止によるパフォーマンス向上などを目指す場合はSaga Patternを採用する必要がある。Saga Patternに関しては、Saga Patternに関してGolangでまとめてみた!を執筆したため、ぜひご覧ください。
代表例
- MySQL シャーディング:アプリケーション層でシャード分けを制御(例:ユーザーIDによる手動ルーティング)
- MongoDB シャーディング:MongoDBは組み込みで自動シャーディング機能を提供している
手動ルーティングの場合のイメージコード
// Golangの例:ユーザーIDからシャードを決定
func GetShard(userID int) *sql.DB {
if userID >= 1 && userID <= 1000 {
return dbShard1 // Shard 1 のDB接続
} else if userID >= 1001 && userID <= 2000 {
return dbShard2 // Shard 2 のDB接続
} else {
return dbShard3 // Shard 3 のDB接続
}
}
// クエリ実行例
func GetUser(userID int) User {
db := GetShard(userID)
var user User
db.QueryRow("SELECT * FROM users WHERE id = ?", userID).Scan(&user.ID, &user.Name)
return user
}
レプリケーションとは
レプリケーションとは、同一データの複数コピーを複数ノードに保持し、可用性や読み取り性能を高める仕組みです。
障害時の冗長化や、読み取り負荷分散のために、分散システムやデータベースで広く用いられています。
同期レプリケーション (Synchronous Replication)
書き込みが行われると、全てのレプリカに反映完了してからクライアントに成功応答を返す。
特徴
- データの強整合性を保証
- 書き込み遅延が大きい
ユースケース
- 金融システムや、強い整合性が求められるデータベース
非同期レプリケーション (Asynchronous Replication)
書き込みを一部のノードに反映し、クライアントに成功応答後、バックグラウンドで他ノードに複製。
特徴
- 可用性と書き込み性能が高い
- 一時的なデータ不一致(レプリカラグ)が発生する
ユースケース
- Webサービス、ログ管理、AP型データベース
利点
- 可用性向上:1台のノードが障害を起こしても、他のノードからサービス提供を継続可能
- 読み取り負荷分散:読み取りリクエストを複数ノードに分散することで、スケーラブルな読み取り性能を実現
課題
- 書き込み整合性の問題:非同期レプリケーションでは、書き込み直後に他ノードで古いデータが返る「レプリカラグ」が発生
代表例
- MySQL レプリケーション:プライマリ-レプリカ構成(マスタースレーブ方式)で非同期レプリケーションを実現。読み取りはスレーブに振り分けて負荷分散
- Cassandra のマルチマスター:全ノードが対等なマルチマスター型で、書き込みがどのノードからも可能。非同期レプリケーションで「最終的整合性」を実現
マスタースレーブ構成とは
マスタースレーブ構成とは、1台のマスターサーバーが書き込み専用として動作し、複数のスレーブサーバーが読み取り専用として動作するデータベース構成です。データはマスターからスレーブへレプリケーションによって同期されます。
構成
- Master(マスター):書き込み専用(INSERT、UPDATE、DELETE)。すべての更新系操作がここに集約される
- Slave(スレーブ):読み取り専用(SELECT)。マスターから非同期レプリケーションでデータを受け取り、読み取り専用のリクエストに対応
用途
- 読み取り負荷分散:読み込み専用クエリを複数スレーブに分散し、マスターの負荷を軽減
- 冗長化による可用性向上:マスターが障害を起こした場合、スレーブを昇格させてサービスを継続可能
- バックアップ用途:スレーブを利用してバックアップや分析を行うことで、マスターへの影響を回避
課題
- スレーブの遅延(レプリカラグ):非同期レプリケーションでは、スレーブへのデータ反映に遅延が発生する。書き込み直後にスレーブへ読み込みリクエストを送ると、古いデータが返る可能性あり
代表例
- MySQL マスタースレーブ構成:MySQLの標準的なレプリケーション機能を利用し、1台のマスターに複数のスレーブを接続する
- PostgreSQL Streaming Replication:同様に、マスターからスレーブへのレプリケーションで冗長化・負荷分散を実現
終わりに
最後まで読んでいただき、ありがとうございます!
