月曜日、私はGoogleのForward-Deployed-Engineerのシステム設計面接を受け、そして落ちた。コンポーネント――キャッシュ、キュー、シャード、ロードバランサー――は知っていた。プレッシャーの下で私ができなかったのは、エンジニアとアーキテクトを分けるたった一つのことだった。そこで私はその週を丸ごと使って、このスキルをゼロから組み直した。以下は、入る前に知っておきたかった形で整理した、私が見つけたことだ。
こんな投稿が話題になっている――"These 15 articles will turn you from engineer to architect"(訳:この15本の記事があなたをエンジニアからアーキテクトに変える)――Instagramの設計、Netflixのアーキテクチャ、キャッシュ、Kafka等へのリンク付きだ。どれも良い記事である。だが15枚の設計図を読んだからといってアーキテクトになれるわけではない。15個のレシピを読んでもシェフになれないのと同じだ。設計図が示すのは何をであり、面接が試すのはどうであって――そしてその「どう」とは、たった一つの習慣に尽きる。
唯一の習慣:何を諦めてよいかを言葉にする
「Instagramを設計せよ」と言われたエンジニアは、見慣れた箱に手を伸ばし、後付けで理屈をこねる。アーキテクトはまず別の問いを立てる。自分は何を犠牲にしてよいのか?実在するあらゆるシステムは、同時にはすべて手に入れられないものたちの間の交渉だ。そして、その有名な原則には名前がある。
CAPだ。この三文字に、考え方のすべてが詰まっている。ネットワーク分断が起きたとき(そして大規模になれば必ず起きる)、そのデータについて維持できるのは一貫性か可用性のどちらか一方であり、両方は無理だ。銀行口座の残高は一貫性を守るために書き込みを拒否し、「いいね」の数は可用性を守って後で整合を取る。アーキテクトはどちらを犠牲にするのかを声に出して言い、なぜそうするのかも語る。「このデータについては一貫性より可用性を優先する。古い『いいね』は許容できても、拒否された決済は許容できないからだ」――この一言にこそ、この仕事の本質が宿っている。
十五枚の設計図は語彙にすぎない。トレードオフこそが文法である。単語を増やして覚えたところで、この言語を話せるようにはならない。
アーキテクトのループ——プレッシャー下でも回せる方法
実際に私が犯した失敗はこうだ。どのアーキテクチャが正しく、どれが無駄になるかを決める数字を固めるより先に、設計図に飛びついてしまったのである。そこで私は、この習慣を、どんな面接でも声に出して回せる五段階のループに変換した。これはあらゆる回答の骨格としても使える。
アーキテクトのループ。 青いステップは、以前の自分ならSketchまで一気に飛ばしていたところだ。面接で評価されるのはConstrain・Estimate・Stress・Evolve——つまり暗記ではなく判断力が問われる部分である。
肝心なのはステップ2だ。概算の数字がすべてを決める関門となり、後続のあらゆる選択を左右する。デイリーアクティブユーザー数 × アクション数 × ペイロード=書き込みQPS。読み取りは通常、書き込みの10〜1000倍だ。ストレージは1日あたりのデータ量×保持期間で決まる。こうした数字がなければ、キャッシュにせよシャード数にせよキューにせよ、何ひとつ正当化できない。できるのは推測だけであり、面接官には推測かどうかが見抜かれる。数字さえあれば、片方のアーキテクチャが明らかに正しく、もう片方が明らかに無駄だとわかり、その根拠を語れるようになる。
語彙を、それが決める意思決定ごとに再分類する
こうして15個のトピックは、単なる読書リストであることをやめ、4つの意思決定グループへと姿を変える。それぞれについて暗記する価値があるのは、非自明な判断——つまり優秀なエンジニアほど間違えがちなポイントだけだ。
グループ1 — データプレーン(状態がどこに存在するか)
| トピック | 非自明な判断 |
|---|---|
| データベースの選択 | 選ぶ基準はアクセスパターンであり、流行りではない:まずホットクエリをモデル化し、それをO(1)にできるストアを選ぶ。ACIDとリレーションが必要ならSQL、大量書き込みのフィードならワイドカラム。シャードキーが存在する理由は、ホットクエリを単一のシャードに収めるためだ。 |
| キャッシング | 難しいのは速度ではなく、無効化だ。TTLは実用的なデフォルトであり、罠となるのはthundering herd(コールド/期限切れキーで発生する問題。訳:群衆殺到現象)——リクエスト・コアレシングと stale-while-revalidate(陳腐化データを返しつつ裏で更新する方式)で解決する。cache-asideは、そのキャッシュ更新方式に対応する一般的な書き込みポリシーだ。 |
| データ構造 | これらはストレージ媒体に対応している。B木はディスクシークを最小化し(SQLのインデックス)、LSM木は書き込みに強い(Cassandra)。コンシステント・ハッシングのリングは、ノードの参加・離脱時に移動するデータ量を最小化する。ブルームフィルタは、ヒットしないと分かっているルックアップを安価にスキップする仕組みだ。 |
グループ2 — 玄関口と契約
| トピック | 非自明な判断 |
|---|---|
| ロードバランサー vs リバースプロキシ | 問うている質問がそもそも違う。ロードバランサーが答えるのは"too much traffic for one box"(訳:1台のマシンには荷が重すぎるトラフィック)という問いであり、同一のバックエンド群にトラフィックを分散させる。一方、リバースプロキシが答えるのは"I need a smart front door"(訳:賢い玄関口が欲しい)という問いだ(TLS終端、ルーティング、遅いクライアントをアプリから遠ざけるバッファリングなど)。単一サービスの前段に置くこともでき、多くのゲートウェイは両方の役割を兼ねている。 |
| REST / API | ステートレス性こそが要点だ。各リクエストが自身のコンテキストを持ち運ぶので、どのサーバーでも処理できる。これが、ロードバランサーの背後で水平スケールを可能にする性質だ。冪等性は、分散システムにおいてリトライを安全にするものだ。RESTの画一性がコストになる場面では、gRPC(内部通信・低レイテンシ・ストリーミング向け)やGraphQL(クライアントに合わせたペイロード向け)に手を伸ばすとよい。 |
グループ3 — 非同期のバックボーン(時間軸での分離)
| トピック | 非自明な判断 |
|---|---|
| Kafka/キュー | リプレイ可能なログは、プロデューサーとコンシューマーを時間とレートの両面で切り離す。順序が保証されるのはパーティション単位であって全体ではない——だからこそ、本当に守りたい順序を保てるようにパーティションキーを選ぶ。本当に見るべき健全性指標はコンシューマーラグであって、スループットではない。 |
| マイクロサービス | その導入動機は技術的というより組織的なものだ。コンウェイの法則が働いている——チームがそれぞれ独立してデプロイできるようにするための組織設計だ。プロセス内呼び出しを手放し、失敗しうるネットワークホップを引き受ける代わりに、ディスカバリ、リトライ、サーキットブレーカー、トレーシング、そして分散トランザクションに代わるサガ(sagas)を手に入れる。逆説的な真実がある:ほとんどのシステムは、きちんと構造化されたモノリスとして始めるべきだ。 |
| アーキテクチャパターン | それぞれが、結合度と柔軟性のトレードオフ、あるいは一貫性とスケールのトレードオフを取る。イベント駆動(疎結合で結果整合性)、CQRS(読み取りと書き込みのモデルを、負荷が乖離した時点で分離する)、strangler-fig(モノリスを段階的に移行する)などがそれだ。パターン名を挙げること自体はスキルではない——そのパターンがどの制約を緩和するのかを理解していることこそが重要だ。 |
グループ4——2つのケーススタディ(一律の戦略が破綻する場所)
InstagramとNetflixが有名なのは、どちらも分布のテール(tail)について鋭い教訓を与えてくれるからだ。
Instagramはハイブリッド・ファンアウト方式を採る。 一律の戦略はテールで破綻する。プッシュなら読み取りはO(1)で済むが、セレブアカウントでは書き込みが爆発的に増えてしまう。プルなら書き込みは軽いが、今度は読み取りが遅くなる。答えは両方であり、アカウントごとに使い分ける。一律のルールがテールで破綻する瞬間を見抜けるかどうか——それこそがテストの本質だ。
Netflixはその逆の分割を教えてくれる。分けているのは巨大でキャッシュ可能なデータプレーン(動画は事前に複数のビットレートへエンコードされ、ISPに近いCDNエッジへプッシュされる)と、小さく動的なコントロールプレーンだ (認証、レコメンドなど、クラウド上で動くマイクロサービス群がこれにあたる)。そしてNetflixは、障害を「普通に起こること」として扱う。実際、システムが生き残れることを証明するために、意図的にインスタンスを落とす——これがカオスエンジニアリングだ。ここからアーキテクトが持ち帰るべき教訓は——障害が起きることを前提に設計し、バイトはユーザーの近くへ動かす、ということだ。
忘れていた最後の一手
月曜にはできなかったが、今はこうしている。ハッピーパスのスケッチを終えたら、声に出して障害モードを一通り検討するのだ。このノードが落ちたらどうなる?このキューが詰まったら?このキャッシュが冷えたら?このシャードが過熱したら? そのうえでボトルネックと、次のスケーリングの一手を名指しする。障害シナリオを欠いた設計は、しょせんエンジニアの答えにすぎない。ボトルネックを名指しし、それをどう乗り越えるかを語ること——それこそがアーキテクトの答えであり、評価される部分なのだ。
この手法をひと息で言うなら
問題を制約し(Constrain)、数値を見積もり(Estimate)、その数値が要求する道筋をスケッチし(Sketch)、障害に対して負荷をかけて検証し(Stress)、そして進化させる(Evolve)——ボトルネックと次の一手を名指しする。すべてのトレードオフを声に出して言う。それだけだ。十五本の記事を知っていることと、十六本目を自分で設計できる人間になること。その違いはここにある。
月曜にシステム設計面接に落ちた。金曜にはそれがフレームワークになっていた——そして正直なところ、合格していた場合よりも、この分野への理解は今のほうが深い。失敗を人に教えられるものへと変えたこと、それが身につくきっかけになった。あなたも準備しているなら、十五本の記事を答え探しのために読むのはやめよう。それらはトレードオフを読み取るために読み、どちらを選ぶかを毎回声に出して言う練習をしてほしい。
Google FDEのシステム設計面接に落ちたあとに書いたものだ。「15の記事でengineerからarchitectへ」というリーディングリストを、その根底にあるたった一つの習慣に落とし込んだ。これが、私よりも落ち着いた気持ちで面接に入るための助けになれば、それで役目は果たしたことになる。 — Paul