第2章: 設計

コードを1行書く前に、システムエンジニアは根本的な問いに答えなければなりません:どんな問題を解決するのか?設計プロセスは、曖昧さが明確さに変わり、要件がインターフェースに蒸留され、トレードオフが変更コストが高くなる前に明示される場所です。優れた設計は、信頼性の高いシステムが構築される基盤です。

プラネタリスケールコンピューティングの文脈では、設計に追加の次元が加わります。単一のマシンで動作するシステムが何千ものサーバーに分散させると壊滅的に失敗することがあります。10人のユーザーにとって洗練された設計が1000万人では崩壊することがあります。設計プロセスは最初からスケール、障害、進化を見越す必要があります。

問題定義

すべてのシステムは問題から始まります。問題定義はシステムが達成すべきこと、誰にサービスするか、どんな制約の下で動作するかを定義します。よく書かれた問題定義は設計の決定を導くのに十分具体的でありながら、実装の詳細への早期のコミットメントを避けるのに十分一般的です。

本書で構築しているシステムを考えてみましょう。echoシステムの問題定義はシンプルです:クライアントからメッセージを受け取り同じメッセージを返す。ディスカバリシステムの問題はより微妙です:サーバーが増減する中でシステム名を受け取り、そのシステムを実装する健全なサーバーのアドレスを返す。ストレージシステムの問題にはさらに別の次元が加わります:プロセスの再起動やハードウェア障害にまたがってデータを永続的に保存する。

各問題定義は暗黙的にシステムのスコープを定義します。ディスカバリシステムはサーバーを発見しますがリクエストをルーティングはしません。ストレージシステムはデータを保存しますがメモリにキャッシュはしません。スコープを狭く保つことは最も重要な設計原則の一つです。システムが多くのことをやろうとすると理解しにくくテストしにくく運用しにくくなります。

設計ドキュメント

設計ドキュメントは問題定義を具体的な計画に変換します。通常4つのセクションを含みます:システムが公開するインターフェース、維持するデータ構造、使用するアルゴリズム、受け入れるトレードオフです。設計ドキュメントは設計者と実装者の間の契約です——たとえ同一人物であっても。

インターフェースセクションは他のシステムがこのシステムとどのようにインタラクションするかを定義します。私たちのシステムではインターフェースは型付き引数と結果を持つRPCプロシージャとして定義されます。コンフィグレーションサービスは5つのプロシージャを定義します:get、set、delete、list、watch。各プロシージャには一意の識別子、リクエスト構造体、レスポンス構造体があります。

データ構造セクションはシステムがどんな状態を維持しどのように整理するかを記述します。キャッシングサービスは高速検索のためのハッシュマップとLRU順序のためのデキューを維持します。ストレージサービスはインメモリハッシュマップ、ライトアヘッドログ、定期的なスナップショットを維持します。

アルゴリズムセクションはシステムがリクエストをどのように処理するかを記述します。多くのサービスではこれは単純です:リクエストのデシリアライズ、データ構造に対する操作、レスポンスのシリアライズ。コンセンサスのようなより複雑なシステムでは選挙プロトコル、ログレプリケーション、状態マシンの適用が記述されます。

トレードオフセクションはおそらく最も重要です。すべての設計決定にはトレードオフが伴い、それを明示することで後の驚きを防ぎます。キャッシングサービスはメモリと速度をトレードします。ストレージサービスは書き込みレイテンシーと耐久性をトレードします。コンフィグレーションサービスは整合性と可用性をトレードします。

インターフェースファースト設計

私たちのシステム全体に現れるパターンはインターフェースファースト設計です。共有ライブラリ(lib.rs)がサーバー(main.rs)が実装する前にインターフェースを定義します。これにはいくつかの利点があります。

第一に、設計者にシステムをクライアントの視点から考えることを強制します。クライアントはどんな操作を必要とするか?どんなデータを送受信するか?この外側からの思考は実装から始めて外側に向かう場合よりもクリーンなインターフェースを生み出します。

第二に、並行開発を可能にします。インターフェースが定義されたらクライアントはスタブやモックを使用してインターフェースに対して記述でき、サーバーの実装と並行して進められます。

第三に、自然なバージョニング境界を提供します。インターフェースが変更されるとプロシージャ識別子が変更され、新旧両方のバージョンが移行中に共存できます。

リソース

すべてのシステムはリソースを消費します:CPU、メモリ、ストレージ、ネットワーク帯域幅、ファイルディスクリプタなど。優れた設計はリソース使用を考慮し予算を設定します。キャッシングサービスにはメモリ使用量を制限するMAX_CAPACITYがあります。モニタリングサービスにはメトリックごとのデータポイント数を制限するMAX_METRIC_WINDOWがあります。

リソース予算は相互に作用します。メモリを少なく使うシステムはCPUを多く必要とするかもしれません(圧縮のため)。ネットワーク帯域幅を少なく使うシステムはストレージを多く必要とするかもしれません(バッチ処理のため)。これらの相互作用を理解することは設計プロセスの重要な部分です。

管理

管理できないシステムはスケールで運用できません。設計には最初から管理の懸念を含める必要があります:システムはどのように設定されるか?ヘルスはどのように監視されるか?どのようにデプロイ・更新されるか?

私たちのシステムはコンフィグレーションモニタリングディスカバリサービスとの統合を通じてこれらの懸念に対処します。各サービスは起動時にディスカバリに登録しモニタリングにメトリックを報告しコンフィグレーションからランタイムパラメータを読み取ります。この管理インフラはビジネスロジック自体と同じくらい重要です。

設計プロセスは反復的です。最初の設計が最終設計になることはめったにありません。実装が予期せぬ課題を明らかにしテストがエッジケースを露出し運用が実世界の動作を表面化させる中で設計は進化します。重要なのは設計を明示的で改訂可能にすることであり最初の試みで完璧にすることではありません。