コンテンツに移動
デベロッパー

バックグラウンド作業に Cloud Run の「常時稼働」 CPU 割り当てを活用

2022年4月11日
https://storage.googleapis.com/gweb-cloudblog-publish/images/image5_YeYyFgz.max-2000x2000.png
Google Cloud Japan Team

※この投稿は米国時間 2022 年 3 月 29 日に、Google Cloud blog に投稿されたものの抄訳です。

昨年秋、Cloud Run チームは、オペレーターが標準のリクエスト サイクル外にコンテナ インスタンスの CPU を常時割り当てることのできる新しい機能を発表しました。デフォルトでは、Cloud Run インスタンスにはリクエストの処理中、コンテナの起動中、シャットダウン時にのみ、インスタンスのライフサイクルに従って CPU が割り当てられます。今回のリリースによりこの動作を変更できるようになり、コンテナ インスタンスに(起動している限り、受信リクエストがなくても)常時 CPU を割り当て、利用できるようにすることが可能になります。CPU を常に割り当てるように設定すると、バックグラウンド タスクやその他の非同期処理タスクの実行に利用できます。

本日の Serverless Expeditions 動画では、この機能を活用した気象警報アプリケーションを同僚の Martin とともに紹介します。アプリにアクセスしたエンドユーザーは、サポートされている米国の州(または地域、計 55 か所)の略称を入力して、最新の気象注意報を入手できます。このアプリは米国政府が支持する米国国立気象局(NWS)の API を活用しています。

お気づきのとおり、気象警報は一過性のものであり、定期的に期限切れとなります。同アプリが直面したのはこの問題です。ユーザーがサイトにアクセスすると、アプリケーションはリクエストごとに API 呼び出しを行わなければならず、レスポンスのレイテンシの平均は最新のテスト結果で最長 1024.2 ミリ秒となりました。リクエストごとに API 呼び出しを行うのではなく、Cloud Firestore や Cloud Datastore のような高速でスケーラブルかつ高可用性の NoSQL ソリューションを共有キャッシュ レイヤとして使用したらどうでしょうか。

エンドユーザーがリクエストした州(または地域)は同じ地点に対する次のリクエストに備えてキャッシュに保存されるため、API 呼び出しによるパフォーマンスの低下を最小限に抑えることができます。共有のキャッシュ メカニズムを使用するもう一つの利点は、アプリに十分なトラフィックがあり、複数のインスタンスが動作しているケースや、異なるリージョンで同じサービスを実行しているケースにも対応できる点です。中間キャッシュ レイヤには欠点がありません。

気象警報がキャッシュ保存される時間は15 分ごとが妥当であると判断し、アプリにこの変更を実装した結果、平均レイテンシは大幅に減少し、最長 192.4 ミリ秒(80% 以上の減少)となりました。次の問題に移りましょう。ユーザーが通常リクエストしない州についてはどうするか、これらのユーザーに提供する使用感(レイテンシ)はどうするか、すべての州においてキャッシュを(ある程度)定期的に更新し続け、エンドユーザーへの API 呼び出しを最小限に抑えるにはどうすれば良いか、という問題です。こうした問題は、「常時稼働」CPU 割り当て機能を活用することで解決できます。

CPU が常に割り当てられるように設定することで、通常のリクエスト サイクル外でアプリのバックグラウンド処理を実行できます。5 分ごとに実行されるスレッドで 15 分を超えるキャッシュ エントリかどうか確認し、確認された場合はその API から最新の気象警報をフェッチしてキャッシュに保存します。それ以外の場合は最新の状態なので、すべてが州が確認されるまで(場合によっては更新されるまで)次の州に移っていきます。

アプリ自体について説明します。このアプリは主に下の表(名前と内容)にある一連の関数で構成されています。表の後に、それぞれの関数を詳しく説明していきます。

https://storage.googleapis.com/gweb-cloudblog-publish/images/Screen_Shot_2022-03-28_at_8.53.45_AM.max-1.max-1800x1800.png
  • stateIsInCache() は、リクエストされた州の気象警報がキャッシュに保存されているか、および更新されているか(「フレッシュ」)の両方を確認します。

  • そうでない場合、fetchState() が呼び出され、その API から 1 つの州の現在の気象警報をフェッチします。その結果が「ETL される」(エンドユーザーのビュー消費のために適切なデータを抽出および変換される)と、cacheState() が呼び出されて実際にこれらの結果をキャッシュに格納します。この 2 つの関数は、以前はfetchAndCacheState() と名付けられた大きな集合体でしたが、単一の関数呼び出しを行うように設計して作業を適切に分割したほうが良いと判断しました。

  • 州がキャッシュに保存されておりフレッシュな場合は、getStateFromCache() がキャッシュに保存された結果を pull します。

  • 「メイン」のウェブ アプリケーション app.all() がすべての HTTP GET および POST リクエストを処理します。

  • updateCache() は各州を確認し、必要に応じて、ユーザーのリクエストごとに呼び出される stateIsInCache() と照合してキャッシュを更新します。

  • setInterval() はバックグラウンドで実行されるバックグラウンド スレッドで、5 分ごとに updateCache() を呼び出してキャッシュを更新します。デフォルトでは、インスタンスは最後のリクエストから 15 分間しか稼働しません(インスタンスがアイドル状態とみなされている場合)。つまり、updateCache() は、リクエストが完了してからインスタンス自体がシャットダウンするまでに最大 3 回実行されます(引き続き稼働している他のインスタンスもキャッシュを更新できます。これは min-instances を 0 より大きい値に設定した場合にも適用され、最後のインスタンスは稼働したまま、キャッシュを更新し続けます)。

では、コード スニペットを見てみましょう。最も興味を引くコードはおそらく stateIsInCache() でしょう。
読み込んでいます...

州の気象情報が現在キャッシュに保存されているのか、また、キャッシュ済みエントリが「フレッシュ」、つまり 15 分以内の最新のものであるかどうかを確認する必要があります。一方、一番つまらなく見える「常時稼働 CPU」割り当て機能を活用するアプリに関するコードは、最も重要なコードです。これは、コンテナ インスタンスがシャットダウンするまで 5 分ごとにキャッシュを更新し続けるバックグラウンド スレッドです。

読み込んでいます...

アプリにアクセスすると、州を略語で選択できる空のフォームが表示されます。フォームを送信すると結果が表示され、同時に空のフォームが表示されて、別の州の気象情報も取得することができます。以下は、ユーザーがメリーランド州の気象警報をリクエストした際のスクリーンショットです。

https://storage.googleapis.com/gweb-cloudblog-publish/images/image1_TXZT4cU.max-1000x1000.max-1000x1000.png

アプリの仕組みを理解し、コードの一部をご覧になったことで、updateCache() を呼び出すバックグラウンド スレッドの setInterval() が、本当にリクエスト外で独立して実行されることがおわかりいただけたと思います。(アプリで長時間実行されているリクエストが Cloud Run のデフォルトの 5 分のリクエスト タイムアウトに達すると、重複する可能性がありますが、)ほとんどのアプリではレスポンスは非常に短時間で発生し、リクエストが完了するとインスタンスはアイドル状態となり、CPU がスロットリングされます。これにより、バックグラウンド スレッドが(適時に)稼働することが不可能になるため、このアプリの機能には「常時稼働」CPU の割り当てが必要になります。

この機能の詳細については、Cloud Run の CPU 割り当てに関するドキュメントをご覧ください。また、この動画のコメント セクションでは、アプリやこの機能についてのご質問、コメント、今後ご覧になりたいエピソードのご要望などを受け付けています。Node.js バージョンと Python バージョンは、アプリのオープンソースのリポジトリ フォルダ内にあります。Cloud Run の「常時稼働」CPU 割り当て機能をぜひご活用ください。

- デベロッパー アドボケイト Wesley Chun
投稿先