「COVID-19 のパンデミックは、仕事、勉強、社会生活の意味をリセットしてしまいました。多くの人がそうであるように、私も同僚とつながる手段として Microsoft Teams に頼るようになりました。この記事では、Microsoft Teams 製品グループの友人、Rish Tandon (コーポレート バイス プレジデント)、Aarthi Natarajan (グループ エンジニアリング マネージャー)、Martin Taillefer (アーキテクト) が、エンタープライズレベルの安全な生産性アプリの管理とスケーリングについて学んだことをご紹介します。」 - マーク ルシノビッチ (CTO、Azure)
スケール、回復性、パフォーマンスは一朝一夕には実現しません。ユーザーに喜ばれる製品を構築するには、持続的かつ計画的な投資、日々の積み重ね、そしてパフォーマンスを第一に考える姿勢が必要です。Teams はその開始以来、2017 年のローンチから 2019 年 7 月までには 1,300 万人のデイリー ユーザー、そして 2019 年 11 月には 2,000 万人のデイリー ユーザーを獲得するなど、力強い成長を遂げてきました。4 月には、Teams の 1 日のアクティブ ユーザー数が 7,500 万人以上、1 日の会議参加者数が 2 億人以上、1 日の会議時間 (分) が 41 億人以上に達したことを発表しました。これまでの Teams の急成長を考えると、このようなペースでサービスをスケーリングするために必要な継続的な作業について、わたしたち Microsoft は慣れているものだと考えていました。COVID-19 によりこの期待は砕かれました。しかしこのような経験があれば、これまでは考えられなかったような成長期の中でサービスを稼働させ続けることができるのではないでしょうか。
揺るぎない基盤
Teams はマイクロサービス アーキテクチャに基づいて構築されており、数百個のマイクロサービスが連携して、メッセージング、会議、ファイル、予定表、アプリなど、製品の多くの機能を提供しています。マイクロサービスを使用することで、Microsoft の各コンポーネント チームが独立して作業し、変更をリリースすることができます。
Azure は、Microsoft Teams を含む Microsoft のすべてのクラウド サービスを支えるクラウド プラットフォームです。Microsoft のワークロードは Azure 仮想マシン (VM) で実行され、古いサービスは Azure Cloud Services を通して、新しいサービスは Azure Service Fabric 上でデプロイされています。Microsoft の第一のストレージ スタックは Azure Cosmos DB で、一部のサービスでは Azure Blob Storage を使用しています。スループットと回復性を高めるために、Azure Cache for Redis を利用しています。Traffic Manager と Azure Front Door を活用して、トラフィックを必要な場所にルーティングしています。通信には Queue Storage と Event Hubs を使用し、テナントとユーザーの管理には Azure Active Directory を使用しています。
この記事では主に Microsoft のクラウド バックエンドに焦点を当てていますが、Teams のクライアント アプリケーションでは最新の設計パターンとフレームワークが使用されていることにも注目します。これにより機能豊富なユーザー エクスペリエンスを提供し、オフラインまたは間欠的に接続されたエクスペリエンスをサポートすることができます。サービスと連動してクライアントをすばやく更新するという主要機能は、迅速な反復作業を実現するための重要な成功要因です。Microsoft のアーキテクチャをより深く掘り下げたい方は、Microsoft Ignite 2019 のこちらのセッションをご覧ください。
アジャイル開発
Microsoft の CI/CD パイプラインは、Azure Pipelines 上に構築されています。Microsoft は、自動化されたエンドツーエンドのテストとテレメトリ シグナルの組み合わせに基づくゲートを使用した、リングベースのデプロイ戦略を採用しています。Microsoft のテレメトリ シグナルはインシデント管理パイプラインと統合されており、サービスとクライアント定義のメトリクスの両方についてのアラートを提供します。大部分の分析には、Azure Data Explorer を利用しています。
さらに、クラッシュ率、メモリ消費、アプリケーションの応答性、パフォーマンス、ユーザー エンゲージメントなどの主要な製品メトリクスに対する機能の動作を評価するスコアカードを備えた、実験的なパイプラインを使用しています。これにより、新機能が期待通りに動作しているかどうかを把握することができます。
Microsoft のすべてのサービスとクライアントは、一元化された構成管理サービスを使用しています。このサービスは製品機能のオンとオフを切り替えるための設定状態を提供しており、キャッシュの Time-to-Live の値を調整し、ネットワーク リクエストの頻度を制御し、API と通信するためのネットワークのエンドポイントを設定することができます。これにより、"ダーク ローンチ" や A/B テストを行うための柔軟なフレームワークが提供されるため、変更の影響度を正確に測定して、すべてのユーザーにとって安全で効率的な変更であることを確認することができます。
主な回復性戦略
Microsoft では、サービスのフリート全体でいくつかの回復性戦略を採用しています。
- アクティブ-アクティブ フォールト トレラント システム: アクティブ-アクティブ フォールト トレラント システムとは、2 つ (またはそれ以上) の運用上独立した異種のパスであると定義されます。各パスは安定している状態でライブ トラフィックを提供するだけでなく、期待されているトラフィックの 100 パーセントを提供する機能を持ちます。同時に、クライアントとプロトコルのパス選択を活用して、シームレスなフェールオーバーを実現することができます。Microsoft はこの戦略を、非常に大きな障害領域や顧客への影響が発生し、そして異種システムの構築と保守を妥当なコストで実施できる場合に採用しています。たとえば Microsoft は、外部から見えるすべてのクライアント ドメインに Office 365 DNS システムを使用しています。さらに、静的 CDN クラスのデータは Azure Front Door と Akamai の両方でホストされています。
- 回復性のために最適化されたキャッシュ: パフォーマンスと回復性の両方を実現するため、Microsoft はコンポーネント間でキャッシュを幅広く活用しています。キャッシュを使用することで平均待ち時間が短くなり、ダウンストリームのサービスが利用できない場合のデータ ソースが提供されます。キャッシュにデータを長期間保存するとデータの鮮度の問題が発生しますが、キャッシュにデータを長期間保存しておくことは、ダウンストリームの障害に対する最善の防御策となります。Microsoft は、キャッシュ データの Time to Refresh (TTR) と Time to Live (TTL) を重視しています。長い TTL と短い TTR 値を設定することで、データの鮮度をどの程度保つか、それに対し、ダウンストリームの依存関係に障害が発生したときにデータをどのくらいの期間維持するかを微調整することができます。
- サーキット ブレーカー: これは、失敗する可能性の高い操作がサービスで行われないようにする一般的な設計パターンです。これにより、再試行要求によって過負荷になることなく、ダウンストリーム サービスを復旧できるようになります。また、依存関係に問題が発生した場合のサービスの応答性も改善され、システムがエラー条件をより許容できるようになります。
- バルクヘッド分離: Microsoft は、重要なサービスの一部を完全に分離されたデプロイに分割しています。バルクヘッド分離は、あるデプロイで何か問題が発生した場合でも、他のデプロイが運用を継続できるように設計されています。この軽減策により、可能な限り多くのお客様の機能が維持されます。
- API レベルのレート制限: Microsoft は、重要なサービスがリクエストを API レベルでスロットルできるようにしています。これらのレート制限は、上記で説明した一元化された構成管理システムによって管理されます。この機能により、COVID-19 での急激な増加時に、クリティカルではない API のレート制限を実現することができました。
- 効率的な再試行パターン: Microsoft は、すべての API クライアントが効率的な再試行ロジックを実装していることを確認し、検証しています。これにより、ネットワーク障害が発生した場合のトラフィック ストームを防ぐことができます。
- タイムアウト: タイムアウト セマンティクスを一貫して使用することで、ダウンストリームの依存関係で何らかの問題が発生したときに作業が停止するのを防ぐことができます。
- ネットワーク障害の優れた処理: Microsoft は、オフライン時や接続不良時のクライアント エクスペリエンスを向上させるために、長期的な投資を行いました。この分野の主な改善点は、COVID-19 による急激な増加が始まったのと同じタイミングで運用環境にローンチされ、ネットワークの品質に関係なく、Microsoft のクライアントに一貫したエクスペリエンスを提供できるようになりました。
Azure Cloud Design Patterns をご覧になったことがある方は、これらの概念の多くはおなじみのものかもしれません。 また Microsoft はマイクロサービスで Polly ライブラリを広く使用しており、これらのパターンのいくつかの実装を提供しています。
Microsoft のアーキテクチャはとてもうまく機能していました。Teams の利用は月を追うごとに増加し、プラットフォームは需要に合わせて簡単にスケーリングすることができました。しかし、スケーラビリティとは "設定した後は忘れてもいい" ことではなく、複雑なシステムで顕在化する新たな挙動に対応するために、継続的な注意を払う必要があります。
COVID-19 により在宅勤務者からの注文が世界中で増え始めたとき、Microsoft は Microsoft のシステムに組み込まれたアーキテクチャの柔軟性を活用し、急増する需要に効果的に対応するためにすべての可能性を解き放つ必要がありました。
容量の予測
Microsoft は他の製品と同様に、生のユーザーおよび利用パターンの両方の観点から、増加がどこで起こるかを予測するためのモデルを構築し、常に反復処理しています。モデルは、過去のデータ、周期的なパターン、新規の大量顧客の流入などのさまざまなシグナルに基づいています。
急増が始まると、Microsoft の以前の予測モデルが急速に使えなくなることが明らかになったため、Microsoft はグローバルな需要の急激な増加を考慮に入れた新しいものを構築する必要がありました。既存ユーザーからの新しい利用パターン、既存ユーザーではあるが休止中であるユーザーの新規利用、そして多くの新規ユーザーの製品の新規利用が、すべて同時に発生しました。さらに、潜在的なコンピューティングおよびネットワークのボトルネックに対処するために、リソースの決定を迅速に行う必要がありました。Microsoft は、複数の予測モデリング手法 (ARIMA、加法的、乗法的、対数的) を使用しています。過剰な予測を避けるために、これに国ごとの基本上限を追加しました。Microsoft は、産業別および地域別の使用量ごとに変曲と増加のパターンの理解を試みることで、モデルを調整しました。ボトルネックとなっているリージョンのピーク時の負荷予測を補強するために、COVID-19 の影響日に関する国別のジョンズ ホプキンズの調査など、外部のデータ ソースを組み込みました。
このプロセス全体を通じ、Microsoft は慎重過ぎるぐらい慎重になり、過剰プロビジョニングを偏重していましたが、使用パターンが安定するにつれて、必要に応じてスケーリングを戻すことも行いました。
コンピューティング リソースのスケーリング
一般的に、Microsoft は自然災害に耐えられるように Teams を設計しています。複数の Azure リージョンを使用することで、データセンターの問題だけでなく、主要な地理的エリアの分断によるリスクを軽減することができます。ただしこれは、そのような事態が発生した場合に影響を受けたリージョンの負荷に対応できるように、追加のリソースをプロビジョニングすることを意味します。Microsoft はスケールアウトするために、すべてのクリティカルなマイクロサービスのデプロイを、Azure のすべての主要地域の追加リージョンにすばやく拡張しました。地域ごとのリージョンの総数を増やすことで、緊急時の負荷を吸収するために各リージョンで保持する必要がある予備の総容量を減らし、それによって必要な総容量を減らすことができました。この新しいスケールで負荷に対処することで、Microsoft は効率を改善する方法について、いくつかの分析情報を得ることができました。
- マイクロサービスの一部を再デプロイして、より多くの小規模なコンピューティング クラスターを利用できるようにすることで、クラスター単位でのスケーリングの考慮事項を避けることができ、それによりデプロイを高速化し、よりきめ細かい負荷分散ができるようになりました。
- 以前 Microsoft は、Microsoft のさまざまなマイクロサービスに使用するのに、特定の種類の仮想マシン (VM) に依存していました。VM の種類や CPU の観点からの柔軟性を高め、全体的なコンピューティング能力やメモリに焦点を当てることで、各リージョンで Azure リソースをより効率的に利用できるようになりました。
- Microsoft のサービス コード自体にも最適化の機会がありました。たとえば、いくつかのシンプルな改善により、アバター (イニシャルが入った小さな泡のようなもので、ユーザーの写真がない場合に使用されます) の生成に費やす CPU 時間を大幅に削減することができました。
ネットワークとルーティングの最適化
Teams の容量使用のほとんどは、特定の Azure 地域の昼間の時間帯に発生し、夜間にはリソースがアイドル状態になります。Microsoft は、このアイドル容量を活用するためのルーティング戦略を実装しました (コンプライアンスとデータ所在地の要件は常に尊重されています)。
- 非対話型のバックグラウンド作業は、現在アイドル状態の容量に動的に移行されます。これは、トラフィックが適切な場所に確実に配置されるように、Azure Front Door で API 固有のルートをプログラミングすることで行われます。
- 通話と会議のトラフィックは、急増に対応するため、複数のリージョンにまたがってルーティングされました。Microsoft は Azure Traffic Manager を使用して、観測された使用パターンを活用して負荷を効果的に分散しました。また、広域ネットワーク (WAN) のスロットリングを防ぐために、時間帯別の負荷分散を行う Runbook の作成にも取り組みました。
Teams のクライアント トラフィックの一部は、Azure Front Door が終点となります。しかし、より多くのリージョンでより多くのクラスターをデプロイすると、新しいクラスターでは十分なトラフィックが得られないことがわかりました。これは、ユーザーの場所と Azure Front Door ノードの場所の分散が原因でした。このトラフィックの不均一な分散に対処するために、Microsoft は Azure Front Door の国レベルでのトラフィックのルーティング機能を使用しました。この例では、フランスの追加トラフィックを Microsoft のサービスの 1 つを英国西部リージョンにルーティングした後、トラフィックの分散が改善されたことが示されています。
図 1:リージョン間のトラフィックをルーティングした後のトラフィック分散が改善されました。
キャッシュとストレージの改善
Microsoft は分散型キャッシュを多く使用しています。大規模な分散型キャッシュです。トラフィックの増加に伴い、キャッシュへの負荷も増加し、個々のキャッシュではスケーリングできないほどになっていました。Microsoft は、キャッシュの使用に大きな影響を与える簡単な変更をいくつか導入しました。
- キャッシュの状態を、生の JSON ではなく、バイナリ形式で保存するようにしました。これにはプロトコル バッファー形式を使用しました。
- キャッシュに送信する前にデータを圧縮するようにしました。圧縮率に対する速度が優れているため、LZ4 圧縮を使用しました。
ペイロードのサイズを 65% 削減し、逆シリアル化の時間を 40% 削減し、シリアル化の時間を 20% 削減することができました。これは、すべての面での勝利です。
調査の結果、いくつかのキャッシュの過度に積極的な TTL 設定が原因で、不必要なデータ削除が発生していることが判明しました。これらの TTL を増加させることで、平均待ち時間とダウンストリーム システムの負荷の両方を軽減することができました。
意図的な機能低下 (機能の使用制限)
わたしたちがどこまで前に進むべきか、まったくわからなかったため、Teams の容量をオンラインで追加するための時間を稼げるよう、予期せぬ需要の急増に迅速に対応できる仕組みを導入することが賢明であると判断しました。
すべての機能が Microsoft のお客様に等しく重要であるとは限りません。たとえば、メッセージの送受信は、他の誰かがメッセージを入力中であることを確認する機能よりも重要です。このため、Microsoft がサービスのスケールアップに取り組む 2 週間は、タイピング インジケーターをオフにしました。これにより、Microsoft のインフラストラクチャのいくつかの部分で、ピーク時のトラフィックが 30 パーセント減少しました。
Microsoft では通常、必要なデータをすぐ利用できるように、Microsoft アーキテクチャの多くの層で積極的なプリフェッチを使用しており、これによりエンドツーエンドの平均待ち時間を短縮しています。しかしプリフェッチは、今後まったく使用されることのないデータをフェッチする際にある程度の無駄な作業が発生し、プリフェッチされたデータを保持するためのストレージ リソースを必要とするため、コストが高くなる可能性があります。Microsoft はいくつかのシナリオでプリフェッチを無効にすることを選択し、待ち時間の長さを犠牲にして、いくつかのサービスの容量を解放しました。プリフェッチの同期間隔を長くしたケースもあります。そのような例の 1 つとして、モバイルでの予定表のプリフェッチを抑制することで、リクエストの量を 80 パーセント削減することができました。
図 2:モバイルでの予定表イベントの詳細のプリフェッチを無効にしています。
インシデント管理
Microsoft には、システムの健全性を追跡し、維持するために使用できる成熟したインシデント管理プロセスがありますが、この経験はまったく別物でした。トラフィックの急増に対応するだけではなく、Microsoft のエンジニアや同僚も、在宅での作業に適応しながら、個人的にも精神的にも困難な状況に直面していました。
Microsoft のお客様だけでなく、エンジニアもサポートできるようにするために、いくつかの変更を行いました。
- インシデント管理のローテーションを、週 1 回のペースから 1 日 1 回のペースに変更しました。
- すべてのオンコール エンジニアは、シフトの間に少なくとも 12 時間の休憩を取りました。
- 会社全体からより多くのインシデント マネージャーに加わってもらいました。
- Microsoft のサービス全体で、重要ではない変更をすべて延期しました。
これらの変更により、Microsoft のすべてのインシデント マネージャーやオンコール エンジニアは、お客様の要求に応えながら、自宅で自分たちのニーズに集中するための十分な時間を確保することができました。
Teams の未来
数年前にこれと同じような状況になっていたらどうなっていただろうか、と振り返ってみるのも興味深いことです。クラウド コンピューティングがなければ、このようなスケーリングは不可能だったでしょう。現在は構成ファイルを変更するだけでできることが、以前であれば新たな機器や建物を購入する必要があったかもしれません。現在のスケーリングの状況が安定してきたことで、Microsoft は未来に目を向けています。Microsoft のインフラストラクチャの改善には多くのチャンスがあると考えています。
- Microsoft は、VM ベースのデプロイから Azure Kubernetes Service を使用したコンテナーベースのデプロイへの移行を計画しています。これにより運用コストを削減し、アジリティを向上させ、業界との連携を図ることができると期待しています。
- Microsoft は REST の使用を最小限に抑え、gRPC などのより効率的なバイナリ プロトコルを採用する予定です。システム全体のいくつかのポーリング インスタンスを、より効率的なイベントベースのモデルに置き換える予定です。
- Microsoft は、Microsoft のシステムを信頼性の高いものにするために配置したすべてのメカニズムが、常に完全に機能し、すばやく動作するように、カオス エンジニアリングの実践を体系的に取り入れています。
Microsoft のアーキテクチャと業界のアプローチの足並みをそろえ、Azure チームのベスト プラクティスを活用することで、支援を求める声が上がったときに、データ分析、モニタリング、パフォーマンスの最適化、インシデント管理に至るまで、エキスパートがすばやく問題を解決することができました。Microsoft 全体の社員や、さまざまなソフトウェア開発コミュニティのオープンな姿勢に感謝しています。アーキテクチャやテクノロジーは重要ですが、システムを健全に保つのはあなたのチームなのです。
関連する投稿: Azure が COVID-19 に対応しました。
関連記事: COVID-19 のパンデミック時、Microsoft が Azure の容量を増やして顧客を支援。