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つしかない。

  1. 垂直スケール (Scale Up): サーバーをでかいやつに買い替える。8コアを32コアに、メモリを32GBから512GBに
  2. 水平スケール (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. レイテンシはゼロ
  3. 帯域は無限
  4. ネットワークは安全
  5. トポロジーは変わらない
  6. 管理者は1人だけ
  7. 転送コストはゼロ
  8. ネットワークは均質である (後年追加)

それぞれ、なぜ「誤謬」なのかを見ていく。

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. 在庫サービス: 在庫を1減らす
  2. 決済サービス: クレジットカードに課金する
  3. 配送サービス: 配送指示を出す
  4. 通知サービス: メールを送る

これらが全部別サーバー、別 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つ。

  1. Leader Election (リーダー選出): 誰が今のリーダーかを決める
  2. Log Replication (ログ複製): リーダーがログをフォロワーに複製
  3. 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. ノード内でイベントが起きたら、自分のカウンタを +1
  2. メッセージを受信したら、自分のカウンタを 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 DynamoDBRiakCassandra (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 を入れると、ネットワーク障害でリトライしても二重課金されない。

仕様: Idempotent Requests

「最大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 の効果

  1. 連鎖障害 (Cascading Failure) を防ぐ: 依存サービスが遅くなった時、こちらが大量にリクエストを投げると、依存サービスのキューが詰まる → さらに遅くなる → 自分のスレッドも詰まる、という悪循環を断つ
  2. ユーザーの待ち時間を短縮: 「どうせ失敗する」のなら、タイムアウト待たずに即エラーを返す方が親切
  3. 依存サービスの回復を妨げない: 弱っている相手にリクエストを投げ続けると、復旧の邪魔をする

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. 分散は「1台でやることを増やしただけ」ではない - 性質がまったく違う世界
  2. 7つの誤謬を忘れない - ネットワークは信頼できない、レイテンシゼロでもない、安全でもない
  3. CAP / PACELC - 分断時も平常時も、一貫性と他の何かのトレードオフ
  4. ステートレスは祝福、ステートフルは呪い - 状態は外に追い出せ
  5. ロードバランサは Health Check が命 - 浅すぎても深すぎてもダメ
  6. マルチプライマリには近づくな - シングルプライマリ + フェイルオーバーで大体足りる
  7. シャーディングは最後の手段 - シャードキー選定が全てを決める
  8. 2PC は遅い、SAGA で帳尻を合わせろ - 結果整合性を受け入れる勇気
  9. 冪等性を最初から組み込め - リトライが安全な世界は天国
  10. Circuit Breaker, Retry with Jitter, Bulkhead - 耐障害性三種の神器
  11. Raft の名前は覚えておけ - etcd, Consul, CockroachDB の心臓
  12. Lamport / Vector Clock - 物理時計を信じるな、論理時計を使え
  13. 「分散しない」も立派な戦略 - Segment は戻した、Basecamp は最初から避けた

あなたが明日から少し違う目で見られること

  • 「マイクロサービスにしましょう」と言われた時、「組織サイズ的に必要?」と問い返せる
  • 「DB を冗長化」「サーバーを増やす」と言われた時、「読み込み? 書き込み? どっちのスケール?」と聞ける
  • 障害ニュースで「分断耐性のために可用性を取った」と書いてあったら「CAP の AP 型ね」と分かる
  • 「APIが落ちた」報告を受けた時、「Circuit Breaker は効いてた?」「リトライ嵐になってない?」と確認できる

考えてみよう

  1. あなたが今までに作ったシステムで、「2台目を増やす時に困りそうな」コードはどこ?
  2. もし全社員10人の会社で「マイクロサービスにします」と言われたら、何と返す?
  3. 「ユーザーの注文処理」を 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 検証の宝庫

カリキュラム本編で関連する章


次の読み物

08_クラウドの歴史.md をまだ読んでいなければ次に。クラウドの歴史 - オンプレからサーバーレスまで、というテーマ。

分散システムを「自前で頑張る」時代から、「クラウドにお任せ」時代へ移った経緯を辿る。AWS, GCP, Azure はなぜあの形になったか、サーバーレスは何を変えたか。次もスマホで気楽にどうぞ。

08. クラウドの歴史 - オンプレからサーバーレスまで