QCon SFで,Suudhan Rangarajan氏が,"Netflix Play API: Why We Built an Evolutionary Architecture"と題したプレゼンテーションを行った。講演の要点は次のとおりだ — 単一のアイデンティティと責務を持つサービスは維持や更新が容易である。サービスを開発する場合は,実施すべき中核的な決定により多くの時間を費やして,それらが徹底的な議論と迅速な実験を必要とする"タイプ1"あるいは"タイプ2"であるかを判断すべきだ。"進化的アーキテクチャ"を設計し,適合関数などのツールを用いることが,多くの利益をもたらす。
NetflixのシニアソフトウェアエンジニアであるRangarajan氏のプレゼンテーションは,同社が2016年に達成した,2つの重要なビジネスマイルストンに関する話から始まった。これらはいずれも,エンジニアリングにも大きな影響を与えている。ひとつは,1月にリリースされた"#netflixeverywhere"である。これは,これまでNetflixが提供されていなかった多くの国のユーザがサインアップして,コンテンツを視聴できるようにするものだ。もうひとつのマイルストンは,11月に新たにリリースされた"Download & Go"機能で,コンテンツをデバイスにダウンロードして,オフラインで視聴することを可能にする。いずれのリリースも"Play API"に大きな負担をかけることになる。このAPIは,特にユーザへのストリーミングを開始するための責務を持っている。この結果,いくつかのサービスの停止やデプロイ頻度の低下,ロールバックの増加などが発生した。Forsgren,Humble,Kim各氏の共著"Accelerate"にあるように,これららはソフトウェアの提供パフォーマンスに関わる重要なメトリクスである。
氏はまず,以前のPlay API Serviceアーキテクチャの概要を説明した。ユーザのデバイスは,エッジ(Zuul APIゲートウェイと思われる)上で動作するAPIプロキシサービスに接続し,Play APIを含むいくつかのAPIをサポートするモノリシックAPIと通信する。次に,このAPIサービスがドメイン毎のマイクロサービスと通信し,ユーザのダウンストリーム要求を処理する。
講演の残りの部分は3つのセクションに分けられ,モノリシックAPIサービスに実施された最近の機能拡張に関する状況や基本概念に関して,より詳細な議論が展開された。セクションは以下の3つで構成された。サービスのアイデンティティ -- サービスの存在する理由を探る。タイプ1および2の決定の区別 -- 長期的な影響があり,より多くの先行投資を必要とする判断は何かを決定する。進化可能性 -- 変更要求や制約とともに進化可能なサービスの構築方法を探る。
サービスのアイデンティティに関するセクションでは、Rangarajan氏が、エンジニアは"常に疑問を持ち"、サービスの責務を判断するためにその存在理由を問うべきだ、と示唆した。Play APIのモチベーションチェーンは、"世界中で数十億の人々を楽しませるために、インターネットTV革命をリード"したいというNetflixの希望に始まり、サインアップからストリーミングまでのユーザエンゲージメントの最大化、さらには"取得、検索、プレイバック機能を24時間利用可能にする"ことにまで連なっている。聴衆に対しては,単一責務の原則を再確認した上で、このエクセサイズを行う場合には"複数のアイデンティティをひとつのサービスにまとめる"ことを回避するべきだ、と警告した。凝縮度が低く結合性が高いという、アーキテクチャ上のアンチパターンを持ったサービスの開発につながる可能性があるからだ。そのためにPlay APIチームが最初に行った大きな変更は、既存のモノリシックなAPIサービスを"機能単位のAPIサービス"モデルに分割することだった。Play APIは再構築され、マイクロサービスとしてデプロイされた — これは"タイプ1"の決定として行われた。
当社のサービスには、シンプルな単一のアイデンティティがあると考えています。アイデンティティは企業、組織、チーム、および相手となるサービスのアイデンティティに関連し、それを補完します。
講演の"タイプ1と2の決定"のパートは、このJeff Bezos氏による意思決定モデルに関する情報源の説明から始まった。タイプ1の決定は非常に重要かつ長期に及ぶ影響を持つものであるため、多くの人々との協議の下で慎重に行う必要がある。タイプ2の決定は容易に変更可能であり、長期にわたる影響もないので、"高度な判断力を持つ個人ないし少数グループ"によって迅速に行われるべきである。Play APIチームが定義したタイプ1決定は、適切な結合度、同期対非同期通信、データアーキテクチャの3つであった。
いくつかの決定は重要かつ不可逆的、あるいは不可逆的に近い -- 一方通行の -- ものでした。ですから慎重に、注意深く、ゆっくりと、協議を重ね、助言を求めつつ実施する必要がありました。これらはタイプ1であると言えるでしょう ...
最初の結合度の件について、Rangarajan氏は、マイクロサービスベースのアーキテクチャ設計においては、実質的に2つのタイプの共有ライブラリが存在すると述べている。共通機能を提供するライブラリと、サービス間通信に使用されるクライアントライブラリである。共通機能に共有ライブラリを使用する -- "ユーティリティパッケージ"のように -- 場合には、過度の"バイナリ結合"が容易に発生し、ライブラリのメンテナンスやアップグレードを難しいものにする。一方のクライアント通信ライブラリでも、"運用上の結合"が容易に発生する。例えば、クライアントライブラリ内で意図的なフォールバック機能を提供すると、過度にリソースを消費することによって、障害の連鎖を引き起こす可能性があるのだ。通信ライブラリではさらに、上流のサービスチームがJavaのクライアントライブラリのみを提供するといったように、"言語的な結合"が引き起こされる場合もあり得る。
これら問題の存在と,現行の要件の特定と議論とを鑑みた結果,Play APIチームは,開発を計画している新たなサービスでは共有"ユーティリティ"ライブラリの使用を最小限にするという決定をした。さらにサービス間の通信には,JSONとHTTPを介したRESTではなく,gRPCを使用することも決定した。これにより,Protocol BuffersによってRPCメソッドとエンティティを定義し,さまざまな言語用のクライアントライブラリ/SDKを自動生成することが可能になった。この"適切な結合"に関するアドバイスを要約すれば,タイプ1の決定は"自動生成した'シン’クライアントによる双方向通信の検討と,サービスバウンダリを越えたコード再利用の最小化",ということになる。
第2のタイプ1決定である同期対非同期が,続いて議論された。検討の結果としてチームは,Play APIとサポートするサービス間にリクエスト/レスポンス形式以上のインタラクションは必要ないという結論に達した。そのため,ブロッキング形式のリクエストハンドラと,発信用のサービス間呼び出しのための非ブロックI/Oを実装することに決定した。
議論はチームが遭遇した第3のタイプ1決定である"データアーキテクチャ"へと進み,Rangarajan氏は"データアーキテクチャは意図しなければ,それ自体がひとつのモノリスになる"と警告した。先程のPlay APIアーキテクチャではいくつかのサービスが同じデータソースをアクセスするため,結合度が高くなり,サービスとその基本となるデータスキーマ双方の進化の道筋が狭くなる,という結果をもたらすことになる。氏は意味深な微笑みを浮かべながら,David Wheeler氏の"コンピュータ科学における問題はすべて別レベルの間接化によって解決される"という言葉を引用しつつ,議論と分析を重ねたチームが,中間データローダと,実質的なサービスと下位データソース間のマテリアライズドビューの実装であるデータ層を実装したことを説明した。
要約すれば,データアーキテクチャに関するタイプ1の決定についてのアドバイスは,次のようなものになる。
サービスからデータを分離すること。最低限でも、データソースが抽象化層経由でアクセスされるようにすることで、後で拡張する余地を残すこと。
講演のこのセクションでの最後のアドバイスはタイプ2の決定に関するもので,"パスの選択,実験,反復"によって行われるべきだ,というものだ。サービス開発時の意思決定において指針となるのは,直面する決定の種類を見極めることに集中する,という原則である。
タイプ1とタイプ2の決定を識別することです。あなたの時間の80パーセントを,タイプ1の決定のための議論や調整に費やしてください。
講演の最後の部分では"全体像"アーキテクチャの原則を取り上げ,Neal Ford,Rebecca Parsons,Patrick Kua各氏による著書"Building Evolutionary Architectures"からの引用として,"進化的アーキテクチャは複数の局面における第1原則として,方向性を持った漸進的な変革をサポートする",という一節が最初に紹介された。Rangarajan氏は,これまで議論を続けたタイプ1の決定の最終的な成果として,必要とする形式の進化をサポートする上で適切な結合度を備えたマイクロサービスアーキテクチャが実現するはずだ,と主張した。その上で氏は,将来の変化を監視と指針の手段として,さらにはPlay APIチームが信頼性よりも単純さ(フォールバックが障害のカスケードを発生させるように),スループットよりもスケーラビリティ(大規模キャッシュがパフォーマンスを向上する反面,キャッシュの立ち上がりに要する時間のため,スケーラビリティの面では望ましくないように)を優先したように,アーキテクチャ設計において避けられないトレードオフに議論を集中するための手段として,"適合関数(Fitness Functions)"の可能性について論じた。
結論として氏は,新たな変更を実施してから1年間,運用上の問題が発生していないと同時に,週平均4.5回のデプロイメントというチームの目標が,ロールバック2回のみでほぼ達成されている,と述べた。
講演のビデオと内容を記録した"Netflix Play API: Why We Built an Evolutionary Architecture"がInfoQで公開されている。