Google App Engine/Javaでセッション情報を定期的に消す処理

Google App Engineでは、セッション情報はデータストアに保存されます。
で、自動的には消えないので、うかうかするとこんなふうに100万件以上たまってしまいます。


容量もそれなりに食って、350MB近くになっていました。GAEでは1GBを超えると課金されるので、この容量は痛い。


ということで、セッション情報を消す処理を書いてみました。あとは、これを手動で呼び出すもよし、CRONで定期的に呼び出すもよし。
あと、ここのlogメソッドのようなものは、どっか書いとくとログを自分のUIで好きに見れるようにできるので便利。これも定期的にまとめたり消したりする処理必要になりますが。
というか、データ全部消す処理って、どう書くのが一番いいんでしょう?なんかすげー時間かかってるんですけど。
※ shin1ogawaさんに「makeSyncCallサーブレットを立てて、ローカルのプログラムからバッチ削除する方が手っ取り早い」「日付とか関係なしに削除、ログの出力も不要、なら標準でSessionCleanupServletってのもあります」と教えてもらった(16:55)

public class SessionClearServlet extends HttpServlet {
    public static void log(String category, String level, String message){
       DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
       Entity ent = new Entity("Log");
       ent.setProperty("c", category);
       ent.setProperty("l", level);
       ent.setProperty("m", message);
       ent.setProperty("d", new Date());
       ds.put(ent);
   }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        //消去用タスクをキューにいれる
        Queue queue = QueueFactory.getDefaultQueue();
        queue.add(TaskOptions.Builder.url("/queue/sessionclear"));

        log(SessionClearServlet.class.getSimpleName(), "info", "セッションログ消去開始");

        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("queued");
        out.close();
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        //実際の消去処理開始
        long now = new Date().getTime() / (60 * 60 * 1000);
        now *= (60 * 60 * 1000);

        DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
        Query entq = new Query("_ah_SESSION")
                .addSort("_expires", Query.SortDirection.ASCENDING)
                .addFilter("_expires", Query.FilterOperator.LESS_THAN, now);
        //開始ログ
        Iterator<Entity> firstIte = ds.prepare(entq).asIterator(FetchOptions.Builder.withLimit(1));
        if(firstIte.hasNext()){
            Date s = new Date((Long)firstIte.next().getProperty("_expires"));
            log(SessionClearServlet.class.getSimpleName(), "info", s.toString());

            //セッション消去開始
            Query q = entq
                    .setKeysOnly();
            for(;;){
                List<Entity> ents = ds.prepare(q).asList(FetchOptions.Builder.withLimit(200));
                if(ents == null || ents.size() == 0) break;
                List<Key> keys = new ArrayList<Key>();
                for(Entity ent : ents) keys.add(ent.getKey());
                ds.delete(keys);
            }
        }
        log(SessionClearServlet.class.getSimpleName(), "info", "セッションログ消去終了");
    } 
}