Google App Engine for Javaでわたしがはまった5つのポイント
昨年の10月から半年間、携帯端末から回収した検索サイトの利用実績を管理するサービスを、Google App Engine上で動作するアプリケーションとして開発していました。Webアプリケーションの開発経験が生かせるようにと、サーブレットAPIを提供するGoogle App Engine for Javaを採用することになったのですが、思いのほか実装に苦戦しました。
そこで、Google App Engine for Javaを使ったアプリケーション開発でわたしがはまってしまったポイントを紹介します。これらの中には、最新のSDKや実行環境において解消されているものもあると思いますが、それでも何かの役に立てば幸いです。
#1 JREホワイトリスト
XMLファイルの解析にDOM Level 3 Load and Saveを使用して実装しました。開発用サーバでは期待どおりXMLファイルを解析することができましたが、App Engineにアップロードしてみたところ、
org.w3c.dom.bootstrap.DOMImplementationRegistry.newInstance()
を呼び出した時点で、内部の実装で使用する
DOMXSImplementationSourceImpl
クラスが見つからないというエラーが発生しました。
JREホワイトリストにDOMImplementationRegistry
が載っていたので、App Engine上でも期待どおり動作してくれてもよさそうなものですが、うまくいきませんでした。ただ、幸いにしてJAXPを使った実装に切り替えたところ、App Engine上で期待どおりの結果を得ることができました。
JREホワイトリストに利用する予定のAPIが掲載されていても、念のためApp Engine上で動作確認しておくことをお勧めします。
#2 データストア
Java版ではデータストアへのアクセスにJDO APIが使用できます。SQLライクなクエリ(JDOQL)が利用できてしまうせいか、エンティティをRDBのテーブルのように設計してしまいがちです。例えば、所有関係を使用しなかったり、複数のフィールドの組み合わせでエンティティを識別しようとしたり……データストアをRDBの代替と考えるとはまってしまいます。
わたしが担当したアプリケーションは、1回のリクエストでデータストアに永続化した大量のデータにアクセスする必要があり、このようなエンティティ設計がボトルネックになってしまいました。このため、作業期間の大半を性能測定とエンティティの再設計に費やすことになりました。最終的に、エンティティ間に適切な所有関係を設定し、永続化するデータの見直しを行い、なんとか使用に耐える程度の性能向上を実現できました。
また、エンティティ・グループ単位でしかトランザクション操作を行えないこともエンティティの設計を難しくする要因だと思います。
#3 サーブレットのロード
アイドル状態のサーブレットがアンロードされる時間が、一般的なWebアプリケーションサーバのそれよりも短いようで、想像よりも頻繁にサーブレットがロードされてしまいます。このとき、リクエストの処理時間のほかにサーブレットをロードする時間を必要としますが、Google App Engineの30秒ルール(リクエストを処理し、レスポンスを返すまでの制限時間)にこのロード時間が含まれてしまいます。
このため、同じ条件でサーブレットを呼び出しても、ロードされている場合には期待どおりの動作をしますが、アンロードされている状態から呼び出したとき、DeadlineExceededExceptionが発生してしまう場合があります。
処理時間が長くて1回のリクエストで処理しきれない場合、処理を分割して複数のリクエストで実行することになりますが、安易に「20秒経過したら処理を中止し、次回のリクエストで処理を継続する」といった経過時間に依存する処理分割は、サーブレットのロード時間のせいで30秒ルールの餌食になる可能性があります。
わたしが担当したアプリケーションでは時間のかかる処理を細分化し、タスクキューを使用してバックグラウンドで実行するように実装しました。
#4 インデックス
わたしが担当したアプリケーションでは、前述のとおり性能問題が発生したために性能測定とエンティティの再設計を目標とする性能が得られるまで何度も繰り返しました。その結果、管理コンソールのDatabase Indexesに、使用されなくなったインデックスがずらりと列挙されてしまいました。
インデックスの総数は、割り当て制限がかかっています。無料のデフォルト割り当てでもそうそう消費し尽くすことはありませんが、気分的に不要なインデックスは取り除きたいと思うものです。しかしながら、Google App Engine SDK for Javaには、App Engine上に配置されたアプリケーションのインデックスを削除する方法がありません。
このため、インデックスを削除するためには、Google App Engine SDK for Pythonの開発環境を構築し、appcfg.py
のvaccume_indexes
コマンドを使用するしかありません。たとえば、myApp というアプリケーションでは、ダミーの myApp
というアプリケーションのプロジェクトを作成したのち、次のようなコマンドを実行します。
> python appcfg.py vaccume_indexes myApp
本来ならば、これで不要なインデックスを削除できるのですが、わたしの作業環境ではインターネットへ直接接続することができません。当然、プロキシサーバを経由する必要がありますので、しかるべき設定を行ったのち、上記のコマンドを実行しました。ところが、プロキシサーバがネットワークエラーを返送してしまい、App Engineに接続できませんでした。
仕方なく、DMZにアクセス権限を持つ研究員にお願いして不要なインデックスを削除してもらいましたが、もっと楽に削除できないものでしょうか。
#5 日本語ドキュメント
Google App Engineには日本語のホームページがあります。ドキュメントやFAQなど、日本語のリソースが整理されているのですが、翻訳ミスとはいえない、明らかな誤植をたまに見かけます。わたしがはまったのは、割り当てのページです。CPU時間について、無料のデフォルト割り当てで 1日あたりの限度が日本語のページでは「46CPU時間」、英語のページでは「6.5CPU-hours」となっていますが……。これは6.5CPU-hoursが正解です。
アプリケーションの性能測定のとき、大量の測定用データを投入する必要があったのですが、1日の割り当てが 46CPU時間あると信じ込んで何気なくデータを投入したところ、15% 程度のCPU時間を消費した時点でサーブレットが503(Service Unavailable)を返送しはじめ、管理コンソールのDatastore Viewerが使用不能……慌ててドキュメントを読み直すことになってしまいました。
このほか、日本語のホームページは最新の情報に追従できていないようです。貴重な日本語の情報なのに……残念です。
■ おわりに……
Google App Engineでアプリケーションを作成していて最もやっかいだったのは、やはり30秒ルールです。ブロードバンドが普及した今日において「制限時間 30秒」というのは妥当だと思いますが、実際にアプリケーションを実装してみるとかなり厳しい制約です。
30秒以内に応答が返せなければ、負荷を最適に分散してくれようとも、どんなにデータストアがスケールしてくれてもDeadlineExceededException
……サービス不能です。1リクエストの処理時間を減らして、いかに有益なアプリケーションを作成するか……センスが問われるプラットホームのようです。