分散システム
複数のコンピュータが協調して一つの系として振る舞う仕組み。規模と可用性を得る代わりに部分故障と非同期の難しさを抱える。
概要
分散システムは、ネットワークでつながった複数のコンピュータ(ノード)が協調して、利用者からは一つの系(システム)として見えるように振る舞う仕組みです。1台のマシンの中で完結する処理と違い、状態やデータが複数のノードにまたがって存在し、ノード同士はメッセージをやり取りしながら足並みを揃えます。「巨大な1台」ではなく「多数の小さな1台の集まり」で問題を解く、という設計思想です。
私たちが日常で触れる大規模サービスの裏側は、ほぼすべて分散システムです。数千万人が使うSNS、世界中に配信するストリーミング、常時止められない決済基盤 — これらは1台のサーバでは処理量にも耐障害性にも到底足りず、多数のサーバに負荷を分け、一部が壊れても全体は動き続けるように組まれています。マイクロサービスやレプリケーション、シャーディングといった語彙は、いずれもこの分散という土俵の上で語られます。
分散システムが与えてくれるのは「規模(スケール)」と「可用性(落ちにくさ)」です。ただしそれは無料ではありません。1台では起きなかった「一部だけ壊れる(部分故障)」「メッセージが遅れる・順序が入れ替わる(非同期)」「相手が死んだのか単に遅いのか判別できない」といった、分散ならではの難しさを丸ごと引き受けることになります。
なぜ生まれたか
かつては、性能が足りなければより高価で強力な1台のマシンに買い替える「スケールアップ(垂直分割)」が基本でした。しかしこのやり方には二つの壁があります。第一に、1台のマシンの性能には物理的な上限があり、青天井には強くできません。第二に、どれだけ高価なマシンでも壊れるときは壊れ、その1台が全サービスの単一障害点(そこが落ちると全部止まる急所)になってしまいます。
そこで「安価な普通のマシンを大量に並べ、横に足していく(スケールアウト、水平分割)」という発想が主流になりました。台数を増やせば処理能力は積み上がり、1台が壊れても残りで処理を続けられるため可用性も上がります。GoogleやAmazonのような、1台では到底さばけない規模のサービスを、壊れることを前提とした安価なハードウェアの群れで実現する — この現実的な要請が、分散システムの理論と実装を大きく前進させました。つまり分散システムは「もっと大きく、もっと落ちないように」という二つの欲求への、ハードウェアではなくアーキテクチャによる回答です。
詳細
何が本質的に難しいのか
分散システムの難しさは、突き詰めると「離れた複数の場所の状態を、遅延と故障がある通信だけで揃えなければならない」ことに尽きます。1台の中なら共有メモリを見れば全体の状態が一目で分かりますが、分散では各ノードが自分の手元しか直接は見えず、他ノードの状況はメッセージ越しの間接的な情報でしかありません。しかもそのメッセージは、遅れることも、失われることも、順序が入れ替わることもあります。
とりわけ厄介なのが「ネットワーク分断(partition)」です。ノード自体は生きているのに、ノード間の通信だけが途切れる状況を指します。このとき、応答が返ってこない相手が「壊れた」のか「生きているが連絡が取れないだけ」なのかを、外から確実に見分ける方法はありません。この見分けのつかなさが、後述の一貫性と可用性のトレードオフの根っこにあります。
一貫性と可用性のトレードオフ
同じデータを複数ノードに複製していると、あるノードで更新した内容が他ノードに伝わる前に読まれると、古い値が返ってしまいます。全ノードが常に同じ最新値を返すことを「一貫性(Consistency)」、どのノードもリクエストに必ず応答することを「可用性(Availability)」と呼びますが、ネットワーク分断が起きたときこの二つは同時には満たせません。分断された向こう側と同期できない以上、「古いかもしれないが応答する」か「正しさを守るためエラーを返す」かのどちらかを選ぶしかないからです。この根本的な制約を定式化したのがCAP定理です。
分断が去った後も、複製の食い違いをどう収束させるかという問題が残ります。多くの分散データストアは、いったんズレを許し、時間が経てば全ノードが同じ値に落ち着くことを保証する結果整合性を採ります。厳密な同期をあきらめる代わりに、可用性と性能を取る割り切りです。
協調のための道具立て
ノード同士が「揃った合意」を作るための仕組みが合意(コンセンサス)アルゴリズムです。RaftやPaxosといったアルゴリズムは、一部のノードが故障しても過半数が生きていれば一つの値に合意できることを保証し、リーダー選出や設定情報の共有などの土台になります。処理の直接呼び出しを避けてメッセージキューを挟めば、送り手と受け手を時間的に切り離し、相手が一時的に落ちていても後で処理を続けられます。ただしこうした非同期な連携では同じメッセージが二重に届くことがあり、何度実行しても結果が変わらない冪等性を処理側に持たせる設計が重要になります。
部分故障と観測可能性
分散システムでは「全部動いている」か「全部止まっている」の二択ではなく、「一部だけ壊れている」状態が常態です。ある1台の遅延が呼び出し元に連鎖し、待たされたスレッドが積み上がってシステム全体を巻き込む「カスケード障害」は典型的な落とし穴で、応答が返らない相手への呼び出しを早めに諦めるタイムアウトや、壊れた依存先を一時的に切り離すサーキットブレーカといった防御が欠かせません。
さらに、処理が多数のノードをまたいで流れるため、障害が起きても「どこで何が詰まったか」を1台のログを見るだけでは追えません。各ノードのログを集約し、システム全体の状態を可視化するモニタリング、そして一つのリクエストが辿ったノード間の経路を串刺しで追う分散トレーシングは、分散システムの運用における必須の観測手段です。「壊れないシステム」ではなく「壊れても気づけて復旧できるシステム」を目指すのが、分散システムとの現実的な付き合い方です。