Google Cloud Platform

App Engine アプリのコールドブート パフォーマンスを最適化する

編集部注 : 数か月前、Android デベロッパーとして有名な Colt McAnlis が Google Cloud の Developer Advocate チームに異動してきました。彼はすぐに、GCP アセットから最高のパフォーマンスを引き出すことをテーマとする新しい連載ブログ(および動画ブログ)、Google Cloud Performance Atlas の制作に取りかかりました。今回の投稿は彼の最初の動画ブログの要点をまとめたもので、App Engine standard environment のコールドブート パフォーマンスに関する問題を取り上げています。

ロード バランサが組み込まれている App Engine standard environment には、トラフィックの需要に基づいてインスタンスの起動や終了が行えるというすばらしい特徴があります。プロビジョニングの必要がなくなるわけですから、コンテンツへのアクセスが急上昇したときはもちろん、日常的なトラフィックの変動に対処するためにも役に立ちます。

基本的に、App Engine アプリケーションの起動が非常に速いことは簡単に確認できます。次のグラフは、インスタンス クラスごとの Hello World アプリケーションの起動時間を示しています。

GAEstartup-1.png
App Engine F2 インスタンス クラスの 250 ミリ秒というブート時間はきわめて高速です。これは、4G 接続のほとんどの CDN で JavaScript ファイルをフェッチするのに要する時間よりも短く、App Engine が新規インスタンスの作成要求を非常に高速に処理することを示しています。

App Engine がインスタンスをどのように管理しているかについては参考資料を見ていただくとして、ここで考えなければならないのは、読み込みリクエスト(Loading request)という特殊な要求のことです。

読み込みリクエストは、新規インスタンスをスピンアップするために App Engine のロード バランサをトリガーします。ここが重要なのですが、読み込みリクエストはインスタンスが起動するまで待機することになるので、応答にかかる時間は平均よりもかなり長くなります。

ロード バランシングに対応しながらユーザー エクスペリエンスを高く保つためには、App Engine アプリケーションのコールドブート パフォーマンスを最適化することがポイントです。以下では、コールドブート パフォーマンスに対処するためのヒントをいくつか集めてみました。

常駐型インスタンスの活用

常駐型のインスタンス(Resident instance)とは、アプリケーションへの負荷に関係なく動いているインスタンスのことです。ゼロまでスケールダウンしても、この種のインスタンスは残ります。

スパイクが発生すると、新規にインスタンスをスピンアップしていたのでは間に合わない要求を常駐型のインスタンスで処理します。新規インスタンスをスピンアップしている間は、要求が常駐型インスタンスにルーティングされるのです。新しいインスタンスが起動したら、トラフィックはそちらにルーティングされ、常駐型のインスタンスはアイドル状態に戻ります。
s5ttBu03bbH3SrIcI8SNf3W1qfCeyN-qh_Qken3PUuEGKup0QVqcO1VdEHCUIIrvY3GKvnB7TZoSv1zKMEW6oUsZMojyTj1vCR9qENYOha8dhH3kF7OCiQzKKFMBmiAajlmqRkTZ26vb.PNG

つまり、レイテンシが異常に長くなったとユーザーに感じさせないためのコツは常駐型のインスタンスにあるのです。実際、常駐型のインスタンスはインスタンスの起動時間をユーザーから見えないようにしています。これはすばらしいことです。

詳細は、常駐型のインスタンスによって起動時間を短縮する仕組みについて説明した Google Cloud Performance Atlas の記事をご覧ください。

並列要求を処理する際のグローバル変数の初期化は要注意

グローバル変数を使うのはプログラミングではごく当たり前のことですが、コールドブート パフォーマンスに影響を与える特定のシナリオではパフォーマンスの足を引っ張ります。

読み込みリクエストの実行中にグローバル変数を初期化し、同時に並列要求を有効にしていると、アプリケーションはちょっとした落とし穴にはまります。グローバル変数の初期化が終わるまで、複数の並列要求がブロックされてしまうのです。以下のログのスナップショットは、この現象を示しています。

iNcq7vIf2kbKD1K9DqoEObIZSfOCsiCV41x2yR_QKOJbJ3G4ErHLnLgnavP3rdtY4szMarFC90T_t4X-_-8uZo7_VaCt0dlyDOzUhc3Mh3TxqoYzEA6MzmYvFpzjgmRi-iprck5wtbjh.PNG

最初の要求が読み込みリクエストで、次の部分にグローバル変数の初期化待ちでブロックされた並列要求の塊が並んでいます。このように要求がブロックされると、応答までのレイテンシは簡単に倍になってしまいます。これでは困ります。

詳細は、グローバル変数に悩まされたデベロッパーについて取り上げた Google Cloud Performance Atlas の記事をご覧ください。

依存コードには要注意

コールドブートの実行中、アプリケーション コードは依存コードをスキャンしてインポートするために忙しくなります。この作業に時間がかかればかかるほど、コードの最初の行が実行されるまでの時間が長くなります。この部分をとてつもなく高速に処理できる言語もありますが、柔軟性を高めるためにこの部分の処理が遅い言語もあります。

公平のために言っておくと、数個のモジュールをインポートするような標準的なアプリケーションでは、パフォーマンスへの影響はほとんど無視できます。しかし、巨大化したサードパーティ製ライブラリのインポートは、起動時間を大幅に長くする要因となってきます。

i90A_CJxK4ukuoNPpCS_oYC8z9BzcfwV3koMJdNycnxjKaX97P9qHfjr-PK43qETvZfrtj1eheGSHBfFe9fyp9WY2Jp-pG10xEu5K_WVI68NWXaKqkDfsFYryK9qzG93jV3wfLgios74.PNG

依存コードの問題に対処することは容易ではありません。ウォームアップ リクエストや遅延読み込みを駆使するだけでなく、極端なケースでは依存コードの木構造をばっさりと切らなければならなくなります。

詳細は、ある電卓プログラムの依存コードの問題を深く掘り下げた Google Cloud Performance Atlas の記事をご覧ください。

1 ミリ秒ずつの短縮が大切

結局のところ、すばやくスケーリングしてユーザーが感じるレイテンシをほどほどに抑えるには、App Engine インスタンスのコールドブート パフォーマンスを最適化することが重要だということです。

Google Cloud アプリケーションの最適化方法をもっと知りたい方は、Google Cloud Performance Atlas のブログ動画をご覧ください。パフォーマンスの向上には、1 ミリ秒ずつの短縮化が大切なのです。

* この投稿は米国時間 6 月 13 日、Developer Advocate である Colt McAnlis によって投稿されたもの(投稿はこちら)の抄訳です。

- By Colt McAnlis, Developer Advocate