タスクチェーンによる30秒制限を超えた処理を試す

タスクチェーンについてよくわかっていなかったので、実験してみました。
実装にあたっては下記を参考にさせていただきました。
Togetter - 「タスクキューのチェインについて」
Togetter - 「タスクキューのチェインについて2(Mapper APIで代替も検討?)」
Togetter - 「DeadlineExceedException発生時のリトライについて」

タスクチェーンのフロー

チェーンの流れはこんな感じになりました。(cacooを使ってオレオレフローチャートで表現)
最初のリクエストで30秒以内に削除が完了すれば、6)7)8)は通らずに完結します。

実際のソースコード

import static com.google.appengine.api.labs.taskqueue.TaskOptions.Builder.*;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.logging.Logger;

import org.slim3.controller.Controller;
import org.slim3.controller.Navigation;
import org.slim3.datastore.Datastore;

import slim3ships.model.Slim3Model;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.labs.taskqueue.Queue;
import com.google.appengine.api.labs.taskqueue.QueueFactory;
import com.google.appengine.repackaged.com.google.common.collect.Lists;
import com.google.apphosting.api.DeadlineExceededException;

public class ClearController extends Controller {
   private static Logger log = Logger.getLogger(ClearController.class.getName());

   @Override
   public Navigation run() throws Exception {

       String triggerTaskName = request.getHeader("X-AppEngine-TaskName");
       if (triggerTaskName == null) {
           log.info("[job start]"); // (1)(2)※ジョブの開始
       } else {
           log.info("[job taskName]:" + triggerTaskName);  //(2)※2回目以降
       }

       List<Key> keys = Datastore.query(Slim3Model.class).asKeyList();//(3)
       try {
           List<Key> partOfKeys = Lists.newArrayList();
           for (Key key : keys) { //(4)
               partOfKeys.add(key);
               if (partOfKeys.size() == 100) {
                   Datastore.delete(key);//(5)
                   partOfKeys.clear();
               }
           }

           Datastore.delete(keys);
           response.setContentType("text/plain");
           response.getWriter().println("delete completed. total size:" + Datastore.query(Slim3Model.class).count());
           response.flushBuffer();
           response.getWriter().close();
           log.info("[job end]"); //(9)
       } catch (DeadlineExceededException e) { //(6)
           String taskName = "task" + format(new Date());
           Queue queue = QueueFactory.getDefaultQueue();
           queue.add(url("/lab/Clear").taskName(taskName));//(7)
           response.setContentType("text/plain;charset=utf-8");
           response.getWriter().println("処理は次のtaskに引き継がれました:" + taskName);
           response.flushBuffer();
       }

       return null;//(8) ,(10)

   }

   private String format(Date date) {
       SimpleDateFormat f = new SimpleDateFormat("yyyyMMddHHmmss");
       f.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo"));
       return "task" + f.format(date);
   }

}

実行結果

タスクがチェーンされて、2回目のチェーンでジョブが完了されました。

I 09-12 11:48PM 02.903 slim3ships.controller.lab.ClearController run: [job end]
I 09-12 11:48PM 01.292 slim3ships.controller.lab.ClearController run: [job taskName]:tasktask20100913154800
I 09-12 11:47PM 32.262 slim3ships.controller.lab.ClearController run: [job taskName]:tasktask20100913154731
I 09-12 11:47PM 02.350 slim3ships.controller.lab.ClearController run: [job start]

疑問点

  • DEEでTQに失敗したら、HardDeadlineExceedErrorのときにリトライする、というようなのを見かけたけどHardDreadlineExceedErrorの発生後にどうやって処理するのか?(catchできないのでは?)
  • とぅぎゃり内で「エラーリトライしてもいいようにタスクは冪等にする」とのことだったが、上記は冪等になっていない。上記はべきとうにしないとまずいケースか?すべきならどうやって冪等にすべきか?param()でkeyのかたまりを投げる?