ご注意
この記事は 2014年3月26日 に書かれたものです。内容が古い可能性がありますのでご注意ください。
GAE上で動くWebアプリケーションに特有の例外について、弊社での運用の事例からいくつか特徴的なものなどをピックアップしてご報告します。
今後追加して行きますので。第一弾です。
Java 7, SDK 1.8.8の環境です。
目次
TooManyResultsException
発生要因
DatastoreからPreparedQuery.asSingleEntity()を利用して1エンティティを取得しようとしたとき、 複数のエンティティが取得された場合に発生するエラー。
原因例
1件だけのはずが複数返却されてきています。原因はいくつかありますが、
- データが論理矛盾を起こしている
- 設計を理解せず複数取得されるのが当然のクエリに対して、asSingle()をしてしまっている
あたりが主な原因です。
対策案
原因が1番の状況では、データをユニークにすることが一番初めに検討するべき対策となるでしょう。
データをユニークにする一番簡単な方法は、ユニークにするべき値(例えば○○IDやアカウント名など)をKeyオブジェクトにセットしてしまう方法でしょう。
Keyオブジェクトが同じentityは、登録する時に必ず一つになります。
ですが、そうもいかないケースもあり得ます。
特にユーザに関するレコードは、メールアドレスをキーにユニーク性を持たることはよくあるケースです。
単純にメールアドレスをKeyにしてしまえばよさそうなものですが、メールアドレスは変更可能ですので、 Keyには別のIdを振って(自動採番など)、なんらか別のロジックでメールアドレスによる一意制約を担保する必要があります。
原因が2番の状況では、データの設計をちゃんと共有することが、なにより重要です。あまりにも当然ですが。
DeadlineExceededException
発生要因
リクエストを60秒以内に処理できなかった場合に発生するGAE特有のエラー。 同一インスタンス上の他のリクエスト諸共Killされることもあるので、非常に厄介である。
原因例
大量にデータを処理する場合や、他システムとの通信を行う場合に発生します。
たまたまDatastoreやMemcacheからのレスポンスに時間がかかったりすると意外と簡単に発生します。
対策案
方法1:TaskQueueやModulesを積極的に利用する。
大量データを処理する場合は、TaskQueue(10分)、Modulesを利用するようにします。
特に大量に処理することがわかっている場合は、何度でも繰り返し実行可能なような仕組みを考える必要があります。
順次処理をするのであれば、件数をこなすために、繰り替えし繰り替えし実行するしか手段がありません。
そうではなく、同時並行で処理することが可能であれば、そちらのほうがより効果的です。
同時処理でも問題ないか見極めた上で、 1つのタスクをなるべく5分以内に処理ができるようにし、TaskQueueにどんどん登録してしまいましょう。
TaskQueueで同時実行数制限が可能なので、とにかくTaskQueueに放り込んでしまい、あとはお好きな並行数で勝手に処理させるのがCloudっぽくてオススメです。
方法2:Timeoutはリトライをするよう心がける。
他システムとの通信の中には、Googleの他のサービスへのアクセスも含まれるので注意が必要です。
Googleの他のサービスとは、Oauth認証、Drive、メール送信などが分かりやすいと思います。
この場合はDeadLineExceededの前にSocketTimeoutExceptionになることも多いので合わせて注意しましょう。
またMemcacheサービスでたまに応答時間が長くなってしまう事象が起きたりしても結果的にこの例外になりますので、例外を適切にハンドリングしたり、リトライをするようにコーディングしましょう。
特にError Code 104 が発生した場合、そのインスタンスで動いているリクエストが全て中断され、 インスタンスがリセットされるので、FrontEndはとにかく60秒制限を意識して実装する工夫が必要となります。
SocketTimeoutException
発生要因
Oauth認証、DriveなどでURLConnectionを使ってreadする場合に起きやすい。
原因例
ネットワーク越しに処理をする場合は、GAEに限らず必ずTimeoutを意識しましょう。
防げるものではないので発生することを前提としてコーディングするのが鉄則です。
その場合、Connectionの開放もれなどを防ぐために、try-with-resources Statementを積極的に用いるといいです。
対策案
いずれの場合も数秒のTimeout時間を設定し何回かリトライをするように実装します。
あまり多くの時間を設定したり、リトライ回数を無限にするのは控えましょう。 前述の時間制限があることを忘れないようにしてください。
タイムアウトには大きくは2種類あります。
1.connect timeout
connect timeoutは接続にかかった時間で、setConnectTimeoutでタイムアウトする時間を設定できます。
引数はミリ秒で指定し、0を指定すると無限になります。
繰り替えしになりますが、GAEのFront Endの場合は数秒程度が望ましいです。
2.read timeout
read timeoutはデータ取得にかかった時間で、setReadTimeoutでタイムアウトする時間を設定できます。
引数はミリ秒で指定し、0を指定すると無限になります。
一節によると以下のメソッドで発生するらしいので気をつけましょう。
- getContent
- getHeaderFields
- getInputStream
- getResponseCode
- getResponseMessage
リトライサンプルコード
以下、Java 7でtry-with-resources Statementを使った例です。
このコードがメソッドにあたり、この上位でfor文によるリトライをします。
[java]
URLConnection connection = new URL(BASE_URL).openConnection();
connection.setConnectTimeout(3000);
connection.setReadTimeout(3000);
try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream(),"UTF-8"))) {
StringBuilder line = new StringBuilder();
String inputLine;
while ((inputLine = in.readLine()) != null) {
line.append(inputLine);
}
}
[/java]
以上、如何でしたでしょうか。今後も追加出来るものができたら追加して行きます。