前回の続きです。今回はシステムデザイン編。
実体験にもとづいて、なるべく雰囲気を再現しようとしてますが
・問題はすべて自作
・人物、会話等はすべてフィクション
なのでよろしくお願いします。実際の会話はNDAにより公開できません。
同じくらいの難易度の問題を、こんなレベルでやり取りして、最終的にはお祈りされました。
~前回までのあらすじ~
GAFAのコーディング面接1回目を何とか乗り切ったyambe2002だが、休む間もなく次の面接が始まって辛い。
出題
ぼく「………」
面接官「あれ?yambe2002?大丈夫?」
ぼ「…はっ!ごめんちょっとボーっとしちゃった。大丈夫大丈夫。えーと、何だっけ?」
面「あー、分かる分かる!面接の連続で疲れるよねー!ぼくの時もそうだったよ」
ぼ「ははは…」
面「じゃもう一回言うね。ぼくからの問題はね、ミュージシャン名で検索すると、関係するコンサートの情報を表示するWebサイトをデザインしてほしいんだ。開始時間や料金なんかだね」
ぼ「コンサート情報を検索できるサイト…ミュージシャンで検索…」
面「うん」
ぼ「Webサイトのデザインか。えーとえーと、あー」(時間を稼ごうとしてる)
面「…」
ぼ「えー、あ、これはクラスをデザインするのかな。それともシステムデザインかな」
面「システムデザインだね。コードは書かなくてもいいよ」
ぼ「OK。じゃまずは、要件やユースケースを詰める必要があるけど」
面「うん」
ぼ「ちゃんとやるなら、それだけで何時間も使っちゃうよね…」
面「それはそうだよね。まあ部分部分はその都度、それっぽく仮定していいよ。この面談では、システム的な思考ができるかどうか、技術的な議論ができるかを見たいんだ。厳密なデザインは求めてないよ」
ぼ「了解。じゃまずは…」(ホワイトボードを拭きながら)
ユースケースと仮定
ぼ「Webサイトって言ってたね。これはインターネットに公開するサイト?」
面「そうだね」
ぼ「だとユーザーは世界中から訪れる。だれでも使えるの?それとも、ログインさせる?」
面「どうしようかな。簡単のため、誰でも使えるようにしようか」
ぼ「アーティスト名のほかにも音楽ジャンルやコンサート名とかでも検索できるのかな」
面「うーん、アーティスト名だけにしようか。ユーザーのブラウザには、アーティスト名の入力欄と、検索ボタンがあるだけ。検索すると、結果が表示される」
ぼ「結果はコンサート一覧だっけ」
面「そう」
ぼ「コンサート情報から、例えば購入サイトに飛んだり?」
面「本来はそうなるだろうけど、今回は単純にいこう。コンサート一覧が表示されるだけ」
ぼ「了解」
面「よさそう?」
ぼ「ここまでは。それで、コンサートやアーティストの情報は、どこから引っ張るの?だれが入力するの?」
面「そこはOut of scopeでいいよ」
ぼ「じゃWrite用のAPIやサービスは作らないよ。あとはえーとえーと」
面「…」
ぼ「あ、Read/Write比は?Readのほうがずいぶん多いだろうね」
面「うん。1000:1でReadが多いことにしよう」
ぼ「かなりRead Heavyなサービスだね。アクセス件数はどれくらいを想定してる?」
面「月に250万の検索リクエストということにするよ」
ぼ「だと……(筆算して)だいたい1秒に1件くらいか…DBへの書き込みはその1000分の1だからかなり少ないな…」
面「…」
ぼ「あとは何があるかな…Well…」(必死に教科書を思い出そうとしている)
面「……」
ぼ「そうだ!アクセスにはスパイクがあるよね」
面「スパイクというと?」
ぼ「ある時間帯にアクセスが集中したり、ある検索ワードだけリクエストが多かったり」
面「ああ、もちろんそうだね」
ぼ「それに、ヘビーユーザーからのアクセスが極端に多かったり」
面「それもあり得そうだね」
ぼ「そして多分、可用性(Availability)が大事なサービスだね」
面「そう考えていいよ」
ぼ「これくらいあれば次に進めそうかな…」
面「OK?」
ぼ「だと思う。まずハイレベルデザインを書くね。冗長化なんかは随時やるよ(ペンを取る)」
デザイン1
ぼ「シンプルにデザインするとこうかな」
面「ふむふむ」
ぼ「念のためもう一度いうけど、これは最低限のハイレベルデザインだよ。これが機能したら、ベンチマーク・ロードテスト・プロファイリングでボトルネックを特定して、Iterativeに改善していくのが良いプラクティスだと思うんだ(本の受け売り)」
面「なるほど。じゃあこのデザインを説明してくれる?」
ぼ「OK。まずはClient。これはエンドユーザーのWebブラウザだね」
面「うん」
ぼ「一応、DNSも書いておいたけど」
面「このサービスのIPアドレスを解決するために必要。それはそうだね」
ぼ「そしてユーザーはサービスを利用するため、Web Serverにアクセスする」
面「Web Serverは何をするの?」
ぼ「えっ?えーと、ユーザーが直接アクセスするサービスで…最終的なHTMLを供給するし…ええとあとはSSLの終端にもなったり…」
面「うん。内部サービスへのRoutingをしたりするのかな。ここにあるRead APIサービスとか」
ぼ「そう!」
面「Read APIを直接、ユーザーにOpenしたらダメなの?」
ぼ「へっ!?そ、それは良いプラクティスじゃないね。えーと、内部のサービスをOpenにするのはセキュリティリスクが高まるし…」
面「ふんふん?」
ぼ「サービスが増えたときに困るし……スケールするのも難しくなる」
面「なるほど。続けて?」
ぼ「う、うん(落ち着け…落ち着け…)。ふう。説明のために、情報の流れをちょっと書き込んでみるね!」
デザイン2
ぼ「こうかな。文法は省略してるよ」
面「なるほど。まずこのクゥールはユーザーからのReadリクエストを表してるんだね」
ぼ「く、クゥール?」
面「あれ?違う?ユーザーからのGETかと思ったけど」
ぼ「(クゥール…クゥール…あ、curlか!!)そうそう!」
面「だよね」
ぼ「アーティスト名をクエリにもったGETを表してるよ」
面「それをWebサーバーが受け取る」
ぼ「うん。そしてURLやクエリをもとに、対応するサービスへRoutingしてるね。ここでクエリのクリーンアップもしてる想定」
面「クリーンアップというと?」
ぼ「え、えーと、キーワードの大文字小文字を統一したり…とか」
面「なるほど」
ぼ「(ほっ)そして、この例ではアーティスト名でRead APIに問い合わせをしてる」
面「ふんふん」
ぼ「Read APIは受け取ったアーティスト名で、データベースにSQLで問い合わせてる。ここでは関係データベースを使ったよ」
面「関係データベースにした特別な理由はある?」
ぼ「えっ。えっとKey-ValueストアとかのNoSQLももちろん使えるけど…今回はたぶん…アーティストテーブルとコンサートテーブルみたいなのを持って、JOINして問い合わせするよね。実装が自然かなと思ったんだ」
面「なるほど」
ぼ「データ数も莫大じゃないし…えーと月250万件の、1000分の1だから…」
面「……」
ぼ「厳密に計算するならちゃんとテーブル設計しないといけないけど、アーティスト情報もコンサート情報も数百Kb程度と仮定すると…ええと…」
面「まあ大したことはなさそうだね。せいぜい月に数千件、多くてもGBオーダーも行かないくらい?」
ぼ「うん。もっとちゃんとスキーマ設計したほうがいい?それとも全体のデザインを議論する?」
面「全体の話をしよう。DBにはアーティストとコンサートテーブルがあって、必要な列が定義されてる仮定で」
ぼ「OK。えーと、で、何だっけ」
面「Read APIが関係データベースにクエリを投げたところだよ」
ぼ「そうだそうだ。そしてRead APIはクエリの結果をもとにコンサート一覧を取得し、Web Serverに返す」
面「このJSONだね」
ぼ「そう。そしてWeb Serverが最終的なHTMLを作り、Clientへ送る」
面「なるほど。良さそうだね」
デザイン3
ぼ「まあこれで一通り機能はしそうだけど、色々と問題がある」
面「どんな問題?」
ぼ「まず、冗長化がまったくされていないことだね。どのボックスも単一障害点になってしまってる」
面「そうだね。どうやって解決する?」
ぼ「基本は並列に稼働させることだね。たとえばWeb Serverならいくつかのサーバーを立ち上げる」
面「うんうん」
ぼ「まあ外部に面するWeb Serverが複数あるとDNS設定が面倒になりそうだから、その手前にロードバランサを置くかなぁ…こっちはMaster-slaveフェイルオーバーを使う(カキカキ)」
面「いいんじゃないかな。同じことがRead APIやデータベースにも言えそう?」
ぼ「そうだね。Read APIも複数稼働して、その前にロードバランサを置く。データベースは…ちょっと違うけど…」
面「こういった可用性パターン(Availability Pattern)で気を付けることは?」
ぼ「どのサービスも、なるべくステートレスに作ることかなあ。ステートを持つとスケールするのが難しくなるはず。たとえばWeb Serverなら、セッション情報を別のキャッシュに保存するとか」
面「いいね。データベースの可用性を上げるにはどうしたらいい?」
ぼ「たとえばレプリカが使えるね。Master-Slaveレプリカを採用して、Read APIはSlaveからデータを読むとか(カキカキカキカキ)」
面「Good!」
デザイン4
面「あ、あと5分もないや。じゃぼくから。さっき君はIterativeに改善するのが良いプラクティスって言ってたよね」
ぼ「う、うん。定期的にベンチマーク、プロファイラを走らせてボトルネックを見つけて改善だね」
面「じゃあそれで、そうだな、Read APIのレイテンシが大きい問題というが発見されたら、どんな解決がある?あ、描かなくていいよ」
ぼ「えーと、たとえばキャッシュを使うってデータベースへのアクセスを減らすとか」
面「具体的には?」
ぼ「そうだね。一例としては、クエリ文字列をKey、結果をValueにしたMapを持つ」
面「なるほど」
ぼ「データベースなら、あとはスキーマを見直すとか、SQLチューニングとか、Federationを導入するとか色々あるね。それこそNoSQLを考えるのもオプション」
面「なるほどなるほど。いいね」
ぼ「(よし、良い感じだ!)キャッシュはアクセスのスパイク対応にもなるし!」
面「OK。じゃ次。このシステムを監視するにはどんな方法が考えられる?」
ぼ「うぇっ!?(やばっ、ぜんぜん勉強してないぞ)えーっと、えーっと、まずその、考えられるのは人力での定期的なチェックだね。えーとSREの人たちが日常的に(だめだこの回答は…!考えろ考えろ…!)」
面「…」
ぼ「えーと、そうだログ!それぞれのサービスが、アクションのたびにログを出力するようにする」
面「そうだね。ログはいつでも有用だね」
ぼ「監視用のサービスを作ってもいい。まああんまり複雑にすると、そっちに手間がかかって本末転倒になりそうだけど」
そして次の面談へ
面「知ってると思うけど、うちの会社もクラウドサービスを展開してるんだ」
ぼ「もちろん知ってるよ!(使ったことないけど…)」
面「うちのクラウドサービスはね、さっき話した監視サービスやデータベースのレプリケーション、サービスの冗長化なんかを解決して…自動化で…AIが…うんぬんかんぬん~~」
面3「(唐突に)やあ!!」
面「やあジョン!紹介するよ、こちらはyambe2002。こちらはジョン。部署Cのプロジェクトマネージャだよ」
ぼ「ナイストゥミ―チュー!」
面3「やあyambe2002。会えて嬉しいよ!」
面「それじゃねyambe2002。Good luck!」
面3「さて、ぼくからは技術じゃなくてBehavioralなことを色々聴きたいんだ。過去のプロジェクトやリーダー経験なんかだね。まずはね…」
ぼ「」(すでに瀕死)
まだまだ続く