風柳メモ

ソフトウェア・プログラミング関連の覚書が中心。

GAEでremote_apiを使う場合の覚書

要約

remote_apiを使えばGoogle App EngineのDatastoreをローカルのコマンドプロンプトから操作出来るように出来る。
ただし、大量のエントリを扱う場合いろいろ制約あり。また、Quotaは普通に消費される。


経緯

この記事がらみで、じゃあGAE/Pythonのremote_apiを使ってDatastoreを削除する場合はどうなのか気になったので、まずはremote_apiの使い方を調べてみた。
参考:http://code.google.com/intl/en/appengine/articles/remote_api.html
以下はSDK 1.5.0で、WindowsXP上で試行した例。

app.yamlの設定

remote_apiを使いたいアプリケーション(app)のapp.yamlに次の設定を追加し、デプロイしておく。

builtins:
- remote_api: on
- datastore_admin: on

なお、ついでに追加したdatastore_adminは、管理コンソールのDatastore Adminを使うための設定。remote_apiだけを使う場合は無くてよい。

SDKのインストールパスの設定確認

「コントロールパネル」→「システム」→[詳細設定]タブ→[環境変数(N)]のユーザー環境変数で、PATHにSDKのインストールパス(デフォルトではC:\Program Files\Google\google_appengine\)が含まれているか調べ、なければ追加する。
ここにremote_api用のスクリプトであるremote_api_shell.pyが存在するはず。

環境変数PYTHONPATHの確認

同じく、環境変数PYTHONPATHに、カレントディレクトリ(.)が含まれているか調べ、なければ(先頭に)追加する。

PYTHONPATHの例

.;C:\Program Files\Google\google_appengine;C:\Program Files\Google\google_appengine\lib\antlr3;C:\Program Files\Google\google_appengine\lib\django;C:\Program Files\Google\google_appengine\lib\fancy_urllib;C:\Program Files\Google\google_appengine\lib\ipaddr;C:\Program Files\Google\google_appengine\lib\webob;C:\Program Files\Google\google_appengine\lib\yaml\lib

remote_api_shell.pyの実行

対象となるアプリケーション(app)のディレクトリに移動し、remote_api_shell.pyを(-sオプションでアプリのサブドメインを指定して)実行する。
後は、Pythonの実行環境となるので、Datastoreのアクセス等、任意の操作を実施する。

実行例

C:\> d:
D:\> cd \gae\appid
D:\gae\appid>remote_api_shell.py -s appid.appspot.com
Email: account
Password:
App Engine remote_api shell
Python 2.5.4 (r254:67916, Dec 23 2008, 15:10:54) [MSC v.1310 32 bit (Intel)]
The db, users, urlfetch, and memcache modules are imported.
appid> from appmodule import dbClassName
appid> entries = dbClassName.all().fetch(500)

実行ディレクトリやappid、account、appmodule、dbClassName等は随時自分のものと読み替えること。

注意事項など

Pythonはローカル環境上で実行される

remote_apiは、あくまでローカルPC上でPythonを実行することに注意(GAEのサーバ側で実行した結果を表示しているわけではない)。

例えば、CPU Timeを知ろうとして、

from google.appengine.api import quota
print quota.get_request_cpu_usage()

を実行してみても、ローカル環境での実行に過ぎないため、常に0が返ってくる。

ただし、Datastore用のモジュール(db)の関数やdb.Model(を継承したclassより作られたobject)の関数を実行する場合には、ローカルPCからHTTP-Request(POST)をGAEのサーバに送り、HTTP-Responseで実行結果を受け取っている。

モジュール名に'-'(ハイフン)を使わないこと

Datastoreにアクセスするためには、db.Modelを継承したclass定義の記述があるモジュール(Pythonスクリプト)をimportする必要が有り、この時には、モジュールのファイル名(から拡張子:.pyを除いたもの)を指定する。
例えば、app.yamlに

- url: /.*
  script: appmodule.py

のように記述したappmodule.py内でclass dbClassName(db.Model)を定義してある場合、

appid> from appmodule import dbClassName

のようにして使う。
この際、モジュール名(ファイル名)に'-'(ハイフン)が含まれているとエラーになってしまうため、'-'を含まないようリネームする必要が有る。

DatastoreへのアクセスはQuotaを消費する

上述のとおり、Datastoreへのアクセス時はGAEサーバとのHTTP-Request/Responseのやり取りが発生するため、これはQuotaの消費に繋がる。

entries = dbClassName.all().fetch(500)
db.delete(entries)

のようにして500件のエントリ削除を試してみたところ、

  • fetch()に 5249cpu_ms
  • db.delete()に186756cpu_ms

で、合計192005cpu_ms要した。
これは、普通にapp上で同様の処理を実施した場合(〜200000cpu_ms)とほぼ同等。

DataStoreの1000エントリ以上の扱いには注意

一応、1000エントリより多くのエントリを一度に取り扱うことも可能になっている模様。
ただし、いろいろ制限があるようなので、一度にfetch()したりするエントリ数は1000以下で、かつ、offsetも1000以下にしておく方が無難。
いくつか試してみたところ、

entries = dbClassName.all().fetch(1000) # 正常に取得出来る(ことが多い)
entries = dbClassName.all().fetch(2000) # エントリサイズに依存して返るときと返らないときがある
entries = dbClassName.all().fetch(2001) # いつまで経っても返ってこない
entries = dbClassName.all().fetch(1,offset=1000) # 正常に取得出来る(ことが多い)
entries = dbClassName.all().fetch(1,offset=1001) # いつまで経っても返ってこない
  • offsetを1000より大きくしたり、offset=0で2000件より多くの数を指定すると、HTTP-Request/Responseのやり取りを何回も繰り返し、戻ってこない
  • HTTP-Responseのサイズは2000kbまでとなっているようで、(offset=0の場合)1000件より大きいエントリ数を指定したとき、その合計のサイズが2000kb×2回の範囲に収まっている場合には正常に取得出来るが、サイズがこれを越えると、HTTP-Request/Responseのやり取りを何回も繰り返し、戻ってこない

のような傾向があった。