(本記事は Improving app resilience | Autodesk Forge の意訳版です。)
パンデミックはインターネットの重要性を再認識されています。ところが、リモートワークやオンラインミーティングなどによって、インターネット トラフィックの急激に増大してしまい、インターネット接続が不安定になったり、パフォーマンスに悪影響が出る問題が顕在し始めるようになっています。
帯域幅を確保・改善するために、オンラインミーティング時にビデオをオフにしたり、一時的に WiFi 接続を止めてスマートフォンのテザリングを利用したりしなければならないことも多々ありました。
そのため、劣化や障害が発生することを想定してアプリやサービスを設計することが重要になっています。同時に、そのような事態が発生した際にどう対応するかという戦略も必要です。
主な対応領域
まずは、アプリのアーキテクチャを設計し、運用環境を計画する際に注意すべき主な機能・分野からご紹介します。
- アラート:何かに障害が発生した場合、アプリケーションは適切な対応チームに警告/通知する機能
- テスト:ユーザーのワークフローを適度な頻度でテストすることで、アプリケーションが問題を自己診断する 機能
- メトリクス:メモリ、ディスク、エラーレートなど、アプリケーションの健全性を追跡・監視するための十分なメトリクスを提供する機能
- ログ:意味のある情報を含むコードの実行による出力する機能
自動化を進めれば進めるほど、障害の発生が問題に発展、影響が大ききなる傾向があります。 ただ、マネージドサービスのように、プラットフォームに組み込まれた機能を使って外部に依存出来る部分も存在します。 例えば、ご自身で MongoDB インスタンスをホスティングした場合には、メンテナンス、モニタリング、バックアップなどが必要になりますが、これらは、AWS DocumentDB, Azure CosmoDB や Google MongoDB などのプラットフォームのサービス プロバイダに依存することも出来るわけです。
サンプル アプリ
ここでは、3D モデルを表示するシンプルな Forge アプリを考えてみます。このアプリには、認証の実行、ファイルのアップロードと SVF/SVF2 変換を処理するいくつかのコードが組み込まれています。ブラウザ上で動作する静的コンテンツ(HTML、 JavaScriptファイル)は、サーバーサイド(認証)と Forge(viewable 情報)に依存しています。
ハイレベル アーキテクチャ
早期の決断がアプリの将来を左右することもありますが、少なくとも時間の経過とともにアプリがどのように変化していくかを考慮することが重要です。
一般的な Web アプリでは、コードと静的ページの両方を同じサーバーでホストしています。 欠点は、コードに障害が発生すると、静的ページが提供されず、サイト全体がダウンしてしまうことです。 別々のサーバーを用意すれば、静的ページは引き続きクライアントに提供され、より適切なエラーメッセージを表示したり、基本的なオフライン機能を提供したりすることが出来るので、そのような事態を防ぐことができます。 いずれにしても、ロードバランサーは、定義されたポリシー(CPU 使用率、トラフィックなど)に基づいて、必要なサーバーの数を定義したり、どのサーバーを再起動するか(エラー率、レイテンシーなどに基づいて)を決定します。
より高速にスケールアップ/ダウン出来る最新のアーキテクチャでは、サーバーコーディングをサーバーレスで実行したり、CDN(content-deliver network)を介して静的なページを提供したりすることが考えられます。 主要なクラウド インフラ プロバイダは、そのようなソリューションを提供しています。
- AWS Lambda & Cloud Front
- Azure Functions & CDN
- Google Serverless & Cloud CDN
Forge のサンプルは、シングルサーバ(安価)でスタートし、最終的にはマルチサーバ(コードと静的)に移行するか、あるいはサーバレスアプローチ(スケーラブル)に移行することが出来ます。これは、サーバーとコードの実装が独立して動作するように開発されている限り、可能(または容易)です。.NET や Nodejs の場合、コードはルーティングした endpoint のみを実装し、静的ページは別個に開発され、サーバーコードとは分離されています。
クライアントコードの実装
アーキテクチャの準備ができたところで、サンプルアプリの静的ページをクライアントに提供します。ブラウザ上で動作する JavaScript のコードは、サーバーのコードが動作していなくても、なんとか動作させる必要があります。 さまざまなフレームワーク(React、Angular、Vueなど)があり、それぞれが独自のリトライ機構を持っています。ここではForge Viewerに焦点を当ててみましょう。読み込みが予期せず失敗した場合は、数秒後にモデルの読み込みを数回再試行するか、この記事で説明したようにキャッシュバージョンを持つことを検討することが出来ます。: Disconnected workflows.
サーバーコードレベルの実装
コードは、接続の問題からサービスのダウングレード/ダウンまで、使用している API プロバイダで起こりうる問題に対応出来るようにすべきです。様々な言語でそれらを実装するライブラリや戦術がいくつか存在します。
最も基本的な機能は、エラーコード 5xx(500 番台)で失敗したコールを再試行することです。4xx エラーのほとんどのケースでは、再試行の前に入力データを変更する必要があり、401 や 403 のように、新たな認証が必要になる可能性が高いものもあります。例外は 429(レートリミット)ですが、これは後述する特定のケースです。
ピーク時トラフィック増大が予想さにれる場合は、キューイングシステムを導入するのも良いアイデアです。今回のサンプルアプリでは、(メモリ消費量やスループット・トラフィックのために)一度に転送するデータの最大量が決まっている場合があります。アプリはそのジョブをキューに入れて、一度に最大 x 個のファイルを処理することができます。 キューの副次的な利点として、処理が失敗した場合に再試行し、アプリを監視したり、処理が予想よりも少し時間がかかっていることをユーザに通知する方法を提供出来る点があります。
いくつかの言語固有のライブラリを紹介します。:
- .NET:Polly は、リトライ、ウェイト&リトライ、サーキットブレーカー、フォールバックなどの方法を提供します。Hangfire のキューイングは、着信コールの管理に役立ちます。
- Nodejs: node-fetch や Axios を使用する典型的なプロジェクトでは、node-fetch-retry や axios-retry を使用することができます。Bee-queue はインカムコールの管理に役立ちます。
Forge サービス
Forge に接続する場合、いくつかの特徴があります。 :
キューイング(Queueing):Model Derivative ジョブ、Design Automation ジョブ、Reality Capture シーンをリクエストすると、それぞれの API がリクエストをキューに入れ、サーバーが利用可能になり次第、処理が行われます。 これには、その時点でのサービスの混雑状況やファイルの複雑さに応じて、数秒から数分かかることがありますが、キューの時間を出来る限り短縮することを目指していますが、お客様のアプリでは、リクエストの処理にかかる時間に加えて、この待ち時間も考慮する必要があります。
レートリミット(Rate-Limit):Forge に限らず、すべてのサービスには endpoint ごとに異なる Rate Limit(呼び出し数制限)が設定されています。アプリケーションは 429 レスポンスを受信した場合、x 秒後に待機して再試行する必要があります("retry-after " レスポンス ヘッダーに従います)。アプリは、待機中にハングアップしたりフリーズしたりしないように準備しておく必要があります。 各 Forge サービスの Rate Limit ドキュメントをご覧ください。
再試行(Retry):以前にコードが動作していて、入力データに大きな変更がなかったと仮定すると、ランダムな問題で API リクエストが失敗した可能性が考えられます。典型的なレスポンスは 5xx です。このようなケースでは、数秒後に最大で数回再試行することが妥当です。 例外として、504 エラーコードがあります。これは、サービスが時間内に応答しなかったことを意味します。例えば、ジョブの処理を開始するために POST メソッドの endpoint を呼び出し、サービスが 60 秒以内に応答せず 504 を返したが、62 秒で呼び出しを処理したというシナリオを考えてみましょう。バックエンドは、現在、アプリのジョブを処理しており、別の POST endpoint で再試行すると、別のジョブがキューに入ります。504 の後に即座に POST を呼び出して再試行するのではなく、まず GET メソッドの endpoint を呼び出してジョブがキューに入ったかどうかを確認すべきです。
Webhooks:Model Derivative ジョブと Design Automation の WorkItem ジョブでは、処理が成功、または失敗した際にアプリケをコールバックする機能があります。 これにより、Forge サービスがプロセスを実行している間も、ユーザがアプリとのやり取りを続けることができます。 Forge がアプリをコールバックする際には、すぐに 200 を返し、後でアクションを実行することが重要です。ここでキューイング システムが重要になります。 コールバックはサーバーに送られ、サーバーはそれをクライアントに中継する必要があります(WebSocket の利用が考えられます)。
サンプルアプリでは、Model Derivative のレートリミットを確認することをお勧めします。入力データが大幅に変更されていない(ユーザーエラーなど)と仮定して、POST Job endpoint のリトライ ポリシーと、変換処理が status:failureで失敗した場合の再試行を設定するのは合理的です。 Webhook は、変換処理が完了したときに通知することが出来ます。アプリは、メッセージをキューに入れ、WebSocket(.NET SignalR、または Nodejs の socket.io)を使用して、モデルをロードするようにクライアントに通知します。
さらに学習するには
- スケーラブルで復元性の高いアプリのためのパターン by Google
- クラウドネイティブの回復性 by Microsoft
- Achieving 99.99% uptime - a tale of Observability(英語)by Autodesk
- Tips and Tricks for Building and Testing Successful Cloud Applications and Services(英語)by Autodesk
- AWS の Autodesk Forge, by Autodesk
By Toshiaki Isezaki
コメント
コメントフィードを購読すればディスカッションを追いかけることができます。