BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ アーティクル Netflixにおける開発体験のスケールアップと成長

Netflixにおける開発体験のスケールアップと成長

キーポイント

  • 安定性とベロシティのバランスは時間と共に変化していく。新しい企業は安定性よりもベロシティを重視し、製品が成熟するにつれて安定性をより重視するのである。
  • 一貫性をもってサポートされた技術スタックは、アジャイル開発と安定したサポートプロセスを提供し、企業がベロシティや安定性を犠牲にすることなく迅速にスケールすることを可能にする。
  • テクノロジーを一貫性をもってサポートするかどうかは、サポートニーズの費用対効果とビジネスへの影響を考慮しながら、継続的なプロセスで決定する必要がある。
  • モノリポかマルチリポか?、自社開発か購入か?といった大きなビジネス上の決定は、長期的な影響力を持つ重要な選択となる。企業のニーズを分析し、アジリティ、安定性、継続的な開発コストに関して企業が何を重視するかに基づいて選択することが重要だ。
  • 集中管理型でサポートのある新しいテクノロジーを容易に試用し、それが成功しなかった場合に素早く失敗できることは、成長にとって不可欠な要素なのである。

デベロッパーエクスペリエンスの良し悪しは開発者が所属する企業の価値観、文化、ビジネスドライバーに大きく依存する。企業が成長すればその成長に合わせて価値観も変化し、開発者が必要とするコードの作成、デプロイ、保守の方法も変化する。この記事では開発者のニーズが変化する理由と時期、その変化を先取りする方法、そして変化が必要になったときに適応する方法について説明する。また私自身や同僚がNetflixで経験したことから、長年にわたって得た重要な学習や事例を紹介していく。

成長のインパクト

速度と安定性のバランスは企業が成長するにつれて変化していく。企業がその歩みを始めたばかりの頃はイテレーションを迅速にまわしてさまざまなアプローチを試す。そのための何かを素早く作ってテストし、そのアプローチを破棄するか拡張する必要があるのだ。このような「プロトタイピング」の段階では、失敗しても影響が限定的であるため、安定性への投資は必要ないことが多い。

製品のユーザー数が増えてくると、安定性と費用対効果に対する期待が高まってくる。その結果、製品の変更に対してより慎重かつ時間のかかるアプローチを取るようになり、安定性はより高い速度を維持する能力に影響を与えることになる。これは十分に確立された安定したCI/CDシステムを導入することで食い止めることができ、より高いベロシティを可能にする信頼性のレベルを作り出すことができるのだ。開発者が自分で行った変更が本番システムを壊さないと信じられるなら、彼らはイノベーションに集中でき、手動の検証作業に時間を奪われずに済むのである。

ビジネスが成長するにつれてリリースプロセスに対する信頼性レベルは非常に重要であり、テスティングピラミッドのような根拠のあるテストプラクティスは、プロセスを成功に導くものだ。このような方法を導入してもCI/CDプロセスが充実してくると検証プロセスに時間がかかるようになるので,ベロシティに影響を与える.この鈍化は製品システム全体の複雑さに比例して大きくなるのである。

この点を解説するために、Netflixでは長年にわたり、標準的なCI/CDプロセスに、特定のライブラリに対して障害を分離するためのライブラリのバージョン依存性ロック、自動統合および機能テスト、カナリアテストなど、多くの検証ステップを導入している。これらのステップはサービスの複雑さに応じて様々な時間がかかる可能性がある。もしサービスに多くの依存関係があれば、その依存関係のうちの一つがビルドプロセスで失敗し、デバッグが必要になる可能性は高い。機能的に複雑なサービスではテストの範囲も広くなり、特に統合テストや機能テストが多く必要な場合は、実行に時間がかかる傾向がある。複数の種類のトラフィックパターン(例:異なるデバイス、リクエストタイプ、データ要件)を実行するサービス上でカナリアテストを実行する場合はノイズを除去して、これらすべてのパターンを確実にカバーするために、より長いカナリアテストの実行が必要となるのだ。

上記のニーズに柔軟に対応するために、私たちはマイクロサービスを採用して機能範囲を分解してサービスを構成している。そうすることで依存関係の影響を小さくし、テスト時間を短くしてノイズの少ないカナリアリリースを実現したのだ。そして簡単なオーバーライドプロセス無しにリリースプロセスをブロックすることを避けるようにしている。依存関係のあるバージョンでビルドに失敗した場合、失敗したサービスのために以前のバージョンにロールバックまたはロックすることは簡単だ。テストの失敗を分析して前方に修正するか、無視するか(できれば後で再度評価する)、変更内容に応じて修正することができる。カナリアリリースにおける失敗は個別に原因を分析し、開発者は必要に応じてリリースを進める(=バイパス)ことが選択可能だ。CI/CDによる速度と安定性のバランス検討は、サービス管理者が自分たちの快適さのレベルやビジネスへの影響に応じて決定することが重要なのである。

ローカルツール VS 集中管理型ツール

ある時点で企業はビジネスニーズにあった技術を開発者が独断に選択して保守するのか、それとも推奨(または強制)される技術を企業が一貫性をもってサポートするのか、決断を迫られる。いわばローカル型ツールと集中管理型ツールの選択である。集中管理型では企業が製品のエコシステム全体に一貫性を持たせることができるという特徴がある。これはセキュリティ、洞察力、回復力、CI/CD等の統合や、アーキテクチャ、パターン、依存関係管理等のベストプラクティスの一貫した規定となる。このような一貫性は、ビジネス全体の観点からは非常に強力だが、特定のユースケースにとっては実際には有害な場合もある。一貫したアプローチを提供する単一のソリューションを定義した場合、ビジネスドライバーを成功させるために異なるアプローチを必要とするユースケースが必ずあるのだから。

一例として、私たちはサポートするサービス技術スタックとしてJavaとSpringBootを指定している。しかしデータエンジニアリングがビジネスニーズに合わせて、pythonやScalaを使用する必要があるユースケースも数多く存在しているのが現状だ。この例では、ビルドツールとしてGradleを多用している。これは、私たちが選んだ技術スタックでは非常にうまく機能するが、Scalaを使う開発者にとってはSBTの方が好ましいかもしれない。彼らのユースケースのためにGradleの提供を強化するか、ScalaコミュニティのためにSBTの使用を許可、及びサポートするかを評価する必要がある。

集中管理型ツールの利点とローカルツールのビジネスニーズのバランスを取ることが重要であり、そのためのトレードオフの評価は不可欠だ。そしてこの評価は絶えず進化していくプロセスである。どの時点で集中管理型ツールを検討すべきかは、データをもとに評価するべきだ。技術スタックを使用しているユーザー数、スタック上のワークフローのビジネスインパクト(収益)、スタックを集中的にサポートするには何人必要なのか?これらの要素をすべて考慮し、十分な優先順位付けと成長の余地があれば、その技術スタックは集中管理型に移行すべきなのである。

Netflixは自由と責任を推進する文化を持っているため、開発者がユースケースに対して独自のソリューションを選択し、その選択に責任を持つことを決定するのをよく見かける。これはビジネスへの影響が少ない小規模なユースケースには、最適な選択肢だ。しかし、影響規模が大きくなる可能性の場合つまり、多くの人がその技術を使い始める、あるいはユースケースのビジネスへの影響が大きい場合は、この選択は長期的にはビジネスに不利になる可能性がある。技術をサポートする人が一人しかいない場合、迅速に行動したり拡張する能力のボトルネックとなり、その人が別のプロジェクトに異動すると、技術をサポートできる人がいなくなり技術負債になりかねない。

集中管理型のサポートですべてのユースケースを網羅できないため、私たちはレイヤーアプローチを採用している。これらのコンポーネントはローカライズされたアプローチでは独立して使用(管理)できるが、サポートされるエコシステム全体を「購入」すればさらに集中的に管理できるようになる。私たちはこれを「Paved Path」(舗装された道)と呼んでいる。一般的な開発者にとってはPaved Pathを利用する方がはるかに楽だ。一方で特殊なビジネスケースでは自己管理し、独自の方法を選択することもできる。その場合はサポートされていないものがうまくいかないと開発者の時間を余分に奪う、将来的にPaved Pathに移行するためのコスト(それがサポートされれば)、コストが高すぎる場合にエコシステムからその技術要素を取り除く、といった決定に伴う責任を明確できるのである。

この決定プロセスを説明すると、Paved Pathを進むと新しいテクノロジーへのサービスの移行が必要になることが頻繁に起こるというものだ。場合によってはレガシーサービスを新しい技術に移行するための混乱とコストは、問題が発生した場合にのみサービスに開発者の時間を費やすよりも低い先行価値としてみなされる。これは最近 Log4Shellの脆弱性が発生し、全フリートの log4j のバージョンを繰り返しアップグレードする必要があった際に実際に経験したことである。Paved Pathにあるサービスでは開発者は完全に手作業だが数時間以内に完了した。Paved Pathを通っているサービスでは必要なやりとりは最小限でありターンオーバーは1日以内に行われたのである。Paved Pathが整備されていないサービスでは、開発者が数日がかりで集中的にデバッグを行い何度もプッシュを繰り返す必要があった。しかし大局的に見ればこれはPaved Pathに移行するよりもコスト効率が良く、ビジネスへの影響も少なかった結果である。

モノリポかマルチリポか?

残念ながら企業がモノリポ戦略かマルチリポ戦略か、どちらを選択すべきか明確な答えはない。なぜなら、どちらのアプローチも製品がスケールするにつれて大きな欠陥が生じるからだ。現状で言える大きな違いは製品ベースの割合に対するリリース速度である。モノリポの場合は製品のサブセットに対するリリースをターゲットにすることは(設計上)困難だ。例えばコードの変更や新バージョン(JDKのバージョンなど)をリリースしたい場合、アプリケーションのオーナーが積極的にその変更に同意するのは簡単ではないだろう。特にモノリポでは新しい変更をリリースできるようになる前にすべての製品の検証に合格することが必須であるためリリースが大幅に遅くなる可能性があるのだ。

一方Netflixのマルチリポは新しいライブラリのバージョンが公開されるとCI/CDプロセスが依存関係のアップデートを自動検知して、そのライブラリを使用するアプリケーションの更新が走るという、リリースに対してとても多用途で高速なアプローチをとっている。このアプローチでは個々のアプリケーションの所有者が、使用するコード変更のバージョンを良い意味でも悪い意味でも定義することができ、公開と同時に使用可能になる。それでも、このアプローチにはいくつかの重大な欠点がある。依存関係のバージョン管理は非常に複雑で、バージョン解決のためにデバッグする責任はライブラリを使用するアプリケーション側にある。Netflixがどのようにこの複雑性を解決しているのか、詳細は「Nebulaを用いたスケールでの依存性管理」を参照されたい。あるサービスが新しいライブラリをリリースした場合、99%の人々にとっては完全に実行可能でも、ごく一部のアプリケーションでは特定し解決しなければならない依存関係の問題が発生することがしばしば見受けられるのだ。

長期的には、私たちはマルチリポ・モノリリースアプローチを可能にするハイブリッドアプローチに移行つつある。単一のリポジトリライブラリ所有者は新しいバージョンをリリースできるが、ライブラリ使用者のために集中管理のパイプラインでライブラリをビルドしてテストを通過する必要がある。そしてパイプラインが失敗した場合は、ライブラリ開発者に問題解決のステップを決定する責任があることにしている。

技術スタックの収束

企業全体を一貫した技術スタックに移行させる方法について話し合うときはいつも「アメとムチ」という比喩が当てはまる。魅力的な新機能を提供して、人々が自ら選択するほどPaved Pathを守ることを魅力的にするのか(アメ)、開発者にPaved Pathから提供されるものを使うように強制するのか(ムチ)。Netflix ではムチはレバレッジの高い、あるいはビジネス・インパクトのあるニーズに留めておくことにしている。

常にアメが採用されるアプローチが理想的な状態である。集中型のアプローチは特定のユースケースにはメリットがあるかもしれない。ビジネス全体の観点から見ると、高いレバレッジがかかっていることがある。このような場合、個々の開発者にとってはアメにあたるものが少なく、既存の開発ワークフローに余計なハードルや複雑さを追加してしまうことさえある。このような場合、私たちは企業の利益のために行動する責任を強調し、それがなぜ重要なのかの理由を明確に提示する必要性があるのだ。余分な負担はできる限り減らし、一貫したアプローチのメリットを示すようにしている。

まれに、一貫した技術スタックを提供するために、個々のチームにとって新しいスタックへの移行の優先順位が他の優先順位よりも高く指示される、トップダウン式のアプローチをとることもある。これは通常セキュリティ上の理由(前述の Log4Shell のケース等)や、一貫した技術スタックの全体的なビジネス上の利点が個々のチームのニーズを上回る場合(例えば移行の最終段階で、残りの使用例に対するサポートのコストが高くなりすぎて維持できなくなった等)に起こるのである。

自社開発か、既製品の購入か?

完全に自社で開発する場合と、既製品を使用する場合に分類してみることにする。Netflixでは可能な限りオープンソース(OS)のプロダクトを採用しており、多くのOS製品を開発・使用している。

可能であれば、OSのものを優先して「購入する」方に傾く。要件に合致し、コミュニティが活発なOSプロジェクトが見つかれば、それがもっとも有力な候補になるものだ。しかしOSがない場合や、既存のプロジェクトと機能的に大きな差がある場合は、自社で構築することを検討する。機能的なニーズが少ない場合は、自社で構築・保守を行うのが一般的だ。プロジェクトが大規模であったり、外部への影響力が大きい場合は、OSプロジェクトとしてリリースすることを検討する。

オープンソースを選択した場合、自社でOSプロジェクトを公開するか、外部のプロジェクトを利用するかに関わらず、いずれの場合も開発コストが発生する。プロジェクトを公開する場合、コードや機能のレビュー、ミートアップ、社内の使用状況との整合性など、製品を取り巻くコミュニティを構築するためのコストが発生する。これらのコストはすぐにかさみ、人気のあるOSの提供にはOSの管理にフルタイムで働く開発者が少なくとも1人必要、などという例が頻繁に見られる。外部製品を使用する場合、コミュニティとの関係を維持することが重要だ。製品に貢献し、将来の方向性やニーズに影響を与え、社内の使用状況と一致させる必要がある。また、一方で外部製品の方向性が社内の利用方法と大きく異なる場合、あるいはOSプロジェクトが解散してしまう場合はリスクとなり得る。

デベロッパーエクスペリエンスの変化

企業の規模が大きくなるにつれて、エンジニアの組織も大きくなり、一貫性がより重要視されるようになる。成長段階の初期では、各開発者は複数のスタックやドメインにまたがって作業し、スタック全体を管理することになりがちである。スタックが大きくなるにつれ、特定の部分に力を注ぐ必要性が明らかになり、スタックに複数の人が携わるようになる。ワークフローに関わる人が増え、スタックの特定部分に特化するようになると、本来自分が気にする必要のないものを最適化する機会が増える機会が生まれる。つまり、インフラ、抽象化、ツールの集中化が進むのだ。また中央集権的なコンポーネントをサポートする少人数のグループでも、多くのビジネス固有の開発者に対応することが可能だ。

また企業が大きくなるにつれて技術や要求が常に変化し、過去に失敗したものが現在では最善の解決策になり得ることを受け入れる必要がある。そのためには失敗を受け入れつつも「早く失敗して、再挑戦する」という姿勢を確立することが必要だ。例えば、私たちは長い間、A/Bテストのシステムを使って、Netflixのユーザーベースで新しい機能や要件を試してきた。そして、視聴者にとって有益でないと判断された機能は破棄する。また製品が進化したり視聴者のニーズが変化した場合には後日に再度その機能を検証することもある。

これは、マルチリポエコシステムにリリースする前に、ライブラリリリースの候補を検証するために使用されている。公開された各候補に対して、設定された受け入れテストの閾値で依存関係の下流の消費者をすべて実行し、候補リリースによって引き起こされた失敗についてライブラリ製作者にフィードバックを提供し、オプションでライブラリビルドの一部としてリリースのゲーティングを自動化することを行った。残念ながら、通常のCIワークフローから帯域外のビルド環境を提供することは難しく、「what-if」の依存関係に対してコンパイルレベル以上のフィードバックを提供することは困難であった。また、当初予定していたのと同じインフラを使用して宣言的CIを追求するつもりはないことが分かり、再評価する必要があった。その代わりに既存のJenkins インフラストラクチャの上に API、抽象化、および機能を提供する Rocket CI を介して、プルリクエストに基づく機能に投資したことで、Jenkinsのビルド環境の仕様に縛られることなく、これらの新機能に投資することができたのである。

社内の技術的プラクティスの例として、マルチリポエコシステムにリリースする前にライブラリのリリース キャンディデートを検証するための「パブリッシャーフィードバック機能」を導入している。これは、公開されたリリース キャンディデートについて、設定された受け入れテストの閾値のもと、依存関係のある下流でこのライブラリを使用するアプリケーションをすべて実行し、リリース キャンディデートによって引き起こされた失敗についてライブラリ製作者にフィードバックを提供するものである。さらにビルドプロセスの一部としてリリースのゲートキーパーの自動化についても実現した。

残念ながら、通常のCIワークフローからアウトオブバンドでビルド環境を提供するのは難しいため、「what-if」の依存関係についてコンパイルレベル以上のフィードバックを提供することは困難だった。また、当初予定していたのと同じインフラを使用して宣言的CIを追求するつもりはないとわかったため、再評価を行う必要があった。そのかわりに、既存のJenkinsインフラストラクチャの上にAPI、抽象化、および機能を提供するRocket CIを通じて、プルリクエストベースの機能に投資した。これにより、Jenkinsのビルド環境の仕様に縛られることなく、これらの新機能に投資することができたのである。

急成長する企業ではたらくくエンジニアリング・マネージャーへのアドバイスは、「たとえ過去に失敗したとしても、新しい挑戦を恐れないこと」である。技術や要件は常に変化しており、過去の失敗が、現在では最善の解決策になっているのかもしれないからだ。

作者について

この記事に星をつける

おすすめ度
スタイル

BT