Google Cloud Platform

Cloud Run の応答時間を最適化する 3 つの方法

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

サーバーレスのコンテナ化は、大きな負担となるインフラストラクチャ管理なしでステートレス マイクロサービスをデプロイする方法をデベロッパーに提供し、世界を席巻しました。Cloud Run ではインフラストラクチャ管理が一切不要です。ウェブサーバーとステートレス ロジックを持つコンテナ イメージを渡し、メモリ / CPU と許可される同時実行の組み合わせを指定するだけです。

Cloud Run は、HTTP エンドポイントの作成、コンテナへのリクエストのルーティング、処理するリクエスト量に応じたコンテナのスケールアップとスケールダウンを行います。また、Cloud Run はアイドル インスタンスなど、応答時間のレイテンシを短縮するためのネイティブ機能をいくつか提供していますが、以下で概説する効果的なサービスを記述することでその多くを改善できます。

アイドル インスタンス

トラフィック量は変動するため、トラフィックの急増によりコールド スタートが必要になる事態を避けられるよう Cloud Run はいくつかのアイドル インスタンスを維持します。たとえば、コンテナ インスタンスがリクエストの処理を完了すると、別のリクエストを処理する必要が発生した場合に備えて、一定期間アイドル状態のままになることがあります。

Cloud Run

ただし、処理するリクエストがなければ、Cloud Run はしばらくしてから未使用のコンテナを終了します。これはつまり、コールド スタートが発生する可能性があるということです。コンテナ インスタンスが必要に応じてスケーリングされ、実行環境は完全に初期化されることになります。min-instance 設定を使用して、アイドル インスタンスを永続的に利用可能な状態に保つことはできますが、サービスがリクエストを処理していない場合でも費用が発生します。

コールド スタートの可能性がある場合に、費用と応答時間のレイテンシの両方を最小化するとしましょう。アイドル インスタンスの最小数は設定しないけれども、コンテナの起動中、リクエストのリッスンを開始する前に追加の計算が必要になった場合は読み込み時間とレイテンシが長くなることも理解していることとします。

Cloud Run コンテナの起動

コンテナの起動時間に合わせたサービスの最適化に役立つテクニックがいくつかあります。ここでの目標は、コンテナ インスタンスのリクエスト処理を遅らせるレイテンシを最小限に抑えることです。そのためには、まず Cloud Run コンテナの起動ルーティンの見直しから始めます。

大まかに、起動ルーティンでは次の処理が行われます。

1.サービスを開始する

○コンテナを起動する

○entrypoint コマンドを実行してサーバーを起動する

2.開いているサービスポートを確認する

ステップ 1a に必要な時間を最小限に抑えるには、サービスを調整します。Cloud Run の応答時間のサービスを最適化する 3 つの方法を順に見ていきましょう。

1. サービスをスリム化する

まず、Cloud Run では、コンテナ イメージのサイズは、コールド スタートやリクエストの処理時間には影響しません。ただし、コンテナ イメージのサイズが大きいと、ビルドにもデプロイにも時間がかかります。

動的言語で記述されたアプリケーションに関しては、特に注意が必要です。たとえば、Node.js や Python を使用している場合、プロセスの起動時にモジュールの読み込みが発生するため、コールド スタート時にレイテンシが増加することになります。

Module Loading

また、インポート時に初期化コードを実行する一部のモジュールにも注意が必要です。

Initialization Code

以下を実行することで、サービスをスリム化できます。

  • 動的言語を使用している場合は、依存関係の数とサイズを最小限に抑える。

  • 起動時に計算するのではなく、計算を延期させる。グローバル変数の初期化は起動時に行われるため、コールド スタートの時間を長引かせる要因となります。使用頻度の低いオブジェクトの初期化を延期すれば、それにかかる時間を先送りすることでコールド スタートの時間を短縮できます。

  • 初期化を短縮して、HTTP サーバーの起動時間を早める。

  • PHP の Composer オートローダーの最適化など、コード ローディングの最適化を使用する。

2. 小さなベースイメージを使用する

alpinedistroless などのリーンなベースイメージを使用して、コンテナのサイズを最小に抑えます。たとえば、alpine:3.7 イメージは、centos:7 イメージよりも 71 MB 小さくなります。

空のイメージである scratch を使用して独自のランタイム環境を構築することもできます。アプリが静的にリンクされたバイナリになっている場合、scratch ベースイメージを使用するのは簡単です。

  FROM scratch
COPY mybinary /mybinary
CMD [ "/mybinary" ]

また、イメージ内で本当に必要とされるもののみをインストールするようにします。必要のない追加パッケージはインストールしないようにしましょう。

3. グローバル変数を使用する

Cloud Run では、リクエスト間でサービスの状態が維持されるとは限りません。ただし、Cloud Run はコンテナ インスタンスを再利用してトラフィックの処理を継続します。

これは、グローバル変数を宣言でき、新しく起動したコンテナでその変数の値を再利用できるということを意味します。また、オブジェクトをメモリ キャッシュに保存することもできます。これをリクエストのロジックではなく、グローバル スコープで行うと、トラフィックが継続中の場合のパフォーマンスが向上します。これはコールド スタート時間の短縮にはなりませんが、コンテナの初期化が発生した際には、オブジェクトがキャッシュに保存されているため、その後の継続するリクエストのレイテンシを短縮できます。

たとえば、リクエストごとのロジックをグローバル スコープで行う場合、コールド スタートにかかる時間はほぼ同じになりますが(ウォーム リクエストにはないキャッシュ保存のロジックを追加するとコールド スタート時間は長くなります)、そのウォーム インスタンスが処理する後続のリクエストのレイテンシは低くなります。

  // Global (instance-wide) scope
// This computation runs at instance cold-start
const instanceVar = heavyComputation();
/**
* HTTP function that declares a variable.
*
* @param {Object} req request context.
* @param {Object} res response context.
*/
exports.scopeDemo = (req, res) => {
// Per-function scope
// This computation runs every time this function is called
const functionVar = lightComputation();
res.send(`Per instance: ${instanceVar}, per function: ${functionVar}`);
};

コールド スタートに役立つ方法の一つとして、グローバル状態を Memorystore などのメモリ内データストアにオフロードすることが挙げられます。これにより、アプリケーション キャッシュへのミリ秒未満のデータアクセスを実現できます。

まとめ

肝要なのは、コンテナの初期化中に計算するロジックを最小限に抑え、できるだけ早くリクエストの処理を開始できるように、よりスリムなサービスを作成することです。このブログでは Cloud Run サービスを設計するためのベスト プラクティスをいくつかご紹介しましたが、効果的なサービスの記述やパフォーマンスの最適化に関するその他のヒントについてはこちらで確認できます。

Cloud コンテンツの詳細については、Twitter で@swongful をフォローしてください。

-デベロッパー アドボケイト Stephanie Wong