モノリス:動くか動かないか。
分散システム:半分は動いてて、残り半分は動いてるかもしれなくて、どっちがどっちか分からない。
これが根本的な問題。モノリスでは、障害は全面的。プロセス死んで、全部止まる。ダウンしてると分かる。
分散システムでは、1つのサービスが失敗して9つは動く。失敗してるサービスは遅いだけで死んでないかもしれない—だからタイムアウトがトリガーしない。リクエストが溜まる。技術的には何も失敗してない、ただ30秒かかってるだけだから、サーキットブレーカーが発動しない。
ユーザー → API Gateway → サービスA → サービスB → ???
↓
サービスC(遅い)
↓
タイムアウト... 最終的に
ユーザーに何て言う?何が起きたか分からない。
ネットワークは分断する。パケットはドロップする。レイテンシはスパイクする。「遅い」と「ダウン」の区別がつかない。
前: 関数コール → 結果(0.001ms)
後: ネットワークコール → 多分結果(5-5000ms)
すべてのネットワーク境界が新しい障害モード:
リトライロジック、冪等キー、サーキットブレーカー、ヘルスチェックが必要。昔は1行だった関数呼び出しに。
サーバーが3台ある。それぞれにクロックがある。一致しない。
サーバーA: 10:00:00.000
サーバーB: 10:00:00.123
サーバーC: 09:59:59.998
これらのサーバー間でイベントを順序付けてみろ。「この書き込みはあの読み取りの前に起きた」—どうやって分かる?分からない。NTPは助けるけど解決しない。
だから分散システムは論理クロック、ベクタークロック、または時間順序への依存を完全に避ける。
エレベーターピッチは聞いたことあるだろ:一貫性、可用性、分断耐性—2つ選べ。
現実:分断耐性は選ばない。分断は起きる。ネットワークは失敗する。唯一の選択は:分断中、古いデータを返すか(可用性)、エラーを返すか(一貫性)?
ほとんどのシステムは可用性を選ぶ。ユーザーは少し古いデータより エラーを嫌う。
リーダーが書き込みを受け取る。フォロワーにレプリケート。レプリケーション完了前にリーダーが死ぬ。
書き込みは起きた?誰に聞くかによる。
クライアント: 「書き込み確認!」
フォロワー1: 「受け取ってない」
フォロワー2: 「受け取った」
新リーダー: フォロワー1
ユーザーデータ: 消えた
だから分散データベースにはレプリケーション確認設定がある。同期レプリケーション:遅いけど安全。非同期:速いけどロスある。
複数のノードに何かに合意させるには複数のラウンドトリップが必要。Raft、Paxos、何でも—レイテンシを払う。
頻度の低い操作には問題ない。ホットパスでは、コンセンサスがボトルネックになる。だから分散キャッシュが存在する:一貫性をスピードとトレードオフ。
分散システムを「複数のコンピューターが俺のコードを実行してる」と考えるのをやめろ。
こう考えろ:「複数のコンピューターが俺のコードを実行してて、互いを信頼してなくて、全員嘘ついてるかもしれなくて、接続はランダムに失敗する」
障害を前提に設計しろ。すべてが壊れると仮定しろ。壊れたとき何が起きるかテストしろ。
分散システムとは、聞いたこともないコンピューターがお前のアプリをクラッシュさせられるシステムだ。
— blanho
RedditはコメントシステムをPythonからGoに移行した。マイグレーションの華やかでない現実。
NetflixはKafka、Cassandra、3つのキャッシュレイヤーを引き剥がした。すべてのキャッシュは嘘だから。
同期呼び出しは動くまでは動く。動かなくなったらメッセージキューが必要だ。その理由がこれ。