App Engine アプリケーションのリソースを管理する方法

この記事は http://code.google.com/appengine/articles/managing-resources.html の日本語訳です

はじめに

App Engine の 課金体系変更 の一環として、アプリケーションの利用レポートに含まれるリソースを変更しました。CPU Hours をなくし、ストレージ容量、帯域に加えて、利用されたインスタンス時間(Frontend、Backend)と、API 呼び出しの回数を元に計算するシステムに移行します。より詳しい情報については、 FAQ をご覧ください。(FAQ の日本語版 もあります。)

新しい課金体系が有効になる前に、我々は課金額の比較機能をリリースして、新しい課金体系でどのように課金額が変わるか確認できるようにしました。つまり、新しい課金額が有効になる前にアプリケーションを最適化して、その結果どのように新しい課金額が変わるか確かめることもできます。

この記事では、新しい利用レポートの見かたや、リソース管理のために使用できるいくつかの戦略と、それらの戦略がアプリケーションのパフォーマンスに与える影響について説明します。

現在と将来の利用レポートを見る

App Engine の日毎の利用レポートはアドミンコンソールの Billing History ページにあります。URL はこちらです: https://appengine.google.com/billing/history?app_id=$APP_ID 。各レポートの横にある [+] マークをクリックすると、その日の詳細が開き、現在と将来の利用レポートを見ることができます。このプレビュー利用レポートは下記のように見えるでしょう:
プレビュー利用レポートの一例

これから新しい課金額について、個々のアイテムがそれぞれ何を意味するのか説明し、リソース管理のために使用できる方法をいくつかご紹介し、それらの戦略がどのようにアプリケーションのパフォーマンスに影響を与えるのかを説明します。

インスタンスの利用量を管理する

新しい明細のはじめ 2 行の項目はアプリケーションのインスタンス利用量についてのものです。インスタンスについてはドキュメンテーションに記載があります。アドミンコンソール内の https://appengine.google.com/instances?app_id=$APP_ID という URL やダッシュボード: https://appengine.google.com/dashboard?app_id=$APP_ID のドロップダウンから “Instances” グラフを表示させることで、アプリケーションがどれだけのインスタンスを使用しているか確認できます。

App Engine のスケジューラー

App Engine はそのスケジューリングアルゴリズムによって、アプリケーションへの現在のトラフィックを捌くためにどれだけのインスタンスが必要なのか判断します。アプリケーションが受け取るすべてのリクエストについて、App Engine は、現在利用可能なインスタンス(アイドル状態のインスタンスか Concurrent Request を受け付けられるインスタンス)で処理するか、リクエストをペンディングキューに入れるか、そのリクエストを処理するために新しいインスタンスを立ち上げるかを決定しています。この判断をするためには、利用可能なインスタンスがあるかどうか、直近でどれくらい早くアプリケーションがリクエストを捌いていたか(レイテンシーを)、アプリケーションの新しいインスタンスが立ち上がってリクエストを処理しはじめるのにどれだけかかるか、などのデータを利用します。多くの場合、新しいインスタンスで処理する方が、既存のインスタンスで処理するよりも早いと判断すれば、リクエストを処理するために新しいインスタンスを立ち上げます。

もちろん、アプリケーションに対するトラフィックは一定ではありませんので、スケジューラーはアプリケーション毎にアイドル状態のインスタンスの数を把握しています。こういったアイドル状態のインスタンスは、ユーザーに対するレイテンシーなしにスパイクを捌くのに有効です。スケジューラーが、アイドル状態のインスタンスが多すぎると判断すると、使われていないインスンタンスのいくつかを落とし、リソースを再利用します。

アプリケーションが使用するインスタンスの数を減らすには

レイテンシーを減らす

アプリケーションのレイテンシーは、トラフィックを捌くために必要となるインスタンスの数に大きな影響を与えますので、アプリケーションのレイテンシーを小さくすることは、実際に立ち上げるインスタンスの数に大きく関わってきます。アプリケーションのレイテンシーを小さくするためには、下記のようなことが役に立ちます:

  • 良くアクセスされる共有データをなるべくキャッシュする – 別の言いかたをすれば – Memcache を使います。またレスポンスに Cache-Control ヘッダーを付ければ、データがより効率的にサーバーやブラウザーによってキャッシュされるようになります。たとえキャッシュを数秒間しかしなかったとしても、アプリケーションはより効率的にトラフィックを捌くことができます。Python のアプリケーションであれば、 実行環境でのキャッシュ も利用できます。
  • Memcache をより効率的に使用します – 個々に呼び出すのではなく、get、 set、 delete 等にバッチ呼び出しを使用します。
  • リクエストを処理するのに必ず必要では無い機能については Task を利用する – ユーザーからのリクエスト処理中にしなくても良い仕事は Task に任せましょう。レスポンスを返す前にこの処理を待つのに比べると、Task Queue にこの処理を送ってしまえば、ユーザー向けのレイテンシーをだいぶ小さくできるでしょう。Task Queue を使えば実行レートをコントロールできるようになり、負荷を均一に保つこともできます。
  • データストアをより効率的に使用する – 後でさらに詳しく説明します。
  • 複数の URL Fetch 呼び出しを並列化する
    • async URL Fetch API を使用する( Java, Python
    • 複数の URL Fetch 呼び出し(これらは個々のユーザー向けリクエスト内で行なっていたかもしれません)をオフラインの Task で async URL Fetch を使用し並列実行します。
  • Java の HTTP セッションを非同期に書きこむHTTP セッション( Java )は appengine-web.xml に と書くことで、非同期にデータストアに書きこむように設定できます。セッションデータは Memcache に同期書き込みされ、あるリクエストがセッションデータを読みにいって Memcache に無かった場合にはデータストアを見に行きますが、このデータは必ずしも最新のデータではないこともあります。このことは、アプリケーションが古い無効なセッションデータを読んでしまうリスクが少しだけあることを意味しますが、殆どのアプリケーションではレイテンシーが減ることの方が重要でしょう。

アドミンコンソールにて、スケジューラーを手動で調節する

アドミンコンソールの Application Settings ページでは、2 つのスライダーを用意して、スケジューラーがアプリケーションのインスタンスを管理するのに使用するパラメーターのいくつかを設定できるようにしています。ここではこれらのスライダーを使用して、パフォーマンスとリソース使用量のトレードオフについてより細かく管理する方法について簡単に説明します:

  • Max Idle Instances を低くする – Max Idle Instances はあなたのアプリケーションが持てる最大のアイドル状態インスタンスの数を設定できます。ここで数を制限すると App Engine はこの数よりも多いアイドル状態のインスタンスがあれば落としますので、それ以上のクオータを使ったり課金が発生することを避けられます。しかし、アイドル状態のインスタンスが少なくなれば、トラフィックにスパイクがあったときに App Engine のスケジューラーが新しいインスタンスを立ち上げなければならなくなります – もしかするとアプリケーションのユーザに対するレイテンシーが大きくなるでしょう。
  • Min Pending Latency を大きくする – Min Pending Latency を大きくすると、App Engine のスケジューラーは、リクエストが設定された秒数よりも長く保留されない限り新しいインスタンスを立ち上げません。全てのインスタンスが忙しい状態であれば、ユーザー向けリクエストは、この閾値に達するまでペンディングキューで待っている必要があるでしょう。この値を大きくすればインスタンスが立ち上がる数を減らすことができますが、負荷が高くなった時はユーザー向けレイテンシーが大きくなるでしょう。

Java の Concurrent Requests を有効にする

我々の 1.4.3 リリース では、Java のインスタンスが同時に複数のリクエストを捌けるようになりました。この設定を有効にするとアプリケーションでトラフィックを捌くために必要なインスタンスの数を減らすことができますが、正しく動作するためには、アプリケーションがスレッドセーフになっていなければなりません。有効にする方法については、我々の Java ドキュメント を読んでください。

ノート: Python 2.7 がローンチするまでは Python でマルチスレッドは使用できません。Python 2.7 では、マルチスレッドのインスタンスはより多くのリクエストを同時に捌けますし、ブロックする API を待っている状態で無駄に Instance Hour のクオータを使用することもありません。現在 Python 版ではインスタンスは 1 度に 1 つのリクエストしか捌けませんし、デベロッパーのみなさんが concurrent requests に移行する時間を取るために、2011 年 11 月 20 日までは frontend インスタンス時間を半額で提供することにしました。Python 2.7 は現在 トラステッドテスターを実施 しています。

リザーブインスタンスの割引を利用する

アドミンコンソールの Billing Settings ページで、リザーブインスタンスの数を設定できます。インスタンスの数を減らすわけではありませんが、インスタンス利用料を減らす助けになります。

Task Queue の設定を調整する

Task Queue のデフォルト設定はパフォーマンスが出るように調整されています。このデフォルト値では、同時に複数のタスクを投入した場合、おそらく新しいフロントエンドインスタンスが立ち上がることになるでしょう。インスタンス時間を節約するためにどのように Task Queue を設定するかについて、いくつかの提案をします。

  • 低レイテンシーが必要でないような task には X-AppEngine-FailFast ヘッダーを付けます。このヘッダーがあると、スケジューラーは利用可能なインスタンスが無い時には即座にエラーを返すようになります。Task Queue はそのリクエストを処理可能なインスタンスが空くまで、スピードを落としながらリトライします。ただ注意していただきたいのは、X-AppEngine-FailFast が付いたリクエストが、空きインスタンスを専有している時に、そのヘッダーが付かないリクエストが来れば、そのリクエストを処理するために新しいインスタンスが立ち上がるということです。
  • Task Queue の設定を変更する( Java , Python
    • “rate” パラメーターを低く設定すれば、Task Queue はより遅い速度でタスクを実行します。
    • “max_concurrent_requests” パラメーターを低く設定すれば、同時に実行されるタスクの数を減らせます。
  • Backends( Java , Python )を使用するとタスク実行のために使用するインスタンスの数を完全にコントロールすることができます。Push queue を dynamic backends で使用しても良いですし、Pull queue を resident backends で 使用することもできます。

アプリケーションのストレージを管理する

App Engine はストレージのコストを、データストアのエンティティサイズ、インデックスのサイズ、Blobstore に保存したデータのサイズによって計算します。必要以上のデータを保存していないか確かめるために下記のことができます。

  • 既に必要なくなったエンティティーや blob を削除する。
  • 下記の データストア利用を管理する で説明しているように、不必要なインデックスを削除する。

データストア利用を管理する

新しい課金体系では、データストアで行われた操作の数を元に計算します(今行われているような、それらの操作にかかった CPU Hour を元にするのではなく)。データストアへのリクエストのレイテンシーを減らすだけではなく、利用リソースを減らすためにできることがいくつかあります:

  • 不必要なインデックスを全て削除するとストレージ使用量とエンティティ書き込み時のコストが減ります。“Get Indexes” の機能( Java , Python )を使用して、アプリケーションでどのインデックスが定義されているか調べます。またアドミンコンソールではアプリケーションでどのインデックスが用意されているかを見ることができます: https://appengine.google.com/datastore/indexes?app_id=$APP_ID
  • データモデルを設計するときになるべく Custom Indexes を作らないようにクエリーが書けるか確かめましょう。App Engine がどのようにインデックスを作るかについて詳しくは Query and Indexes についての文書を読んでください。
  • 可能な限り、インデックスされるプロパティー(こちらがデフォルトです)の代わりにインデックスされないプロパティー( Java , Python )を使用します。注意点としては、インデックスされないプロパティーに対して検索する必要が後で出てきた時には、コードを修正してプロパティーをインデックスするようにするだけではなくて、全てのエンティティーに対して map reduce を実行して保存し直す必要があります。
  • App Engine の 1.5.2 と 1.5.3 ( Java , Python )でデータストアのクエリープランナーが改善されたので、クエリーに必要なインデックスの数が減っている場合があります。そういったカスタムインデックスの一部をパフォーマンスのために使い続けることもできますが、その他のカスタムインデックスは削除してストレージ領域とエンティティー書き込みのコストを減らすことができます。
  • データモデルを再構築してクエリーを使っていた部分を key による取得に変更するとより安く効率的になります。
  • 可能な場合はエンティティ全体を取得する代わりに keys-only クエリーを使用してください。
  • レイテンシーを減らすために、複数の get() を batch get() に置き換えてください。
  • ページネーションには、offset ではなく datastore cursor を使用してください。
  • async datastore API( Java , Python ) を利用して複数の datastore RPC を並列実行します。

帯域を管理する

外向きの帯域については、利用量を減らす 1 つの方法は、可能な限り、静的なファイルにはレスポンスに適切な有効期限を持った Cache-Control ヘッダーを付ける( Java , Python )ことです。Public な Cache-Control ヘッダーを使用すれば、プロクシサーバーやブラウザーがレスポンスを指定した時間キャッシュするようになります。

内向きの帯域はコントロールが難しいですが、それはユーザーがそのデータを送ってくるものだからです。しかし、ここでは我々の DoS Protection サービス( Java , Python )について言及しておきましょう。こちらを使用すると、アプリケーションを乱用すると思われるユーザーの IP アドレスからのアクセスをブロックできます。

他のリソースを管理する

残りの項目は、Email, XMPP, Channel API です。これらの API についていえるのは、なるべく効率的に使用することを心がけてください。これらの API の利用状態を検査するのに良い方法は Appstats (Python , Java )を使用し、必要以上の呼び出しを行なっていないことを確認することです。またアプリケーションのエラーレートをチェックして、おかしな呼び出しを行なっていないか調べるのは良い考えです。こうした方法で早期発見できることも多いです。

This entry was posted on Friday, 02 September, 2011.

Tags: ,