お気に入り | 日本語 | ログイン

Python ランタイム環境

App Engine は事前にロードされた Python インタープリタを使用して、安全な「サンドボックス」環境で Python アプリケーション コードを実行します。アプリケーションはこの環境と対話して、Web リクエストの受信、作業の実行、応答の送信を行います。

Python ランタイムの選択

Python SDK の appcfg.py という名前のツールを設定ファイル app.yaml で使用すると、App Engine はアプリケーションで Python ランタイム環境を使用することを認識します。次の設定要素を使用して、Python ランタイム環境を選択します:

runtime: python
api_version: 1

最初の要素の runtime は、Python ランタイム環境を選択します。この記事の執筆時には、App Engine はランタイム環境として python の 1 種類のみをサポートしています。

2 番目の要素の api_version は、使用する Python ランタイム環境のバージョンを選択します。この記事の作成時点では、App Engine における Python 環境 1 のバージョンは 1 つのみです。仮に App Engine チームが、環境への変更をリリースする必要に迫られ、新しい環境が既存のコードとの互換性を持たない可能性がある場合は、新しいバージョン識別子を使用します。api_version 設定を変更してアプリケーションをアップロードするまで、アプリケーションは選択したバージョンを引き続き使用できます。

app.yamlappcfg.py の詳細については、Python アプリケーション設定アプリケーションのアップロード をご覧ください。

リクエストと CGI

App Engine は、アプリケーションに対する Web リクエストを受け取ると、アプリケーションの app.yaml 設定ファイルに記述された URL に対応するハンドラ スクリプトを呼び出します。App Engine は CGI 標準を使用して、リクエスト データをハンドラに伝え、レスポンスを受け取ります。

App Engine は複数の Web サーバーを使用してアプリケーションを実行し、リクエスト処理の信頼性が確保できるようにサーバーの使用数を自動調整します。指定されたリクエストは任意のサーバーに転送できますが、以前に同じユーザーからのリクエストを処理したサーバーと同じサーバーには転送できません。

サーバーは、リクエストの URL とアプリケーションの設定ファイル内の URL パターンとを比較して、実行する Python ハンドラ スクリプトを判定します。その後、ハンドラをリクエスト データを使用した CGI 環境で実行します。CGI 標準 に従って、サーバーはリクエスト データを環境変数と標準入力ストリームに転送します。スクリプトはリクエストに応じた処理を行い、レスポンスを作成して標準出力ストリームに転送します。

ほとんどのアプリケーションは、ライブラリを使用して CGI リクエストを解析し、Python 標準ライブラリの CGI モジュールのような CGI レスポンスを返すか、CGI プロトコルを認識する Web フレームワーク(webapp など)を使用します。環境変数と入力ストリーム データの形式について詳しくは、CGI ドキュメントをご覧ください。

次の例のハンドラ スクリプトは、ユーザーのブラウザにメッセージを表示します。このスクリプトは、メッセージの種類と内容を識別する HTTP ヘッダーを標準出力ストリームに出力します。

print "Content-Type: text/plain"
print ""
print "Hello, world!"

レスポンス

App Engine は、リクエスト ハンドラ スクリプトが標準出力ストリームに書き込むデータをすべて収集し、スクリプトが終了するまで待機します。スクリプトが終了すると、すべての出力データがユーザーに送信されます。

App Engine は、ハンドラの終了前にデータをユーザーのブラウザに送信することをサポートしていません。一部の Web サーバーではこのテクニックを使用して、1 つのリクエストへの応答で一定期間内にデータをユーザーのブラウザに「ストリーミング」します。App Engine ではこのストリーミング技法をサポートしていません。

クライアントがリクエストと一緒に、gzip 圧縮コンテンツを受け入れることができるという内容の HTTP ヘッダーを送ると、App Engine は応答データを自動的に圧縮し、適切な応答ヘッダーを添付します。Accept-Encoding および User-Agent リクエスト ヘッダーを使用して、クライアントが圧縮した応答を受信できるかの信頼性を判断します。カスタム クライアントは、「gzip」の値を使用して Accept-Encoding および User-Agent ヘッダーを両方指定すれば、コンテンツを強制的に圧縮することができます。

リクエスト タイマー

リクエスト ハンドラがリクエストを処理し、レスポンスを返す時間には、制限が設定されています(一般的には 30 秒前後)。制限時間を過ぎると、リクエスト ハンドラは割り込まれます。

Python ランタイム環境は、DeadlineExceededError をパッケージ google.appengine.runtime から発行して、リクエスト ハンドラに割り込みます。リクエスト ハンドラがこの例外をキャッチしない場合、すべてのキャッチされない例外と同様、ランタイム環境は HTTP 500 サーバー エラーをクライアントに返します。

リクエスト ハンドラはこのエラーをキャッチして、応答をカスタマイズできます。ランタイム環境が例外を発生させた後、リクエスト ハンドラは少し時間を使って(1 秒未満)カスタマイズした応答を準備します。

from google.appengine.runtime import DeadlineExceededError

class MainPage(webapp.RequestHandler):
  def get(self):
    try:
      # Do stuff...

    except DeadlineExceededError:
      self.response.clear()
      self.response.set_status(500)
      self.response.out.write("This operation could not be completed in time...")     

2 回目の期限までにハンドラが応答を返さない、または例外を発生さなかった場合は、ハンドラは終了し、デフォルトのエラー応答が返されます。

リクエストはレスポンスするまでに最大 30 秒の時間がありますが、App Engine の処理が最適化されているアプリケーションは、数百ミリ秒で処理されるような短いリクエストを実行するアプリケーションです。効率的なアプリケーションはほとんどのリクエストに対して迅速にレスポンスします。レスポンスが遅いアプリケーションは、App Engine のインフラストラクチャでうまくスケーリングを行うことができません。

サンドボックス

App Engine が複数の Web サーバー間のアプリケーションにリクエストを分散できるように、そしてアプリケーション同士が干渉し合わないようにするために、アプリケーションは隔離された「サンドボックス」環境で実行されます。この環境でアプリケーションは、コードの実行、App Engine データストア内のデータの保存とクエリ、App Engine メール、URL フェッチ、ユーザー サービスの使用、ユーザーの Web リクエストの検証とレスポンスの作成を行うことができます。

App Engine アプリケーションは次のことはできません。

  • ファイルシステムに書き込むこと。アプリケーションで永続的なデータを保存するには、App Engine データストアを使用する必要があります。ファイルシステムからの読み取りはできます。また、アプリケーションを使用してアップロードされたアプリケーション ファイルはすべて使用できます。
  • ソケットを開くことや別のホストに直接アクセスすること。アプリケーションは App Engine URL フェッチ サービスを使用して、ポート 80 とポート 443 から他のホストに HTTP および HTTPS リクエストを行うことができます。
  • サブプロセスやスレッドを生成すること。アプリケーションに対する Web リクエストは、数秒以内に単一プロセスで処理する必要があります。レスポンスに時間がかかりすぎるプロセスは、Web サーバーに負荷がかかりすぎないように終了されます。
  • 他のシステム コールを実行すること。

ピュア Python

Python ランタイム環境では、Python 2.5.2 を使用します。

Python ランタイム環境のすべてのコードは Python のみで記述される必要があり、C 拡張やその他のコンパイルされる必要のあるコードは使用できません。

この環境には、Python 標準ライブラリが含まれます。ネットワークやファイルシステムへの書き込みのように、コア関数が App Engine でサポートされていないため、無効になっているモジュールもいくつかあります。さらに、os モジュールも入手可能ですが、サポートされていない機能は無効になっています。サポートされていないモジュールをインポートしようとしたり、サポートされていない機能を使用しようとすると例外が返されます。

標準ライブラリ内のモジュールには、App Engine で作動するよう置き換えられたか、カスタマイズされたものも少数あります。次に例を示します。

  • cPicklepickle にエイリアスされます。cPickle 固有の機能はサポートしていません。
  • marshal は空です。インポートは成功しますが、使用できません。
  • 次のモジュールは同様に空です。 imp, ftplib, select, socket
  • tempfile は、StringIO にエイリアスされる TemporaryFile を除いて無効になります。
  • logging は使用可能であり、使用は推奨されています。以下をご覧ください。

Python 標準ライブラリと App Engine ライブラリに加え、ランタイム環境は次のサードパーティのライブラリを含んでいます。

アプリケーション ディレクトリにコードを書き込み、他のピュア Python ライブラリをアプリケーションに含めることができます。アプリケーション ディレクトリ内のモジュール ディレクトリをリンク先とするシンボリック リンクを作成すると、appcfg.py がリンクをたどり、アプリケーションにそのモジュールを含めます。

Python モジュールのインクルード パスにはアプリケーションのルート ディレクトリ(app.yaml ファイルを含むディレクトリ)が含まれます。アプリケーションのルート ディレクトリに作成するモジュールはルートからのパスを使用することで使用可能になります。Python がサブ ディレクトリをパッケージと認識できるように、忘れずに __init__.py ファイルをサブ ディレクトリに作成してください。

アプリケーション キャッシュ

Python ランタイム環境は、単一の Web サーバーのリクエスト間でインポートされたモジュールをキャッシュします。これは、スタンドアロンの Python アプリケーションが、複数のファイルによってインポートされたモジュールでも、一度だけロードするやり方と似ています。ハンドラ スクリプトが main() ルーチンを提供する場合、ランタイム環境はスクリプトもキャッシュします。それ以外の場合、ハンドラ スクリプトはリクエストごとにロードされます。

アプリケーション キャッシュはレスポンス タイムに多大なメリットをもたらします。以下で説明するように、すべてのアプリケーションで main() を使用することをおすすめします。

インポートがキャッシュされます

効率向上のため、Web サーバーはインポートされたモジュールをメモリに保存し、同じサーバーの同じアプリケーションに対するそれ以降のリクエストについては、再ロードまたは再評価を行いません。ほとんどのモジュールはインポート時にグローバル データを初期化せず、また、副作用がないため、キャッシュしてもアプリケーションの動作が変化することはありません。

アプリケーションが、リクエストごとに評価されるモジュールに依存するモジュールをインポートする場合、アプリケーションはこのキャッシュ動作を調整する必要があります。

次の例では、インポートされたモジュールのキャッシュ方法を示します。mymodule がインポートされるのは単一の Web サーバーにつき 1 回であるため、グローバル mymodule.counter0 に初期化されるのは、サーバーによる最初のリクエスト時のみです。それ以降のリクエストは、先行するリクエストの値を使用します。

### mymodule.py
counter = 0
def increment():
  global counter
  counter += 1
  return counter


### myhandler.py
import mymodule

print "Content-Type: text/plain"
print ""
print "My number: " + str(mymodule.increment())

これにより My number: # が出力されます。# は、このハンドラがリクエストを処理した Web サーバーによって呼び出された回数です。

ハンドラ スクリプトもキャッシュ可能

インポートされたモジュールに加え、App Engine にハンドラ スクリプト自体をキャッシュさせることも可能です。ハンドラ スクリプトが main() という名前の関数を定義すると、スクリプトとそのグローバル環境がインポートされたモジュールと同様にキャッシュされます。指定された Web サーバーのスクリプトに対する最初のリクエストは、通常のスクリプト評価を行います。それ以降のリクエストに対しては、App Engine はキャッシュ環境で main() 関数を呼び出します。

ハンドラ スクリプトをキャッシュするには、App Engine は引数を使用せずに main() を呼び出すことができなければなりません。ハンドラ スクリプトが main() 関数を定義しない場合、または main() 関数が(デフォルトを持たない)引数を要求する場合、App Engine はリクエストごとにスクリプト全体をロードおよび評価します。

解析された Python コードをメモリに保存すると時間の節約になり、応答時間が短縮化されます。グローバル環境のキャッシュには、他にも以下のような利用方法があります。

  • コンパイルされた正規表現。すべての正規表現は、コンパイルされた形式で解析され、格納されます。コンパイルされた正規表現をグローバル変数で保存し、アプリケーション キャッシュを使用してコンパイルされたオブジェクトをリクエスト間で再使用することができます。
  • GqlQuery オブジェクト。GqlQuery オブジェクトが作成されると GQL のクエリ文字列が解析されます。パラメータ バインディングと bind() メソッドを使用して、GqlQuery オブジェクトを再使用すると、オブジェクトをそのたびに再構築するより時間が短縮されます。GqlQuery オブジェクトを値に対するパラメータ バインディングを使用してグローバル変数で保存した後、新しいパラメータ値を各リクエストにバインドさせて再使用することができます。
  • 設定とデータ ファイル。アプリケーションがファイルからの設定データをロードし解析する場合、解析データをメモリに保存し、リクエストごとにファイルがリロードされるのを防ぐことができます。

ハンドラ スクリプトは、インポートされると main() を呼び出します。App Engine では、スクリプトのインポートによって main() が呼び出されることを予期するため、App Engine ではサーバー上で初めてリクエスト ハンドラがロードされるときにこの関数を呼び出しません。

次の例では、ハンドラ スクリプトのグローバル環境のキャッシュを使用して、上記の例と同じことを行っています。

### myhandler.py

# A global variable, cached between requests on this web server.
counter = 0

def main():
  global counter
  counter += 1
  print "Content-Type: text/plain"
  print ""
  print "My number: " + str(counter)

if __name__ == "__main__":
  main()

注: リクエスト間で、ユーザーの個人情報を「漏らさない」ように注意してください。キャッシュが必要な場合以外は、グローバル変数の使用を避け、必ず main() ルーチン内のリクエスト固有のデータを初期化してください。

main() を持つアプリケーション キャッシュはアプリケーションのレスポンス タイムに多大なメリットをもたらします。すべてのアプリケーションにおすすめします。

ロギング

App Engine の Web サーバーは、ハンドラ スクリプトが標準出力ストリームに Web のリクエストに対するレスポンスとして記述するものすべてをキャプチャします。また、ハンドラ スクリプトが標準エラー ストリームに記述するものすべてをキャプチャし、ログ データとして格納します。アプリケーションのログ データは、管理コンソール を使用して検討、分析したり、appcfg.py request_logs を使用してダウンロードできます。

App Engine の Python ランタイム環境には、Python 標準ディクショナリからのロギング モジュールがログ レベル(「debug(デバッグ)」、「info(情報)」、「warning(警告)」、「error(エラー)」、「critical(致命的)」)などのロギング概念を理解するための特別なサポートが含まれます。

import logging

from google.appengine.api import users
from google.appengine.ext import db

user = users.get_current_user()
if user:
  q = db.GqlQuery("SELECT * FROM UserPrefs WHERE user = :1", user)
  results = q.fetch(2)
  if len(results) > 1:
    logging.error("more than one UserPrefs object for user %s", str(user))
  if len(results) == 0:
    logging.debug("creating UserPrefs object for user %s", str(user))
    userprefs = UserPrefs(user=user)
    userprefs.put()
  else:
    userprefs = results[0]
else:
  logging.debug("creating dummy UserPrefs for anonymous user")

環境

実行環境にはアプリケーションに有用な環境変数がいくつか含まれます。これらの変数は、App Engine に固有のものもあれば CGI 標準に属するものもあります。Python コードは os.environ ディクショナリを使用して、これらの変数にアクセスできます。

次の環境変数は App Engine に固有のものです。

  • APPLICATION_ID: 実行中のアプリケーションの ID。
  • CURRENT_VERSION_ID: 実行中のアプリケーションのメジャー バージョンとマイナー バージョン(X.Y)。メジャー バージョン番号(「X」)はアプリケーションの app.yaml ファイルで指定されます。マイナー バージョン番号(「Y」)は、アプリケーションの各バージョンが App Engine にアップロードされるとき、自動的に設定されます。開発用 Web サーバーでは、マイナー バージョンは常に「1」です。
  • AUTH_DOMAIN: Users API を使用してユーザーを認証するためのドメイン。appspot.com にホストされたアプリケーションは gmail.comAUTH_DOMAIN を持ち、Google アカウントを受け付けます。Google Apps を使用してカスタム ドメインにホストされたアプリケーションは、そのカスタム ドメインと同じ AUTH_DOMAIN を持ちます。

次の環境変数は CGI 標準の一部で、App Engine では特殊な動作をします。

  • SERVER_SOFTWARE: 開発用 Web サーバーでは、この値は「Development/X.Y」(「X.Y」はランタイムのバージョン)です。

その他の環境変数は CGI 標準に従って設定されます。これらの変数について詳しくは、CGI 標準をご覧ください。

ヒント: 次の webapp リクエスト ハンドラは、ブラウザ内のアプリケーションから見える環境変数をすべて表示します。

from google.appengine.ext import webapp
import os

class PrintEnvironmentHandler(webapp.RequestHandler):
  def get(self):
    for name in os.environ.keys():
      self.response.out.write("%s = %s<br />\n" % (name, os.environ[name]))

割り当てと制限

アプリケーションへの各リクエストはリクエスト割り当てに数えられます。

リクエストの一部として受信したデータは受信した帯域幅(課金対象)の割り当てに数えられます。リクエストへのレスポンスとして送られたデータは送信帯域幅(調整可能)の割り当てに数えられます。

HTTP および HTTPS(セキュア)の両方のリクエストは、リクエスト受信した帯域幅(課金対象)および送信した帯域幅(課金可能)の割り当てに数えられます。管理コンソールの [Quota Details] ページでは、情報としてセキュア リクエスト受信したセキュアな帯域幅送信したセキュアな帯域幅値も別々に表示されます。HTTPS リクエストのみがこの値として数えられます。

リクエスト ハンドラの処理時間として使用された CPU 処理時間はCPU 時間(課金対象)の割り当てに数えられます。

割り当ての詳細については、割り当て、および管理コンソールの「割り当て詳細」セクションをご覧ください。

割り当てに加え、リクエスト ハンドラには次の制限があります。

制限 制限値
リクエスト サイズ 10 MB
レスポンス サイズ 10 MB
リクエスト処理時間 30 秒
同時動的リクエスト 30 *
アプリケーション ファイルの最大数 1,000
静的ファイルの最大数 1,000
アプリケーション ファイルの最大サイズ 10 MB
静的ファイルの最大サイズ 10 MB
アプリケーション ファイルと静的ファイルの最大合計サイズ 150 MB

* アプリケーションは、約 30 個のアクティブな動的リクエストを同時に処理できます。つまり、サーバー側の平均リクエスト処理時間が 75 ミリ秒であるアプリケーションは、待ち時間なく最大で 400 リクエスト/秒処理できます((1000 ms/秒 / 75 ms/リクエスト) * 30 = 400)。 CPU 性能への依存が高いアプリケーションには、長時間実行されるリクエストの処理時に多少の追加待ち時間が発生します。これは、同じサーバーを共有している他のアプリケーションに対して処理時間を割り当てるためです。静的ファイルへのリクエストにはこの制限は適用されません。