プログラマ的京都生活

2009年08月30日

[]GAE/Jの実行環境

「今明らかにされるGoogle AppEngine for Javaの実行環境」を読んで自分でも作ってみました。


http://gae-env.appspot.com/show


ソースコードは以下です。java.lang.Runtimeとorg.apache.commons.lang.SystemUtilsを利用しました。

public void doGet(HttpServletRequest req, HttpServletResponse resp)
		throws IOException {

	resp.setContentType("text/plain");

	PrintWriter writer = resp.getWriter();
	try {
		Runtime env = Runtime.getRuntime();

		writer.println("processors: " + env.availableProcessors());
		writer.println("freeMemory: " + env.freeMemory());
		writer.println("totalMemory: " + env.totalMemory());
		writer.println("maxMemory: " + env.maxMemory());

		Field[] fields = SystemUtils.class.getFields();
		for (Field f : fields) {
			writer.println(f.getName() + ": " + f.get(null));
		}
			
		writer.println("\n=================================================================");
		writer.println("source code is here : http://d.hatena.ne.jp/mtoyoshi/20090830/1251600442");
	} catch (Exception e) {
		throw new RuntimeException(e);
	} finally {
		writer.close();
	}
}

確かにプロセッサ数は1,337になってる。でもって、やっぱり空きメモリは6MB程度なんだ。

大丈夫なんか?と思って、ちょっと負荷をかけるプログラムを作りました。


http://gae-env.appspot.com/show2


ソースは以下。Effective Java 第2版の負荷がかかるプログラム例(項目5 不必要なオブジェクトの生成を避ける)に少し手を加えたもの。ロジック前後は先ほどのプログラムとほぼ同じなので略します。

・・・略・・・
List<Long> list = new ArrayList<Long>();
for (long i = 1; i < LOOP_MAX; i++) {
	list.add(i);

	if (i % 1000000 == 0) {
		writer.printf("%d : free: %d, total: %d\n", i, 
			Runtime.getRuntime().freeMemory(), 
			Runtime.getRuntime().totalMemory());
	}
}

writer.println("======================================");
			
Long sum = 0L;
for (long i : list) {
	sum += i;
	if (i % 1000000 == 0) {
		writer.printf("%d : free: %d, total: %d\n", i, 
				Runtime.getRuntime().freeMemory(), 
				Runtime.getRuntime().totalMemory());
	}
}
・・・略・・・

ちなみにループの最大値のところにある「500」を「400」にした途端、GAE上ではプログラムは正しく実行されなくなります。おそらくOutOfMemory的なことになっていると思われます。なので、このプログラムはそのギリギリのところをついたプログラムになっているはずなんですが、途中経過を表すfreeMemoryやtotalMemoryの出力値はずっと変わりません。さすがにおかしいと思うので、このメモリについてはちょっと信用ならないかもしれません。

なお、ローカルでこのプログラムを実行すると、以下のようになります。

freeMemory: 3023488

totalMemory: 5177344

maxMemory: 532742144

======================================

1000000 : free: 2045032, total: 25632768

2000000 : free: 7760152, total: 55730176

3000000 : free: 11103400, total: 82698240

4000000 : free: 24291032, total: 123215872

======================================

1000000 : free: 12426064, total: 123215872

2000000 : free: 19527312, total: 123215872

3000000 : free: 19043136, total: 123215872

4000000 : free: 18558960, total: 123215872

======================================

freeMemory: 13717200

totalMemory: 123215872

maxMemory: 532742144

======================================

1 + 2 + 3 + ... + 4294967 = 9223368618061

2009年08月08日

[][]Google App Engine Hackathon in Kyoto参加してきた

忘れないうちに感想を。

  • 約30人くらいが4人程度のチームに分かれて1日中プログラミングをするという雰囲気がすごい良かった!
  • 保守性を考えずにコードを書きまくるのは仕事ではできない事なのでエキサイティング!publicフィールドの嵐(笑)
  • 久々にペアプログラミングした。Eclipseの使い方で知らないものがあったので収穫♪
  • 事前準備超重要
    • 2日前に事前MTGを行い、モデリング、画面設計の再確認、仕様の再確認を行った
    • 当日スムーズに行くための事前課題をやってきた。そのおかげで、当日誰かが「待ち」になることはなかった!
  • フレームワークSlim3を使った。Slim3はさすがGAE用に作られているだけあって使いやすかった。変にはまる事無くスムーズに開発できたのはかなり大きかった。
  • 「役に立つもの」にフォーカスしすぎた。もっと「面白い」要素を入れるべきだった。
    • Javaチームに比べると、JRubyPythonPHP(実はPHP使っていないw)チームは技術的要素、笑える要素を入れて楽しんでいた印象。
  • Java以外のチームのPCのMac率がかなり高かった。
  • Hackathonというネーミングから敷居の高さを感じるが、実際は最近プログラミングしていないなという方や、普段の業務はWEB系じゃないんだけど、ちょっとやってみたいという方も参加できる結構フランクなイベント。大学生も何人かいた。平均年齢25歳という噂も。
  • Googleの人が3人来ていた。喋っている内容が数式とかPythonではなく、日本語だったので理解出来た。
  • プレゼンは笑い重要。見た目のインパクト重要。
  • Google App EngineのTシャツ欲しかった。。
  • 京都岡山だと言うことが分かった。
  • ニコニコ動画の生放送があった。現場でいると、楽しいというよりハラハラドキドキ。コメントちょっと怖い。
  • 終わった後の居酒屋でのビールがすごい美味しかった!!!

とまぁこんな感じでしょうか。

最後に、今回プログラミングしている最中に一回だけはまったことがあったのでそれをメモしておきます。

GAEで多対多を実現しようとすると、関連先のキーをSetなどのコレクションで保持しておく必要があります。で、GAEのドキュメントにはこのようにあります。

Key 値を使用して多対多関係をモデリングする場合、関係の両側を管理するのはアプリケーション側の役目であることにご注意ください。

つまり、Keyからオブジェクトに変換する部分は自分でやってね、ということです。で、私は次のようなコードを書きました。

List<Xxx> list = pm.getObjectsById(keySet);

pmってのはPersistenceManagerです。これを実行しても一向にオブジェクトは取得できず、「そんなキーのオブジェクトねぇよ!」というエラーが出てしまいます。

実はこれ、使用するAPIが違っていました。ひがさんのblogにこれの解決策があります。

http://d.hatena.ne.jp/higayasuo/20090522/1242963317

あまりにもはまってしまってGoogleの方に質問して教えていただきましたw。あの時は本当に助かりました。ありがとうございました。

2009年08月02日

[]ドキュメントが日本語に

先週は英語だったのに、今週見たらドキュメントが日本語化されていた♪これで学習がより進む〜。

http://code.google.com/intl/ja/appengine/docs/java/overview.html

2009年07月26日

[]The Java Servlet Environmentの翻訳

ついでに訳してみた。

原文:http://code.google.com/intl/ja-JP/appengine/docs/java/runtime.html


++++

App Engineは安全なSandBox環境でJava6を使ったJava Webアプリケーションを実行します。

App Engineはリクエストを処理し、レスポンスを返すためにアプリケーションのServletクラスを実行します。


Selecting the Java API Version

使用しているSDK、APIバージョンはWEB-INF/libにあるappengine-api-*.jarというjarによって表現されます。

もちろん、新しいjarを再配置するまでは、前のバージョンを使い続けることもできます。

※2009年7月現在で最新にアップデートすると「appengine-api-1.0-sdk-1.2.2.jar」という名前です。

GAEアプリケーションを新規に作成する時に、以下のようにバージョンを選択できるようになります。

f:id:mtoyoshi:20090726110157p:image


Requests and Servlets

App EngineはWEBリクエストを受け取ったら、デプロイメントディスクリプタの設定にしたがってservletを実行します。

App Engineはアプリケーションを実行するのに複数のWEBサーバーを使用します。サーバーの数は自動的に調整されるため、同じユーザーからのリクエストであっても前のリクエストを処理したサーバーと同じとは限りません。


Response

レスポンス送出時のストリーミングデータはサポートされていません。

クライアントがgzipなどの圧縮されたコンポーネントを受け取ることが出来るということが書かれたHTTPヘッダを送信してきた場合、App Engineは自動的にレスポンスデータを圧縮します。


The Request Timer

リクエストハンドラは30秒で処理しなければならないという制限があります。

30秒に達した場合はcom.google.apphosting.api.DeadlineExceededExceptionをスローすることで、リクエストハンドラの処理を中断します。もしこの例外をcatchしていない場合は、500エラーをクライアントに返します。

さすがに例外をcatchして独自のレスポンスを返す程度のちょっとの時間くらいは与えますよ。といっても1秒程度ですが。


While a request can take as long as 30 seconds to respond, App Engine is optimized for applications with short-lived requests, typically those that take a few hundred milliseconds. An efficient app responds quickly for the majority of requests. An app that doesn't will not scale well with App Engine's infrastructure.

※この訳が分からなかった。というか「short-lived request」が何を指しているかよく分からなかった。


Sandbox

複数のWEBサーバー間でHTTPリクエストを振り分けることを許すために、またアプリケーションが他からの干渉を防ぐために、アプリケーションは厳格な「サンドボックス」な環境で実行されます。

サンドボックスの意味は、以下を参照。

http://e-words.jp/w/E382B5E383B3E38389E3839CE38383E382AFE382B9.html


GAEアプリケーションが出来ないこと:

  • ファイルシステムへの書き込み
    • App Engineデータストアに書き込んでください。
    • ファイルの読み込みはOKです。
  • ソケットのオープンや他のホストへの直アクセス
    • App Engine URLフェッチサービスを使ってください。
  • サブプロセスやスレッドの生成
    • WEBリクエストは2・3秒以内のシングルプロセスで処理されなければならない。
    • 長い時間かかる処理はWEBサーバーの過負荷を避けるために終了させられます。
  • システムコール

Threads

ThreadGroupやThreadを新たに生成することは出来ません。つまり、ThreadPoolExecutorやTimerといったThreadを利用するクラスも使えないので注意してください。

ただし、現在のスレッドに対するメソッドは実行できます。


Filesystem

FileWriterのようなファイルシステムへの書き込みを行うクラスは利用できません。

ただし、FileReaderのようなクラスを使った読み込みは可能です。Class.getResource()やServletContext.getResource()のようなリソースとしてのアクセスも可能です。


java.lang.System

いくつかのメソッドが利用できなくなっています。

  • exit(), gc(), runFinalization(), runFinalizersOnExit()

いくつかのメソッドはnullを返します。

  • inheritedChannel(), console()

ネイティブなJNIコードは実行できなかったり、そもそも提供されてなかったりします。以下は実行するとjava.lang.SecurityExceptionが起こるメソッド一覧。

  • load(), loadLibrary(), setSecurityManager()

Reflection

アプリケーションのクラスに対しては、AccessibleObject.setAccessible()を使うことでプライベートなメメンバーへの読み込みや書き込みが出来ます。

java.lang.StringなどのJRE、javax.servlet.http.HttpServletRequestのようなAPIクラスに対してもリフレクションを使うことが出来ます。ただし、その場合はprivateやprotectedなものには実行できず、publicなメンバーへのアクセスに限定されます。

アプリケーションに属さない他のクラスにはリフレクションを実行することは出来ません。

An application cannot reflect against any other classes not belonging to itself, and it can not use the setAccessible() method to circumvent these restrictions.


Custom Class Loading

完全にサポートしています。

AppEngineはアプリケーションによってロードされた全てのクラスに対して同じ権限を割り当てるために、全てのクラスローダーをオーバーライドします。

Custom class loading is fully supported under App Engine. Please be aware, though, that App Engine overrides all ClassLoaders to assign the same permissions to all classes loaded by your application. If you perform custom class loading, be cautious when loading untrusted third-party code.


JRE White List

JREのいくつかのクラスは利用できないように制限されています。詳しくは以下参照。

http://code.google.com/intl/ja-JP/appengine/docs/java/jrewhitelist.html


Logging

java.util.logging.Loggerを使ってログを出力することが出来ます。

管理コンソール上でログを確認できます。また、ダウンロードすることも出来ます(詳細は以下)。

http://code.google.com/intl/ja-JP/appengine/docs/java/tools/uploadinganapp.html#Downloading_Logs

System.outやSystem.errへの書き込みは全てAppEngineによってキャプチャされ、アプリケーションログに記録されます。

System.outはINFOレベル、System.errはWARNINGレベルです。

log4jのようなログフレームワークもまた利用可能です。しかしながら、java.util.logginアダプタを使うことで、管理コンソールのログレベルの表示をよりよく管理できます。

※プログラム例あり


logging.propertiesを、WEB-INF/classesもしくはWARの中にコピーしてください。次に、appengine-web.xmlのsystem-propertiesに設定を追加してください。

※設定例あり


The Environment

システムプロパティーや環境変数はJVM全体ではなく、あなたのアプリケーションにのみ適用されます。

それらはデプロイメントディスクリプタ(http://code.google.com/intl/ja-JP/appengine/docs/java/config/webxml.html)に設定することが出来ます。

アプリケーションサーバー上のJVMを初期化するときに、以下のシステムプロパティーが設定されます。


Quotas and Limits(割り当てと制限)

LimitAmount
リクエストサイズ10MB
レスポンスサイズ10MB
リクエスト継続時間30秒
同時接続数(simultaneous dynamic requests)30
ファイル総数(アプリケーションファイルと静的ファイル)3,000
アプリケーションファイルの1つあたりの最大サイズ10MB
静的ファイルの1つあたりの最大サイズ10MB
全てのアプリケーションファイルと静的ファイルのサイズ総数150MB

同時接続数30について

アプリケーションは、同時に、約30のリクエストを処理できます。 これはサーバーサイドの平均リクエスト処理時間が75ミリ秒であるアプリケーションの場合、1秒に400リクエストを処理できることを意味しています。ただし、CPUに負荷をかける重い処理をするアプリケーションは、同一サーバー上で共有している他のアプリケーションにリソースを割り当てるために、待ち時間が発生するかもしれません。

なお、静的ファイルに対するリクエストはこの「30」には含まれません。

[]デプロイメントディスクリプタの翻訳

デプロイメントディスクリプタにもいくつかの拡張がなされていたり、使えないものがあったりするようです。その部分だけを抜き出して翻訳してみました。


Security and Authentication

App Engineではユーザー認証にGoogle Acountsを利用できます。アプリケーションは、Google Acounts APIを使うことで現在サインインしているかどうかを調べることができ、なおかつsign-in/sign-outのURLを生成します。デプロイメントディスクリプタを使って、URLパスに対してアクセス制限を設けることが出来ます。

<security-constraint>要素はパターンにマッチしたURLにセキュリティ制約を定義できます。もし定義されたURLにサインインしていない状態でアクセスしてきた場合は、Google Accountsサインインページにリダイレクトします。サインインした後はもとのアプリケーションのURLにリダイレクトします。

また、<security-constraint>要素は<auth-constraint>要素を子要素に持つことができます。ここではマッチしたロールのユーザーだけがアクセスできる、と言ったことが出来るようになります。だれでもOKならば「*」を使ってください。

※設定例あり

なお、App Engineは<security-role>などのカスタムセキュリティロールや他の認証メカニズムである<login-config>などはサポートしていません。


Secure URLs

GAEは*.appspot.comドメインを使っているURLに対してHTTPSのセキュアなコネクションをサポートしています。

一方、Google Appsドメインは現在、HTTPSをサポートしていません。もしGoogle Appsドメインに対してHTTPSリクエストが来た場合は、「host not found」エラーを返します。

アプリケーションでセキュアなURLを利用したい場合は、appengine-web.xmlで<ssl-enabled>true</ssl-enabled>と定義する必要があります。詳しくは以下参照。

http://code.google.com/intl/ja-JP/appengine/docs/java/config/appconfig.html#Enabling_Secure_URLs__SSL_

次に、デプロイメントディスクリプタの<security-constraint>に定義が必要です。

※設定例あり


web.xml Features Not Supported

<load-on-startup>要素をサポートしています。しかしながら、実際はアプリケーションの起動時ではなく、最初のリクエストがあった時となります。

However, the load actually occurs during the first request handled by the web server instance, not prior to it.


※MIMEタイプの説明はちょっとよく分からなかった。

App Engine supports <mime-mapping> elements for specifying the MIME type to use for resources whose filenames end with certain extensions. However, MIME mappings only apply to servlets, not to static files. Static files use a fixed list of mappings of filename extensions to MIME types.


デプロイメントディスクリプタの要素には、IDEで使用する際の表示名・詳細記述・アイコンを定義できるものがありますが、App Engineはこれらを使わず、無視します。

また、以下のものをサポートしていません。

  • JNDI環境変数(<env-entry>)
  • EJBリソース(<resource-ref>)
  • <distributable>要素
  • <run-at>を使用したサーブレットスケジューリング

2009年07月25日

[]FAQの翻訳

GAE/Jと英語の両方を勉強中なので、勉強がてら訳してみた。といっても要約がメインです。

原文:http://code.google.com/intl/ja-JP/appengine/kb/java.html


GAE上でお気に入りのフレームワークを使いたいのですが

利用可能なものはここに掲載しています。

http://groups.google.com/group/google-appengine-java/web/will-it-play-in-app-engine?pli=1

Javaで有名なStrutsは1系も2系もOKです。


VM上でどのプログラミング言語が使えるのでしょうか

同じくここに掲載しています。

http://groups.google.com/group/google-appengine-java/web/will-it-play-in-app-engine?pli=1

※Ruby、PythonPHPなどが使用可能です。


ファイル読み込みがうまくいかないのですが

あなたのアプリケーションの以下の場所にあるファイルなら読み込むことができます。

  • war/WEB-INF
  • appengine-web.xmlの<resource-files>に定義されている場所

もしファイルの配置場所が問題ないのにエラーが起こっている場合は、ファイル読み込みメソッドに問題があるかもしれません。

File, FileInputStream, FileReader, RandomAccessFileが利用できるファイル読み込みクラスです。

以下がGAE上で使用できるJavaのクラスファイル一覧です。ここにリストアップされていないクラスを使っていないか確認してください。

http://code.google.com/intl/ja-JP/appengine/docs/java/jrewhitelist.html

なお、jarファイルにプロパティファイルのようなリソースを含めた場合は、ClassやClassLoaderを使用して読み込んでください。


ファイル書き込みは利用できますか

できません。

代わりに、App Engine データストアに永続化してください。詳しくは以下を参照のこと。

http://code.google.com/intl/ja-JP/appengine/docs/java/runtime.html#The_Sandbox


スレッドは利用できますか

できません。詳しくは以下を参照のこと。

http://code.google.com/intl/ja-JP/appengine/docs/java/runtime.html#The_Sandbox


既にPythonアプリケーションがあるんですが、そちらのデータにアクセスすることはできますか

はい、Javaコードからアクセスすることができます。プロパティ名などは言語を超えて同じものとなっています。


同じアプリケーション内でJavaPythonのコードを実行することは出来ますか

はい、可能です。

バージョン xはJavaで実行し、バージョン yは(Jythonを使って)Pythonで実行するといったことが可能です。


ログ出力時にUserServiceFailureExceptionが出たのですが

アプリケーションGoogle Appsドメインに連携している場合、Google Appsアカウントのサブドメイン上のアプリケーションにアクセスしなければなりません。

example.comがあなたのGoogle Appsドメインなら、例えばwww.example.comのようなサブドメインを作る必要があります。


全てのリクエストハンドラが初期化失敗例外(Initialization failed exception)を出すんですが

JSPを事前にコンパイルしていると発生するかもしれません。

Jasperを使ったJSPの事前コンパイルをしているなら、以下のjarファイルをWEB-INF/libに配置してください。

ELとloggingは使っていなくても必要ですので注意してください。


JavaEEをサポートしていますか

全てではありませんが、多くのコンポーネントをサポートしています。詳しくは以下を参照してください。

http://groups.google.com/group/google-appengine-java/web/will-it-play-in-app-engine

※例えば、JSP、Servlet、JavaMail、JSFなどをサポート。EJBやJMX、JDBCなどはサポートしてない。


ファイルアップロードはどうすればいいですか

Apache Commons file uploadパッケージのクラスを使ってmultipart formデータを取得できます。

※FileItemStream, FileItermIterator, ServletFileUploadを使った例が示されています。

参考:


ローカルで開発する時に、既に8080ポートを使っているんですが、変更できますか

可能です。

--port=希望のポート番号

もしくは、antのターゲットファイルに以下のように設定してください。

<dev_appserver war="war" port="希望のポート番号"/>


GAE上でGoogle Data APIライブラリを使用できますか

利用できます。

ただし、権限エラーを避けるために以下のような設定をappengine-web.xmlファイルに追加してください。

<system-properties>

<property name="com.google.gdata.DisableCookieHandler" value="true"/>

</system-properties>

Google Data APIライブラリの詳細は以下。

http://code.google.com/p/gdata-java-client/


Eclipseプラグインがうまく動かないんですが

Error Logビュー(Window - Show View - Other...を選択し、General - Error Log)で確認してください。


アプリケーションのアップロード時に「Invalid runtime specified」が出るのですが

アカウント認証で失敗している可能性があります。今は10,000人しか利用できないという制限があります。


appcfgのEmail引数が動いていないようですが

Emailコマンドライン引数はappcfgオペレーションの前に指定されなければなりません。

例えば以下のように。

appcfg.sh -e youremail@example.com update app_directory/war