07. 分散システム入門 - 「一貫性」の難しさ
読了時間: 50-70分(移動中に2-3回に分けて読むのが丁度よい) 想定読者: 「マイクロサービスとか流行ってるけど、何が難しいのかピンと来てない人」 ゴール: 「分散は簡単に考えるな」が腹の底から分かる。パターン名を10個以上覚える 手は動かさない: スマホで気楽にどうぞ
この記事で持って帰れること
- 分散システム7つの誤謬を踏まえて、ローカル呼び出しとリモート呼び出しの差を語れる
- CAP / PACELC、SAGA、Quorum、Raft、Vector Clock など主要パターン名を10個以上覚える
- Circuit Breaker / Retry with Jitter / Bulkhead という耐障害性三種の神器を理解する
- 「マイクロサービスにしましょう」に対して「組織サイズ的に必要?」と問い返せる
- 「分散しない」も立派な戦略だと理解できる (37signals, Segment の事例から)
前回 (06_データベース理論) との繋がり
前回扱った CAP 定理は、分散システム全般の議論の入口だった。今回はそれを データだけでなく「計算」「時刻」「合意」全体に拡張 する話。レプリケーション・シャーディングといった DB 側の話も再登場するが、より上位の視点で見直す。
大前提: なぜ「分散」が必要なのか
あなたが個人ブログを作るとする。アクセスは1日100人。サーバーは1台で十分。データベースもファイルシステムも全部同じマシンに乗っている。エラーが起きたら SSH して tail -f /var/log/myapp.log する。シンプルで美しい。
これが、もしあなたのサービスが急にバズって1日1000万アクセスになったらどうなるか。1台のサーバーでは捌けない。CPU が張り付き、メモリが溢れ、ディスクI/O が詰まり、応答時間が5秒、10秒と伸びていき、最後はタイムアウトしてサイトが落ちる。
選択肢は2つしかない。
- 垂直スケール (Scale Up): サーバーをでかいやつに買い替える。8コアを32コアに、メモリを32GBから512GBに
- 水平スケール (Scale Out): サーバーを増やす。1台を10台に、100台に、1000台に
垂直スケールは限界がある。世界最強の CPU でも処理能力には天井がある。しかも高い。32コアのサーバーは8コアの4倍の値段では済まない。だいたい10倍以上する。
そこで水平スケール、つまり「分散」が登場する。安いマシンを大量に並べて、処理を分担させる。Google も Amazon も Netflix も、世界を支えているシステムはすべて分散システムだ。
↓ 単一サーバーと分散システムの対比 ↓
flowchart LR subgraph 単一サーバー U1[ユーザー] --> S1[サーバー1台<br/>CPU/メモリ/DB] end subgraph 分散システム U2[ユーザー] --> LB[ロードバランサ] LB --> A1[App 1] LB --> A2[App 2] LB --> A3[App 3] A1 --> DB[(DB クラスタ)] A2 --> DB A3 --> DB end
ただ、ここで多くのエンジニアが甘く見るポイントがある。
「分散」は、1台でやっていたことを10台でやるだけ、ではない。まったく別の世界。
1台で動いていたコードを10台に複製しても、ほとんどの場合まともに動かない。データが食い違う、注文が重複する、ユーザーが「カートに入れた商品が消えた」と怒る、お金が二重に引き落とされる、最悪は不整合に気づかずに数日経って取り返しがつかなくなる。
なぜそうなるか。これからじっくり話す。
この記事の楽しみ方
分散システムは「人類が手こずってる現役のフロンティア」です。50年やってもまだ難しい。
だからこの記事を読んで「ふーん、これは難しいんだ」と分かれば十分。具体的なアルゴリズムを暗記する必要はない。
ただし、パターン名は覚えてほしい。「Circuit Breaker」「Idempotency」「SAGA」「Quorum」みたいな言葉が会話に出てきた時、「あ、あの話か」と引っかかれば、後でググって深掘りできる。
第1章: なぜ1台では足りないのか
「サーバーを増やす」と一言で言うが、実は3種類の増やし方がある。これを AKF Scale Cube と呼ぶ。
- X軸 (コピー): 同じものを並べる。Web サーバーを10台に複製、ロードバランサで振り分け
- Y軸 (機能分割): 「ユーザー API」「決済 API」「通知 API」を別サーバーに
- Z軸 (データ分割): 「ユーザーID 0-999万は DB1」「1000-1999万は DB2」
実際の大規模システムは、3つ全部組み合わせる。Netflix なら「視聴サービスを X軸で100台コピー、Y軸で『動画配信』『推薦』『課金』に分割、Z軸で地域別にデータ分割」みたいな具合。
そして、1台のサーバーで動かしている、ということは、そのサーバーが死んだら全部終わる、ということ。これを Single Point of Failure (SPoF) と呼ぶ。
単一障害点の怖さ
サーバー1台の年間ダウンタイムは経験的に数時間から数十時間。「99.9% 稼働 (年間8.76時間ダウン)」を「スリーナイン」と呼ぶ。商用サービスでは 99.99% (フォーナイン、年間52分) や 99.999% (ファイブナイン、年間5分) を目指す。1台では絶対に達成できない。複数台に分散して冗長化が必須。
ここで分散を始めると、すぐに次の壁にぶつかる。
第2章: 分散システム7つの誤謬
1994年、Sun Microsystems の Peter Deutsch という人が「分散システムを初めて作るやつが必ずやらかす7つの勘違い」をリストアップした。30年経っても、いまだに全エンジニアが踏むトラップ。
7つの誤謬 (The 8 Fallacies of Distributed Computing)
後にもう1つ追加されて「8つ」になったが、慣習で「7つの誤謬」と呼ばれる。
- ネットワークは信頼できる
- レイテンシはゼロ
- 帯域は無限
- ネットワークは安全
- トポロジーは変わらない
- 管理者は1人だけ
- 転送コストはゼロ
- ネットワークは均質である (後年追加)
それぞれ、なぜ「誤謬」なのかを見ていく。
2-1. 「ネットワークは信頼できる」は嘘
1台のマシン内では、関数呼び出しは絶対に成功する。func() を呼んで、戻ってこない、ということは起きない (バグやスタックオーバーフロー以外は)。
ところがネットワーク越しの呼び出しは、頻繁に失敗する。
- パケットがロスする (Wi-Fi の電波が一瞬切れる、ルーターが過負荷で破棄する)
- ケーブルが抜ける、スイッチが壊れる
- 相手サーバーが落ちている
- DNS が引けない
- ファイアウォールが急にブロックする
- クラウドプロバイダのリージョン全体が落ちる (実話。AWS でも年に何回かある)
つまり「relayer の関数を呼んだ」は、「成功」「失敗」「タイムアウト (成否不明)」の3状態がある。特に3つ目の「タイムアウト」が地獄。
「タイムアウト」は最悪の返答
あなたが決済 API を呼んだとする。10秒待っても応答が返ってこない。
この時、相手は:
- リクエストを 受け取っていない? (再送すれば良い)
- リクエストを受け取って 処理中? (再送したら二重決済になる)
- 処理は 完了したけど応答だけ届いていない? (再送したら同じく二重決済)
どれか分からない。それなのに、ユーザーには「決済完了」か「失敗」のどちらかを返さないといけない。
これを真面目に解くと、後述の Idempotency (冪等性) や 2-phase commit が必要になる。
2-2. 「レイテンシゼロ・帯域無限」は嘘
同じマシン内の関数呼び出しは、ナノ秒オーダー。だがネットワーク越しは桁が違う。
| 通信先 | 大体のレイテンシ |
|---|---|
| 同じプロセス内の関数呼び出し | 1 ns |
| 同じマシンの別プロセス (IPC) | 数 μs |
| 同じデータセンター内のサーバー | 0.5 ms |
| 同じ国の別データセンター | 10-30 ms |
| 大陸間 (東京⇔米西海岸) | 100-150 ms |
| 衛星経由 | 500 ms 以上 |
ナノ秒とミリ秒で 100万倍 の差。1台で動いていた処理を分散にしただけで100万倍遅くなる、ということが起きうる。
よくあるアンチパターン: 「N+1 がネットワーク越しに」
モノリス時代、ユーザー一覧画面で「各ユーザーの注文件数」を出すために、
SELECT count(*) FROM orders WHERE user_id = ?を100回ループで呼ぶコードがあったとする。DB が同じマシンなら、たぶん1秒以内に終わる。これをマイクロサービスに切り出して、「ユーザーサービス」と「注文サービス」を別サーバーにした瞬間、状況が一変する。
100回 × ネットワーク往復5ms = 500ms 遅くなる。1ページ開くのに0.5秒余計にかかる。
対策は「バッチエンドポイントを作る」、「最初から関連データを返す」、「キャッシュする」など。ネットワーク越しは1リクエストが死ぬほど高い という感覚を持つ。
2-3. 残りの誤謬
- 「ネットワークは安全」: 中間者攻撃、盗聴、なりすまし、すべて起きる。TLS / mTLS が必要 (04. TLS)
- 「トポロジーは変わらない」: クラウドでは IP もホスト名もしょっちゅう変わる
- 「管理者は1人」: 大規模になると部署が分かれ、「あの設定誰が変えた?」が日常茶飯事
- 「転送コストはゼロ」: クラウドの egress 料金は地味に高く、月100万円も普通にいく
- 「ネットワークは均質」: 社内 LAN と東京⇔米国とでは何もかも違う
なぜ Deutsch はこれを書いたか
当時 (1994年) は CORBA や DCOM など「リモートのメソッドをローカルと同じように呼べる」フレームワークが流行り、上記の誤謬を踏みまくって地獄を見た。30年経った今、gRPC や REST も同じ罠を踏みうる。「リモート呼び出しはローカル呼び出しではない」 を肝に銘じる。
第3章: 分散の3大問題 - 一貫性、可用性、レイテンシ
分散システムを語る時、必ず出てくる3つの軸がある。
3-1. CAP 定理 (おさらい)
06. データベース理論 でも触れたが、ネットワーク分断が起きた時、データシステムは「一貫性 (Consistency)」か「可用性 (Availability)」のどちらかを諦めるしかない。これが CAP 定理。
- CP 型: 一貫性を取り、分断中はサービスを止める (RDB のクラスタ、HBase など)
- AP 型: 可用性を取り、分断中も動かす。データはあとで整合させる (Cassandra, DynamoDB, S3 など)
3-2. PACELC 定理 - もう一つの軸
CAP 定理だけでは不十分、という主張から、Daniel Abadi が提唱した拡張版が PACELC。
PACELC とは
- P (Partition) Availability or Consistency: 分断時にどちらを取るか
- Else Latency or Consistency: 分断していない平常時、レイテンシか一貫性か
たとえば DynamoDB は PA/EL 型。分断時は可用性を優先、平常時もレイテンシを優先 (= 弱い一貫性)。
一方、Google の Spanner は PC/EC 型。常に強い一貫性を取り、その代わりレイテンシは犠牲にする。
「平常時すらレイテンシ vs 一貫性のトレードオフがある」というのが重要。「分散システムでは、ただ整合性のあるデータを返すだけで遅くなる」ということ。
3-3. なぜ「読み取り」だけでも問題なのか
「書き込みは難しいけど、読み取りは簡単じゃない?」と思うかもしれない。違う。
3台のレプリカ DB がある。書き込みは Primary、読み取りは Secondary。書き込み直後にユーザーが読みに行ったが、複製がまだ届いていない Secondary に当たると、自分が書いたはずのデータが見えない。
これを Read-after-Write 不整合 という。「自分が書いたものくらい、自分の次の読みで見えてほしい」という直感的な期待が、分散では成り立たない。
対策は色々あるが、根本的に解決するには「自分の書き込み後は Primary から読む」「過半数のレプリカから読む (Quorum)」など、複雑なルールが必要になる。
第4章: ステートレス vs ステートフル
分散システムの設計で、最も基本的な分かれ目がこれ。
ステートレスとは「サーバーがリクエストの間で状態を持たない」設計。どのサーバーに振っても良い、サーバーを足し引きしても問題ない、1台死んでも他で代替可能。HTTP は本質的にステートレス。だから Web は水平スケールしやすい。
ステートフルは逆に状態 (セッション、接続情報、メモリ上のキャッシュ) を持つ。特定サーバーへ送る必要 (sticky session)、そのサーバーが死ぬと状態が消える、スケールしにくい、と苦しみ満載。
ステートフルにせざるをえない場合
- ゲームサーバー (プレイヤーの位置情報をメモリで管理)
- WebSocket / 長期接続のチャットサーバー
- 動画配信のセッション (再生位置・帯域調整)
これらは「状態を外に追い出せない」性質がある。
「状態を外に追い出す」のが基本戦略
ステートフルにしないコツは「状態を外部ストレージに持たせる」こと。セッションは Redis、ファイルは S3、一時的な計算結果は memcached。サーバー側はステートレスなので、増やすのも減らすのも自由。
アンチパターン: ファイルをローカルディスクに書く
Web アプリで「ユーザーが画像をアップロードしたら、サーバーのローカルディスクに保存」というコード。1台運用なら動くが、サーバーを2台にした瞬間に壊れる。アップロードを受けたサーバーAにしか画像はないので、Bに来たリクエストでは見つからない。
対策: 最初から S3 など外部ストレージに置く。これだけで分散しやすさが激変する。
第5章: ロードバランサ
ステートレスなサーバーを複数台並べたら、リクエストを「どのサーバーに振るか」を決める仕組みが必要。それがロードバランサ (LB)。主な振り分けアルゴリズムは4つ。
主要な振り分けアルゴリズム
- Round Robin (ラウンドロビン): 順番に均等に。単純で速いが、サーバー性能がバラバラだと不公平
- Weighted Round Robin (重み付け): 性能の良いサーバーに2倍流す、など重みを設定
- Least Connection (最小接続数): 接続数が少ないサーバーに振る。WebSocket など長期接続で有効
- Sticky Session (セッション固定): 同じユーザーは必ず同じサーバーへ。ステートフルなサーバーで必須だが、偏るリスクあり
LB の罠
アンチパターン: Health Check 設定がガバガバ
LB は
GET /healthでサーバーの生死を確認するが、「200 を返すだけ」にしているサービスが多い。すると DB が死んでるのにサーバーは元気に 200 を返し続け、LB は「全サーバー正常」と判定し、ユーザーには「内部エラー」が返り続ける。正解は DB 接続や依存サービスの疎通までチェックする
/health/deepを持つ。LB は浅いチェックを高頻度で、深いチェックを低頻度で。
sticky session の落とし穴
特定の人気ユーザーがサーバー1に固定されていて、その人だけアクセスが集中してサーバー1が過負荷、という事故が起きる。負荷が均等にならない。なるべく sticky session を使わずに済むよう、状態は外部ストレージに追い出すのが王道。
LB には L4 (TCPレベル、中身を見ない、速い・軽い、AWS NLB) と L7 (HTTPレベル、URL パスやヘッダで賢く振れる、AWS ALB / nginx / Envoy) がある。最近は L7 が主流。
↓ ロードバランサの主要な振り分けアルゴリズム ↓
flowchart TD Req[受信したリクエスト群] --> LB{ロードバランサ<br/>アルゴリズム} LB -->|Round Robin<br/>順番に均等| RR[Server1 → 2 → 3 → 1...] LB -->|Weighted<br/>重み付け| W[Server1 重み3<br/>Server2 重み1] LB -->|Least Connection<br/>最小接続数| LC[一番暇な<br/>サーバーへ] LB -->|Sticky Session<br/>セッション固定| SS[同じユーザーは<br/>同じサーバーへ]
第6章: レプリケーション
DB の話。1台だと SPoF だし読み込みも捌けないので、複製 (レプリカ) を作る。
6-1. プライマリ-レプリカ方式
最も一般的なパターン。書き込みは Primary 1台のみ、読み込みは複数 Replica に分散。書き込みは Primary から Replica へ レプリケーションログ (PostgreSQL なら WAL, MySQL なら binlog) で伝播する。
嬉しいのは: 読み込みがスケールする (Read 9割の典型サービス向け)、Primary が死んでもレプリカに昇格できる (フェイルオーバー)、バックアップを Replica で取れる (Primary に負荷をかけない)。
↓ プライマリ-レプリカの非同期レプリケーション ↓
sequenceDiagram participant Client as クライアント participant Primary as Primary DB participant R1 as Replica 1 participant R2 as Replica 2 Client->>Primary: 書き込み (UPDATE) Primary-->>Client: OK (即応答) Primary->>R1: WAL/binlog 転送 Primary->>R2: WAL/binlog 転送 Note over R1,R2: 数ms〜数秒の遅延あり Client->>R1: 読み込み (SELECT) R1-->>Client: 古いデータの可能性
6-2. レプリカの遅延 (Replication Lag)
レプリケーションには時間がかかる。普通は数ミリ秒、過負荷なら数秒、最悪は数分。
これが意外と事故を生む。
アンチパターン: 書き込み直後にレプリカから読む
ユーザーがプロフィールを更新 → 完了画面に遷移 → 「現在のプロフィール」を表示
完了画面でレプリカから読むと、レプリカへの伝播が間に合わずに 古いデータが表示される。「あれ、ちゃんと保存されてない?」とユーザーが二度更新して、データ事故になる。
対策: 「自分が書いた直後の読みだけは Primary から」というルーティングをアプリ側で持つ。あるいは「書き込み完了画面では新しいデータをそのまま使う (DB から再取得しない)」など。
6-3. マルチプライマリ (Multi-Primary) の罠
「Primary が1台だと書き込みが詰まる」「フェイルオーバーが面倒」と思って、複数台を全部 Primary にする構成もある。これを マルチプライマリ や マスター・マスター と呼ぶ。
[Primary1] ←→ [Primary2] ← どちらにも書ける
↕ ↕ ← 双方向に同期
└────同期────────┘
「両方に書けて、両方が同期する」。最高に見える。
だが、これは罠だらけ。
マルチプライマリの恐怖
ユーザーAが Primary1 にレコード更新、同時にユーザーBが Primary2 に同じレコード更新。
どちらが勝つ? どちらが正解? 答えは「分からない」。
「Last-Write-Wins (最後に書いた方が勝つ)」というルールでも、時計がずれている分散システムでは「どちらが本当に最後か」を決めるのが超難しい。
結果として、データが消える、矛盾する、戻ったように見える、などの事故が起きる。Galera Cluster や Cassandra で実際に運用していると、これに頭を悩ませることになる。
実務では、よほどの理由がない限り シングルプライマリ + フェイルオーバー で十分。マルチプライマリは「グローバルに分散したデータベース」など、特殊な要件でのみ使う。
第7章: シャーディング - データを切り分ける
レプリカは「同じデータのコピーを増やす」戦略。一方、データ自体が巨大すぎて1台に収まらない場合は「データを切り分ける」必要がある。これが シャーディング (Sharding)、あるいは 水平分割 (Horizontal Partitioning)。
7-1. シャーディングの基本
たとえばユーザーが10億人いるとする。1台の DB に収まらない。なので「ユーザーID で分割」する。
ユーザーID 0 〜 2.5億 → Shard1
ユーザーID 2.5億 〜 5億 → Shard2
ユーザーID 5億 〜 7.5億 → Shard3
ユーザーID 7.5億 〜 10億 → Shard4
各シャードは独立した DB。各シャードは普通の Primary + Replica 構成。これで「全体としては10億人を扱えるが、1台あたりは2.5億人で済む」。
↓ シャーディング (水平分割) の構造 ↓
flowchart TD App[アプリケーション] --> Router{シャードルーター<br/>ユーザーID で判定} Router -->|ID: 0〜2.5億| S1[(Shard 1<br/>Primary+Replica)] Router -->|ID: 2.5億〜5億| S2[(Shard 2<br/>Primary+Replica)] Router -->|ID: 5億〜7.5億| S3[(Shard 3<br/>Primary+Replica)] Router -->|ID: 7.5億〜10億| S4[(Shard 4<br/>Primary+Replica)]
7-2. シャードキーの選択が運命を決める
何を「分割の鍵」にするか、これが超重要。
シャードキー選択でよくある失敗
悪い例1: 「登録順 ID」をシャードキーに 新規ユーザーは全員一番新しい ID なので、最後の Shard に集中する。古いシャードは閑古鳥、新しいシャードは過負荷。ホットスポット問題。
悪い例2: シャードをまたぐクエリが頻発する 「ユーザーID で分割」したが、機能上「都道府県で集計」が多発する。すると全シャードに問い合わせる必要があり、遅い・複雑・スケールしない。
良い例: アクセスパターンに合ったキー ECサイトなら「ユーザーID」で分けると個人のページが速い (買い物履歴は1シャードで完結)。SNSなら「投稿ID」でなく「投稿者のユーザーID」で分けると、その人の投稿が同じシャードに集まる。
7-3. リシャーディングの地獄
最初のシャード設計が間違っていた、あるいはデータが増えてシャードを増やしたい、となった時、「リシャーディング」 が必要になる。
これがめちゃくちゃ大変。たとえば4シャードを8シャードに増やすと、半分のデータを新シャードに移さないといけない。サービスを止めずに、データを移しながら、不整合を起こさず、巨大なデータを移送する。数週間から数ヶ月かかるプロジェクトになる。
大手企業のシャーディング事例
- Instagram: PostgreSQL を user_id でシャーディング。シャード数は当初から大量 (数千) にしておき、リシャーディングを回避する設計
- Pinterest: MySQL の上に独自シャーディング層。リシャーディング地獄を経験した記録がブログにある
- Discord: メッセージ DB を Cassandra に移行、後に ScyllaDB へ。シャーディングのオーバーヘッドとの戦いの歴史
「シャーディングは最後の手段」というのが業界の合言葉。それくらい運用が重い。
7-4. Consistent Hashing - 「途中追加」を楽にする
シャード数を変更しても、できるだけ既存データを移動させない手法が Consistent Hashing (一貫性ハッシュ)。
円周上にシャードを配置して、データのハッシュ値で「右回りで一番近いシャード」に置く、というアイデア。シャードを1つ追加しても、影響を受けるデータは隣接区間だけで済む。
Memcached, DynamoDB, Cassandra など多くの分散ストレージで使われている。詳しくは別記事に譲るが、「Consistent Hashing」という名前だけ覚えておくと、後で出てきた時に「あれか」となる。
第8章: 分散トランザクション - お金が二重に引かれる前に
ここからが分散システムの本番。「複数のサービスにまたがる処理を、すべて成功するか、すべて失敗するか」 をどう保証するか。
8-1. 例: ECサイトの注文処理
ユーザーが「注文する」ボタンを押した時、裏で何が起きているか。
- 在庫サービス: 在庫を1減らす
- 決済サービス: クレジットカードに課金する
- 配送サービス: 配送指示を出す
- 通知サービス: メールを送る
これらが全部別サーバー、別 DB だとする。普通の DB トランザクション (BEGIN ... COMMIT) は1つの DB の中でしか効かない。
問題: 在庫は減ったが、決済が失敗したら?
在庫が減りっぱなしになる。商品は「売れた」ことになっているのに、お金は入っていない。これが何百件も積み重なると、棚卸しの時に大事故になる。
8-2. 2-Phase Commit (2PC)
古典的な解決策が 2PC。「みんなで準備できたら、みんなでコミット」するプロトコル。
[Coordinator] [Service A] [Service B] [Service C]
| | | |
+--Phase 1: Prepare----->| | |
+-----------Prepare----->+ | |
+-----------Prepare------+------------->| |
+-----------Prepare------+--------------+------------->|
| | |
←-----Yes----------------+ | |
←-----Yes----------------+--------------+ |
←-----Yes----------------+--------------+--------------+
| | |
全員 Yes なら | | |
+--Phase 2: Commit------>+ | |
+-----------Commit------>+------------->| |
+-----------Commit------>+--------------+------------->|
各サービスは「Prepare (準備)」段階でロックを取り、すべて準備完了したら全員に「Commit」を投げる。途中で1つでも失敗したら「Rollback」を全員に投げる。
↓ 2-Phase Commit の流れ (シーケンス) ↓
sequenceDiagram participant C as Coordinator participant A as Service A participant B as Service B participant D as Service C Note over C,D: Phase 1: Prepare C->>A: Prepare? C->>B: Prepare? C->>D: Prepare? A-->>C: Yes (ロック取得) B-->>C: Yes (ロック取得) D-->>C: Yes (ロック取得) Note over C,D: Phase 2: Commit C->>A: Commit C->>B: Commit C->>D: Commit A-->>C: Done B-->>C: Done D-->>C: Done
2PC の致命的な弱点
Coordinator (調整役) がフェーズの途中で死ぬと、参加サービスは ロックを取ったまま無限に待つ。手動介入が必要。
加えて、全員の Prepare 完了を待つので 遅い。マイクロサービスで使うとパフォーマンスが死ぬ。
このため、現代のマイクロサービスでは 2PC はほぼ使わない。RDB のクラスタ内 (XA トランザクション) や金融など強い整合性が必須の場面に限定される。
8-3. SAGA パターン
現代的な代替案が SAGA パターン。「トランザクションを諦め、補償処理 (Compensation) で帳尻を合わせる」。
注文成功シナリオ:
[在庫減算] → [決済成功] → [配送指示] → [通知] → 完了
途中失敗シナリオ:
[在庫減算] → [決済失敗] → [在庫を元に戻す (補償)] → 注文キャンセル
各ステップで「もし失敗したら、これまでのステップを取り消す処理 (補償処理)」を予め用意しておく。失敗したら逆順で補償を走らせる。
SAGA の本質
SAGA は 「結果整合性」 を受け入れる。一瞬「在庫は減ったが決済はまだ」みたいな中途半端な状態が存在する。でも最後には整合する。
ユーザーから見ると「お金は引かれたけど商品が来ない」「在庫はあるのに在庫切れ表示」みたいな一時的なズレが起きうる。これを許容できる業務か、を考えて使う。
ECサイトの注文、銀行送金 (送金中状態あり) など、多くの実務で SAGA は使われている。
SAGA の実装には2つの流派がある。
- Orchestration 型: 中央のオーケストレーターが各ステップを呼び出す。順序が明確だが中央集権
- Choreography 型: 各サービスがイベントを発行・購読しあう。疎結合だが流れが見えにくい
イベント駆動アーキテクチャ (Kafka など) と相性が良いのは Choreography 型。
↓ SAGA パターン: 失敗時の補償トランザクション ↓
sequenceDiagram participant O as 注文サービス participant I as 在庫サービス participant P as 決済サービス participant S as 配送サービス O->>I: 在庫を1減らす I-->>O: OK O->>P: クレカ課金 P-->>O: 失敗 (残高不足) Note over O,S: ロールバック開始<br/>(補償処理を逆順に実行) O->>I: 在庫を1戻す (補償) I-->>O: 戻し完了 Note over O: 注文キャンセル確定
8-4. Outbox パターン - 「DB 書き込み + イベント発行」を確実に
これも実務で頻出。「DB を更新したら、Kafka にイベントを流す」というコードを書くと、初心者は普通こう書く。
アンチパターンの典型
db.Exec("UPDATE orders SET status = 'paid' WHERE id = ?", orderID) kafka.Publish("order_paid", orderEvent) // ← ここが落ちたら?DB は更新成功、でも Kafka 発行が失敗したら? 注文は「支払い済み」になっているのに、配送サービスはイベントを受け取らない。配送が走らない事故。
正解は Outbox パターン。
1. DB トランザクション内で、orders テーブルと outbox テーブルの両方に書き込む
2. 別プロセスが outbox テーブルを定期的にポーリング
3. 未送信のレコードを Kafka に発行 → 送信済みフラグを立てる
DB のトランザクション内で「やるべき仕事を記録」しておき、後でそれを取り出して実行する。失敗してもリトライできる。
第9章: 合意アルゴリズム - Paxos と Raft
「複数のノードで合意を取る」というのは、想像以上に難しい。
9-1. なぜ「合意」が必要か
例: 5台の DB レプリカで、誰が Primary かを決めたい。今の Primary が死んだ時、誰が次の Primary になるか。
「投票して決めればいい」と思うが、5台のノードは互いに通信が不安定。一部のノードはネットワーク分断で他と話せない。誰が生きていて、誰が死んでいるかも分からない。
それでも 「全員が同じ答えに合意する」 ことを保証するアルゴリズムが必要。これが 合意アルゴリズム (Consensus Algorithm)。
9-2. Paxos - 難解な原典
1989年、Leslie Lamport が Paxos という合意アルゴリズムを発表。これが分散システム合意の元祖。
Paxos が難解すぎる伝説
Paxos の論文は数学的で、誰も読み解けなかった。Lamport 自身は「古代ギリシャの島で議員たちが投票するように…」みたいな比喩で書いて、余計に分かりにくくなったと言われる。
しかも実装するのが超難しい。Google が Chubby というロックサービスで Paxos を実装したが、論文通りには動かなくて独自拡張をたくさん入れた、という有名な話がある。
「Paxos は理論的には正しいが、実装は誰にもできない」と言われた時代があった。
9-3. Raft - 「分かりやすい合意」
そこで2014年、Stanford の Diego Ongaro と John Ousterhout が Raft を発表。論文タイトルは「In Search of an Understandable Consensus Algorithm」。「分かりやすい合意アルゴリズムを探して」。
Raft の役割は3つ。
- Leader Election (リーダー選出): 誰が今のリーダーかを決める
- Log Replication (ログ複製): リーダーがログをフォロワーに複製
- Safety (安全性): 一度コミットされたログは絶対に消えない
各ノードは「Follower」「Candidate」「Leader」のいずれかの状態。Leader が死ぬと、生き残りの Follower が Candidate になって投票を始め、過半数の票を取ったノードが新 Leader になる。
Raft が支えているもの
- etcd (Kubernetes の心臓部、設定・状態の保存)
- Consul (HashiCorp のサービスディスカバリ)
- CockroachDB (分散 SQL)
- TiKV (TiDB の下回り)
- MongoDB のレプリカセット (Raft 風の独自実装)
あなたが Kubernetes を使っているなら、知らずに毎日 Raft の恩恵を受けている。
9-4. Quorum - 「過半数の合意」の魔法
Raft も Paxos も、根底には Quorum (定足数) という概念がある。
「3台のレプリカのうち2台に書けたら成功」「5台のうち3台に書けたら成功」というルール。過半数のノードが合意すれば、それが真実。
なぜ過半数か? ネットワーク分断が起きた時、両側に過半数が含まれることはありえない から。片側だけが過半数を持つ。これにより「分裂して両方が違う真実を作る (Split Brain)」を防げる。
Quorum はあらゆる分散システム設計の基本概念。「過半数ルール」を忘れない。
第10章: 順序を決める - Lamport Clock と Vector Clock
分散システムでは「どっちが先に起きた?」を決めるのが死ぬほど難しい。
10-1. 時計はずれる
各サーバーは自分の時計を持っている。これを 物理時計 (Physical Clock) と呼ぶ。NTP で同期するが、それでもミリ秒単位ではずれている。
「ユーザーAがレコードを更新した時刻」と「ユーザーBが同じレコードを更新した時刻」のどちらが先か、サーバーの時計で判断すると、間違うことがある。
時計逆転問題
NTP は「時刻が大きくずれていたら、急に時計を巻き戻すことがある」(slew vs step)。これにより、同じサーバー内で「タイムスタンプが過去に戻る」ことがある。
ログのタイムスタンプを使ってデバッグしている時、「あれ? このイベントの方が先のはずなのにタイムスタンプが後?」となることがある。地獄。
対策: 単調増加クロック (CLOCK_MONOTONIC, Go なら time.Now() ではなく自前カウンタ) を使う。
10-2. Lamport Clock (1978年)
Leslie Lamport が考案した、物理時計を使わずに「論理的な順序」を決める方法。
各イベントに「論理時刻」を振る。ルールは2つ。
- ノード内でイベントが起きたら、自分のカウンタを +1
- メッセージを受信したら、自分のカウンタを
max(自分, 相手) + 1にする
これだけ。これで「因果関係のあるイベントは必ず順序が付く」ことが保証される。
ノードA: 1 → 2 → 3 → (Bにメッセージ)
↓
ノードB: 1 → 2 → 4 (= max(2, 3) + 1) → 5
ただし Lamport Clock は 「同時発生 (concurrent)」を区別できない。ノードAの「3」とノードBの「4」のどちらが因果的に先かは分かるが、「同時に起きたイベント」を区別できない。
10-3. Vector Clock - 因果関係を完全に追跡
これを解決するのが Vector Clock (ベクトルクロック)。各ノードが「全ノード分のカウンタ」を持つ。
ノードA: [A:3, B:1, C:0]
ノードB: [A:2, B:5, C:2]
これを比較することで、「Aのイベントが因果的にBより先か、後か、独立 (concurrent) か」が完全に判定できる。
Vector Clock は Amazon DynamoDB、Riak、Cassandra (Lightweight Transactions) などで使われている。「複数の書き込みが衝突した時、それを検出する」ために重要な役目を果たす。
なぜこれが大事か
分散システムで「最新の値はどれ?」を決める時、物理時計を頼ると間違える。Vector Clock があれば、「これは因果的に最新」「これとこれは衝突しているのでアプリ側で解決して」と判定できる。
Amazon の DynamoDB は当初これを表に出していたが、現代の DynamoDB は内部に隠してユーザーには「Last-Write-Wins」を提供している。複雑すぎてアプリ作者が扱いきれないため。
10-4. Google の TrueTime - 物理に戻る
Google の Spanner は逆のアプローチを取った。「世界中の時計を超高精度に同期させてしまえ」。
各データセンターに原子時計と GPS を置き、誤差 7ms 以内を保証する仕組みを作った。これが TrueTime。
TrueTime のおかげで Spanner は「全世界で強い一貫性のあるトランザクション」を実現している。グローバル企業向けの SaaS や金融で使われている。
「ハードウェアで物理時計を超高精度にする」というのは Google にしかできない芸当。一般人は Lamport / Vector Clock の世界で頑張る。
第11章: 冪等性 (Idempotency) - 何回呼んでも同じ結果
ここまで何度も「ネットワークは信頼できない」「タイムアウトしたら再送する」と書いてきた。再送が安全にできるためには 冪等性 が不可欠。
11-1. 冪等性とは
「同じ操作を何回繰り返しても、結果が変わらない」性質。
冪等な操作: 「ユーザーID 123 の名前を 田中 にする」
→ 何回呼んでも結果は同じ
冪等でない操作: 「ユーザーID 123 の残高を 100円 増やす」
→ 1回呼ぶと +100、2回呼ぶと +200、結果が変わる
HTTP のメソッドで言うと、GET, PUT, DELETE は冪等 (に設計されるべき)。POST は冪等でない (新規作成だから何度呼んでも違うレコードができる)。
11-2. 冪等キーで POST も冪等にする
決済処理のような「POST だけど絶対に重複させたくない」処理では、Idempotency Key (冪等キー) をクライアントから送る。
POST /payments
Idempotency-Key: 7a8f9b2c-... ← ユニークなキー
{ "amount": 1000 }
サーバー側は「このキーで処理を既にやってるか」をチェックし、やってあれば前と同じ結果を返す。新規なら処理する。
Stripe API の冪等キー
Stripe は決済 API で冪等キーを標準サポートしている。Idempotency-Key ヘッダにユニークな ID を入れると、ネットワーク障害でリトライしても二重課金されない。
「最大24時間、同じキーで来たリクエストは前回と同じ応答を返す」という設計。これを真似しているクラウド API は多い。
11-3. 「重複を作らない」設計
冪等性は 設計の段階で組み込む ものであって、後から付け足すのは難しい。
冪等な設計のコツ
- PUT で「最終状態を指定」: 「残高を 100 にする」(冪等) であって「残高に +100」(非冪等) ではない
- ユニークキー制約に頼る: DB の UNIQUE 制約で重複を弾く
- イベントの ID をクライアントが発行: クライアント側で UUID を生成、サーバーは同じ UUID を二重処理しない
- 状態機械を導入: 「pending → confirmed → cancelled」のような状態遷移で重複処理を防ぐ
冪等性は分散システムの命綱。「リトライしても安全」というのは、それだけで天国。
第12章: 障害に強いパターン - Circuit Breaker, Retry, Bulkhead
分散システムでは、依存サービスが落ちることが日常茶飯事。それに対する自己防衛策。
12-1. Circuit Breaker (回路遮断器)
電気のブレーカーと同じ発想。「依存サービスが落ちてると気づいたら、しばらく呼ぶのをやめる」。
[Closed] → 通常呼び出し
↓ (失敗が一定数を超える)
[Open] → 呼び出し即失敗 (= 落ちてる相手に無駄打ちしない)
↓ (一定時間経過)
[Half-Open] → 試しに1回だけ呼ぶ
↓ 成功 → Closed
↓ 失敗 → Open
何が嬉しいか。
Circuit Breaker の効果
- 連鎖障害 (Cascading Failure) を防ぐ: 依存サービスが遅くなった時、こちらが大量にリクエストを投げると、依存サービスのキューが詰まる → さらに遅くなる → 自分のスレッドも詰まる、という悪循環を断つ
- ユーザーの待ち時間を短縮: 「どうせ失敗する」のなら、タイムアウト待たずに即エラーを返す方が親切
- 依存サービスの回復を妨げない: 弱っている相手にリクエストを投げ続けると、復旧の邪魔をする
Netflix の Hystrix というライブラリが有名 (今はメンテ終了、後継は resilience4j)。
↓ Circuit Breaker の状態遷移 ↓
stateDiagram-v2 [*] --> Closed Closed --> Open: 失敗が閾値超え Open --> HalfOpen: 一定時間経過 HalfOpen --> Closed: 試行成功 HalfOpen --> Open: 試行失敗 note right of Closed 通常呼び出し end note note right of Open 即失敗を返す (無駄打ち防止) end note note right of HalfOpen 試しに1回だけ呼ぶ end note
12-2. Retry with Backoff (指数バックオフ)
失敗したらリトライ、は良いが、即リトライすると依存サービスをさらに苦しめる。指数的に待ち時間を増やす のが定石。
1回目失敗 → 100ms 待ってリトライ
2回目失敗 → 200ms 待ってリトライ
3回目失敗 → 400ms 待ってリトライ
4回目失敗 → 800ms 待ってリトライ
...
加えて Jitter (ジッター、ランダムずらし) を加える。
Jitter なしリトライの罠 (Thundering Herd)
1000台のクライアントが同時に失敗 → 全員が「100ms 後にリトライ」 → 100ms 後に1000台が同時に押し寄せる → サーバーが再び落ちる
これを Thundering Herd Problem (殺到問題) と呼ぶ。
対策: 待ち時間に乱数を混ぜる。「100ms ± 50ms ランダム」のように。AWS の公式ガイドラインでも Full Jitter が推奨されている。
↓ Retry with Exponential Backoff + Jitter のシーケンス ↓
sequenceDiagram participant C as Client participant S as Server C->>S: Request (1回目) S-->>C: Fail (500) Note over C: 100ms ± Jitter 待機 C->>S: Request (2回目) S-->>C: Fail (500) Note over C: 200ms ± Jitter 待機 C->>S: Request (3回目) S-->>C: Fail (500) Note over C: 400ms ± Jitter 待機 C->>S: Request (4回目) S-->>C: OK 200
12-3. Bulkhead (隔壁)
船の中の「区画分け」と同じ発想。1つの区画が浸水しても、他の区画は無事。
具体的には「依存サービスごとにリソース (スレッドプール、コネクションプール) を分ける」。
[アプリ] ─→ [DB プール]: 最大50接続
─→ [API A プール]: 最大20接続
─→ [API B プール]: 最大20接続
こうすると、API A が遅くて20接続が全部詰まっても、DB や API B の接続には影響しない。
Bulkhead がない時の事故例
アプリが共通の HTTP クライアントプール (最大100接続) で全外部 API を呼んでいた。
ある日、外部の決済 API がメチャクチャ遅くなって応答が30秒。100接続全部が決済 API 待ちで埋まる → 他の API (在庫、ユーザー検索) も呼べなくなる → 全機能停止。
「決済 API の遅延」が「サービス全体の停止」に拡大した。これが連鎖障害。
これら3つ (Circuit Breaker, Retry, Bulkhead) は 分散システム耐障害性の三種の神器。サービスメッシュ (後述) や resilience4j などで簡単に組み込める。
第13章: サービスメッシュ - 「分散の地獄」を共通化する
ここまで読むと「分散システム、難しすぎる」と思うはず。Circuit Breaker、リトライ、サービスディスカバリ、暗号化、トレース…。これを全マイクロサービスで個別実装するのは無理。
そこで登場したのが サービスメッシュ。
13-1. サイドカープロキシ
サービスメッシュの中心アイデアは 「アプリの隣にプロキシ (サイドカー) を置き、ネットワーク通信を全部プロキシに任せる」。
旧来:
[アプリA] --(HTTP)--> [アプリB]
↑ Circuit Breaker, Retry, mTLS を各アプリが実装
サービスメッシュ:
[アプリA] - [プロキシA] --(HTTP)--> [プロキシB] - [アプリB]
↑ Circuit Breaker, Retry, mTLS を全部プロキシが担当
アプリは普通の HTTP を喋るだけ。プロキシが裏で全部やる。
13-2. Istio と Linkerd
代表的なサービスメッシュ実装。
- Istio: Google + IBM + Lyft 製。多機能、複雑、リソース消費大
- Linkerd: 軽量、シンプル、Rust 製プロキシで高速
- Consul Connect: HashiCorp 製、Consul とセットで使える
いずれも Kubernetes 環境で広く使われている。
サービスメッシュで何ができるか
- mTLS の自動有効化: 全サービス間通信を暗号化・相互認証
- トラフィック制御: 「カナリアデプロイ (5%だけ新バージョンに流す)」「A/Bテスト」が宣言的に書ける
- 可観測性: 分散トレース、メトリクス、ログを自動収集
- 障害注入 (Chaos Engineering): 「5%のリクエストにわざと遅延を入れる」「10%失敗させる」でレジリエンステスト
13-3. サービスメッシュは「銀の弾丸」ではない
サービスメッシュ導入で詰まる例
- 学習コスト: Istio は YAML の山と概念の山。専任の SRE が必要
- リソース消費: 全 Pod にサイドカーがつくので、メモリ・CPU が増える
- デバッグ困難: 通信が複数層を通るので、問題切り分けが難しい
- オーバーエンジニアリング: マイクロサービス数が10個以下なら、アプリ内ライブラリで十分かも
「Istio 入れたら全部解決」ではない。むしろ「分散システムの問題を、別のレイヤーに移しただけ」と捉えるのが正しい。
第14章: 「分散を避ける」が最強戦略になる時もある
分散システムの話を散々してきて何だが、「分散しない」も立派な戦略 だ。
14-1. モノリスは時代遅れではない
2010年代前半、マイクロサービスがバズワードになり、「モノリスは悪、マイクロサービスこそ正義」みたいな空気があった。
その揺り戻しが2020年前後から来ている。「モジュラーモノリス」「マジョスト・イナフ・アーキテクチャ」など、シンプルな構成への回帰が言われている。
DHH (Rails 創始者) の主張
Basecamp / HEY の創業者であり Rails 開発者の David Heinemeier Hansson は、「マイクロサービスは大企業のスケール問題への解。中小企業がやるとオーバーエンジニアリング」と主張している。
実際 Basecamp は数百万ユーザーを モノリスの Rails で運用している。
「分散はコストである」という現実を直視した上で、必要になってから分散すべき、という考え方。
14-2. Segment が「マイクロサービスからモノリスに戻した」話
実例として有名なのが、Segment (顧客データプラットフォーム企業) の事例。
2017年頃、Segment はマイクロサービスアーキテクチャを採用していた。だが、サービス数が爆増し、運用が破綻。2018年、彼らは モノリス (彼らはこれを「Centrifuge」と呼んだ) に戻す決断 をした。
そのブログ記事 (邦題: 「マイクロサービスからモノリスに戻った話」) は当時バズった。要点はこう。
- マイクロサービスは「組織のスケール」のためにあるが、Segment の組織サイズでは過剰だった
- 100以上のサービスがあると、デバッグ・デプロイ・運用のコストが膨大
- すべてのサービスを統一されたランタイムにまとめた方が、開発速度も信頼性も上がった
教訓
マイクロサービスは「組織が大きくなった時のために、コンウェイの法則を活用する」設計手法。コードの設計だけでなく、組織の設計でもある。
5人のチームで20マイクロサービス、というのはまず破綻する。「1チーム = 1〜数サービス」が目安。チーム数と組織規模で「マイクロサービス化していい量」が決まる。
14-3. 「先に分散すべきもの」は何か
分散を避けつつ、「ここだけは分けた方が良い」ものを見極める。
分散すべき強い理由
- データ量が物理的に1台に収まらない (PB級のデータ)
- 負荷特性が極端に違う (動画変換は CPU 重い、API は I/O 重い、別マシンが効率的)
- 障害ドメインを分けたい (決済が落ちても閲覧は動かしたい)
- チームが完全に分かれている (独立にデプロイしたい)
- 技術スタックを分けたい (推薦エンジンは Python、API は Go、など)
これらが当てはまらないなら、無理に分散する必要はない。
↓ モノリス vs マイクロサービスの構造対比 ↓
flowchart LR subgraph モノリス UA[ユーザー] --> Mono[1つの大きなアプリ<br/>ユーザー / 決済 / 在庫 / 通知<br/>すべて同居] Mono --> MDB[(共有 DB)] end subgraph マイクロサービス UB[ユーザー] --> GW[API Gateway] GW --> US[User Service] GW --> PS[Payment Service] GW --> IS[Inventory Service] GW --> NS[Notification Service] US --> UDB[(User DB)] PS --> PDB[(Payment DB)] IS --> IDB[(Inventory DB)] end
第15章: 分散システムを学び続けるには
最後に、ここから先に進む人へのガイド。
15-1. 必読の本・記事
分散システムの古典
- 『Designing Data-Intensive Applications』 (Martin Kleppmann, 邦訳『データ指向アプリケーションデザイン』): 分散システム本のド定番。今すぐ読むべき。レプリケーション、シャーディング、合意、トランザクションを全部カバー
- 『Distributed Systems for Fun and Profit』 (Mikito Takada, mixu.net で無料公開): 短くてエッセンスが詰まった入門
- Aphyr のブログ『Jepsen』 (jepsen.io): あらゆる分散 DB のバグを暴いてきた歴史的な検証シリーズ。「この DB は安全ではない」と公開処刑する怖い記事群
- AWS Builders’ Library: Amazon の現役エンジニアが書いた現場の知見集
15-2. 触って学ぶ
手を動かすなら
- Raft 実装の写経: 教育用の Raft 実装 (etcd の中身、または MIT の 6.824 講義) を読み・写す
- 小さな分散システム自作: 2台の Go プロセスで「いずれかが Primary」を決める仕組みを書いてみる
- Kafka を触る: ローカルで Kafka を立てて、Producer / Consumer を書く。順序保証、再送、コンシューマグループの挙動を観察
- Kubernetes を構築: minikube や kind でクラスタを立て、etcd の中身を見る (etcdctl)
15-3. 「失敗事例」を読み込む
業界の事故レポート (Postmortem) は最高の教材。
- AWS の障害レポート: 大規模障害の後に AWS が公開する詳細な報告書
- Google SRE Book (無料公開): SRE 文化の原典。Toil、SLO、Error Budget などの概念
- GitLab の Postmortem 集: 「2017年のDB全消し事故」など赤裸々な事故記録が GitHub で公開されている
- Netflix Tech Blog: Chaos Engineering, レジリエンス設計の業界リーダー
まとめ: あなたが今後分散システムを語る時に思い出してほしいこと
この記事のエッセンス
- 分散は「1台でやることを増やしただけ」ではない - 性質がまったく違う世界
- 7つの誤謬を忘れない - ネットワークは信頼できない、レイテンシゼロでもない、安全でもない
- CAP / PACELC - 分断時も平常時も、一貫性と他の何かのトレードオフ
- ステートレスは祝福、ステートフルは呪い - 状態は外に追い出せ
- ロードバランサは Health Check が命 - 浅すぎても深すぎてもダメ
- マルチプライマリには近づくな - シングルプライマリ + フェイルオーバーで大体足りる
- シャーディングは最後の手段 - シャードキー選定が全てを決める
- 2PC は遅い、SAGA で帳尻を合わせろ - 結果整合性を受け入れる勇気
- 冪等性を最初から組み込め - リトライが安全な世界は天国
- Circuit Breaker, Retry with Jitter, Bulkhead - 耐障害性三種の神器
- Raft の名前は覚えておけ - etcd, Consul, CockroachDB の心臓
- Lamport / Vector Clock - 物理時計を信じるな、論理時計を使え
- 「分散しない」も立派な戦略 - Segment は戻した、Basecamp は最初から避けた
あなたが明日から少し違う目で見られること
- 「マイクロサービスにしましょう」と言われた時、「組織サイズ的に必要?」と問い返せる
- 「DB を冗長化」「サーバーを増やす」と言われた時、「読み込み? 書き込み? どっちのスケール?」と聞ける
- 障害ニュースで「分断耐性のために可用性を取った」と書いてあったら「CAP の AP 型ね」と分かる
- 「APIが落ちた」報告を受けた時、「Circuit Breaker は効いてた?」「リトライ嵐になってない?」と確認できる
考えてみよう
- あなたが今までに作ったシステムで、「2台目を増やす時に困りそうな」コードはどこ?
- もし全社員10人の会社で「マイクロサービスにします」と言われたら、何と返す?
- 「ユーザーの注文処理」を SAGA で設計するなら、補償処理を何個用意する?
さらに深掘りするなら
- 書籍: 『データ指向アプリケーションデザイン』(Martin Kleppmann) - 最強の一冊。これだけで通用する
- 書籍: 『マイクロサービスアーキテクチャ 第2版』(Sam Newman) - 設計トレードオフの詳細
- 書籍: 『Building Microservices』- 上記の英語原典
- 書籍: 『Release It!』(Michael Nygard) - Circuit Breaker などのパターンの元ネタ
- ドキュメント: The Twelve-Factor App - クラウドネイティブなアプリ設計の原則
- 動画: MIT 6.824 講義 (YouTube で全部公開) - 分散システムの本格講義
- OSS: etcd - Raft の参考実装、コードが読みやすい
- OSS: Hashicorp Raft - Go 製の Raft ライブラリ
- ブログ: Aphyr’s Jepsen analyses - DB 検証の宝庫
カリキュラム本編で関連する章
- データベース章 - レプリケーション・シャーディングの実装
- インフラ章 - Kubernetes, サービスメッシュ, ロードバランサの実践
- Go 章 - context, retry, circuit breaker パターンの実装
- セキュリティ章 - mTLS, ゼロトラスト
次の読み物
08_クラウドの歴史.md をまだ読んでいなければ次に。クラウドの歴史 - オンプレからサーバーレスまで、というテーマ。
分散システムを「自前で頑張る」時代から、「クラウドにお任せ」時代へ移った経緯を辿る。AWS, GCP, Azure はなぜあの形になったか、サーバーレスは何を変えたか。次もスマホで気楽にどうぞ。