2009年11月7日土曜日

#appengine の「データストアの読み込み専用状態」をエミュレートする

先日もメンテナンスがあり、AppEngineのデータストアが読み込み専用状態になってましたね。データストアへの書き込みを行うアプリケーションではこの「読み込み専用状態」時の対策をする必要があるのですが、「読み込み専用状態」の時の振る舞いをテストする方法を書いておきます。自動テストができる範囲は広ければ広い程よいですもんね。

ApiProxy.Delegateを実装・適用するだけ

ApiProxy.Delegateを実装して、makeSyncCall()メソッド内で「Datastoreへの書き込み」の時に ApiProxy.CapabilityDisabledException( )を投げてやるだけ。私は下記のようなユーティリティを作ってテストしてます。

static <R>R runOnReadOnlyMode(Callable<R> callable) throws Exception {
  @SuppressWarnings("unchecked")
  final ApiProxy.Delegate<Environment> backupedDelegate = ApiProxy.getDelegate();
  Delegate<Environment> delegate = new Delegate<Environment>() {

    public void log(Environment arg0, LogRecord arg1) {
      backupedDelegate.log(arg0, arg1);
    }

    public byte[] makeSyncCall(Environment arg0, String service, String method, byte[] arg3)
        throws ApiProxyException {
      if (service.equalsIgnoreCase("datastore_v3")
          && (method.equalsIgnoreCase("Put") || method.equalsIgnoreCase("Delete"))) {
        throw new ApiProxy.CapabilityDisabledException(service, method);
      }
      return backupedDelegate.makeSyncCall(arg0, service, method, arg3);
    }
  };
  ApiProxy.setDelegate(delegate);
  try {
    return callable.call();
  } finally {
    ApiProxy.setDelegate(backupedDelegate);
  }
}

こんなカンジにできるので、他にもDatastoreTimeoutExceptionやらApiProxyException系の例外、例えばApiProxy.UnknownExceptionなんかが発生したときの振る舞いも簡単に自動試験できますね!

不思議

Datastoreについては上記の方法で簡単にApiProxy.CapabilityDisabledExceptionをエミュレートできるのですが、MemcacheServiceについては簡単には行かない事が判明しています。

  • MemcacheServiceは例外をキャッチした際に、それをログ出力するだけのハンドラでExceptionを握りつぶす実装になっている
  • 先日の読み込み専用モードの時にログが記録されていない気がする=Memcacheサービスは読み込み専用状態に書き込み要求を受けても例外をスローしない…?

前者についてはMemcacheService#setErrorHandler()メソッドに、new com.google.appengine.api.memcache.StrictErrorHandler()を設定してやればちゃんと例外が飛ぶのでまぁなんとかなるのですが、後者については次回のメンテナンスの時になるまで調査ができませんね。ひょっとするとMemcacheサービスの仕様なのかな?という気もします。「常にMemcacheから値を取得できるとは限らない」という前提なので、書き込みに失敗しても問題ない、という仕様なのかな?それであれば、そもそも試験する必要がありませんね。
どなたか前回のメンテナンス時にMemcacheサービスから書き込みエラーとか何かの例外を受け取った人がおられたら、わかる範囲で良いので教えてください!

読み込み専用モードを考慮してアプリ側で工夫できる事もある

がっつりデータストアに書き込む必要がある処理はどうしよーも無いのですが、例えばカウンタの記録など「書き込みがメインでは無いが、書き込み処理も付随する」ような機能はちょっと工夫しておけば読み込み専用時にも稼働させる事ができます。

私が稼働させているAppEngineアプリのひとつに空うさぎというFriendFeed/Twitter/RSSクライアントアプリケーションのファイル管理用の小さいアプリケーションがあるのですが、このアプリの中にファイルのダウンロード件数を記録する、という機能があります。この機能は「ファイルのイメージをレスポンスする」と同時に「ダウンロード数カウンタを増加させるTaskをQueueに投入する」という実装をしています。投入されたTaskはデータストアの書き込みに成功しない場合はHttpStatusの500を返すように実装しているので、読み込み専用時は常に500を返す事になります。その結果、読み込み専用モードが解除されるまではAppEngineのTaskQueueに残り続けるので、読み込み専用モードが解除された頃にはカウンタの整合性はいずれ正しくなる、という動作をします。AppEngineはまだベータバージョンですし、工夫でしのげる場所はうまく実装しておきたいですね。

コメントを投稿