2010-05-22

DeadlineExceededException と HardDeadlineExceededError

サーブレットの処理が 30 秒で終わらないときに出る
com.google.apphosting.api.DeadlineExceededException
というのは知ってたんだけれど、最近ログに現れた
com.google.apphosting.runtime.HardDeadlineExceededError
というのは一体何?…と思い調べてみた。

すると ajnk1 で話を聞いた bufferings さんのページを発見。 ほほう、30 秒ルールにひっかかって発生した DeadlineExceededException も、catch でつかまえたり、finally でロールバックしたりできるんだ。その処理に 300~400ms 近くかかってしまうと、次は HardDeadlineExceededError が出て有無を言わさず強制終了になってしまう…という理解でいいのかな。

300ms でできる処理って…。まあ何に使うのかわからないけど、一応覚えとこっと。 には、TaskQueue で呼ばれた Task で HardDeadlineExceededError が出た場合には、リトライがされないというような記述も。

としたら、DeadlineExceededException を catch とか、finally でロールバックとか、下手にしない方がいいのかも。

2010-05-09

GAE/J で Amazon Product Advertising API

今日は Google App Engine for Java で Amazon Product Advertising API を使うためのヒントみたいなもの。「Amazon Product Advertising API」は、2009 年 5 月まで「Amazon アソシエイト Web サービス」と呼ばれていたので、そちらの名前で覚えている人も多いかと。このサービスを使うとアプリケーションから簡単に Amazon の商品を検索することが可能に。この API 詳細は で理解してもらうとして、問題はその Signature ―― サービスの名称変更とともに API のリクエスト URL につけることが義務づけられた電子署名がちょっと大変かも。 リクエスト自体は、これまでのものにタイムスタンプを追加して、パラメータの値を RFC3986 のパーセントエンコード(URL エンコードとわずかに違う)、バイト順にパラメータを並べ替え。そのリクエスト全体の文字列をユーザーの秘密キーで HMAC(SHA256 アルゴリズム)を作成し、BASE64 に変換、パーセントエンコード後 Signature パラメータとしてリクエスト末尾につければ、ようやく API へのリクエストの URL の完成という流れ。ああ、ややこしい。

以前からこのサービスを使って、クリボウの Blogger 入門サイドバーとかで実際にキーワードを元に広告を表示しているのだけれど、これは何年か前に Perl で書いたのを を参考に、2009 年に書き直したもの。じゃあ GAE/J では、認証をどうするんだろう?と思ったので調べてみた。前置きが長かったけれど、結論は簡単。Amazon が公開している Java のサンプルコードを利用するだけ。 …だけ、とか書きながらこのままだと動かないのでちょっと修正。


サンプルコードの修正

import org.apache.commons.codec.binary.Base64;
まず、この BASE64 変換を行う org.apache.commons.codec.binary.Base64 のライブラリがないと思うので、以下からゲット。 Binaries 形式のものをダウンロード、解凍してできた commons-codec-*.*.jar をプロジェクトの WEB-INF/lib に入れてビルドパスに加える。
private String endpoint = "ecs.amazonaws.com"; // must be lowercase
private String awsAccessKeyId = "YOUR AWS ACCESS KEY";
private String awsSecretKey = "YOUR AWS SECRET KEY";
次に 33、34、35 の各行。34、35 行は、自身の Amazon Web Service アクセスキーと、 Amazon Web Service 秘密キーを入力。当たり前か。33 行は、そのままだとアメリカの amazon.com の結果を表示してしまうので、
private String endpoint = "ecs.amazonaws.jp"; // must be lowercase
と書き換えておく。

3つ目は、40 行からのコンストラクタで、UnsupportedEncodingException、NoSuchAlgorithmException、InvalidKeyException が出るぞと警告されてしまうので、throws するなり catch するなり。

最後は、なんでかわからないんだけれど、Signature に無駄な %0D%0A という文字列(パーセントエンコード前は「CRLF」改行)が入ってしまうのを阻止する。いや原因がわからず、阻止の仕方もわからないので、
signature = new String(encoder.encode(rawHmac));
らへんのコードを
signature = (new String(encoder.encode(rawHmac))).replaceFirst("[\\r\\n]*$", "");
と、パーセントエンコード寸前に最後の改行文字を削除させる。なんて対症療法!と思うけれど、まあこれで動くので。だれか詳しい人は教えて下さい。


使い方

HashMap<String, String> map = new HashMap<String, String>();
map.put("Service", "AWSECommerceService");
map.put("Version", "2009-01-06");
map.put("Operation", "ItemSearch");
map.put("SearchIndex", "Books");
map.put("ResponseGroup", "Small,Images");
map.put("Keywords", "Google App Engine");
map.put("AssociateTag", "kuribosblogge-22");
map.put("ItemPage", "1");

SignedRequestsHelper srh = new SignedRequestsHelper();
String url = srh.sign(map);
とかいう風に使うと、url にちゃんと AWSAccessKeyId や Timestamp が補われ、パーセントエンコード、ソートもされて、Signature の付加された URL が入るはず。やってみると…。
http://ecs.amazonaws.jp/onca/xml?AWSAccessKeyId=0E87C7QWYAM53DHJ2682&AssociateTag=kuribosblogge-22&ItemPage=1&Keywords=Google%20App%20Engine&Operation=ItemSearch&ResponseGroup=Small%2CImages&SearchIndex=Books&Service=AWSECommerceService&Timestamp=2010-05-09T08%3A30%3A56Z&Version=2009-01-06&Signature=VHsoRNX4slmxgG%2BS7xLKqcSf3Ogf9r7Qa1I8TILySMk%3D
ほらほら。これに URLFetch でアクセスすると、「Google App Engine」にヒットする書籍のデータ(画像も)が XML 形式で返ってくるので、解析してゴニョゴニョと上手く表示してやれば OK。ちなみに Signature の計算が間違っていると、
<?xml version="1.0"?>
<ItemSearchErrorResponse xmlns="http://ecs.amazonaws.com/doc/2009-01-06/"><Error><Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.</Message></Error><RequestID>17711f2a-00c6-415f-9995-6f3f96d0ae57</RequestID></ItemSearchErrorResponse>
みたいに返って来て、データは全く取れないので、チェックしてみるといいと思う。

This request caused a new process to be started for your application.

This request caused a new process to be started for your application, and thus caused your application code to be loaded for the first time. This request may thus take longer and use more CPU than a typical request for your application.
と Logs に出てくるのが気になっていたんだけれど…。

#appengine でスピンアップのリクエストの時ってWarningのログを出してくれている? "This request caused a new process to be started for your application"って出てるless than a minute ago via sobees


スピンアップした(アプリケーションが起動した)のがわかるようになってたのか。こりゃ便利。CPU 時間がいつもよりかかったリクエストの理由を知ったり、スピンアップ、スピンダウンの間隔を知ったりするのにいいね。

2010-05-01

Google App Engine Datastore Performance Test

Google App Engine Datastore Performance Test なるアプリを発見。

Google App Engine Datastore Performance Test

Python、Java の JDO、Java の Low-level API と、アクセスの手段別のデータストア処理時間が計測可能。

エンティティの追加に関しては Java の Low-level API を使った処理の方が Python 版より速いようだが、更新となると Python 版の方に軍配が上がるみたい。

…というか、JDO が遅すぎだということを再確認。

GAE に新しい Quota が加わるそうな

Tasks Strage Quota

Google App Engine のダッシュボードから Task Queues をクリックすると、見慣れない Quota 表示が。

Tasks Daily Quota として Task Queue API Calls があるのは前からのことなので気にならないのだけど、Tasks Storage Quota として、Task Queue Stored Task Count と Task Queue Stored Task Bytes という項目が新たに加わった様子。

画像は 4 月 29 日のもの。まだ適用されない Quota だとはいえ、171 % とか、1014 % とかいう数字は、刺激が強い…。同日行われた GAE のメンテナンスのせい…だと信じたい。