任意の Kind の Entity を全て削除する

Kind の Entity を削除したい時に、Dashboard からだとチマチマしか削除できないので全件削除する Servlet を書いた。
DataStoreCleaner が DataStore にアクセスして Entity の削除を行うクラス。
DataStoreCleanServlet が Servlet
TaskQueue を使って Entity が全てなくなるまで削除を繰り返す。
1回のタスクで100件削除する。まだ Entity が残っている場合は TaskQueue で削除処理を継続する。


動作確認した環境:
 Eclipse3.5 + jre1.5.0_16 + appengine-java-sdk-1.3.0


Production 環境で試したところ、Property が本のタイトルのみという Kind で100件削除するのは1秒ぐらいだった。

DataStoreCleaner.java


import java.util.ArrayList;
import java.util.List;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.FetchOptions.Builder;

/**
* 指定された Kind の Entity を削除します。
*/
public class DataStoreCleaner {
private static final DatastoreService _dstore;
private final String _kind;

static {
_dstore = DatastoreServiceFactory.getDatastoreService();
}

public DataStoreCleaner(String kind) {
_kind = kind;
}

/**
* 指定された件数の Entity を削除します。
* @param cnt 削除する件数
* @return 実際に削除した件数
* @throws IllegalStateException
*/
public int clean(int cnt) {
PreparedQuery pq = _dstore.prepare(new Query(_kind));
FetchOptions options = Builder.withLimit(cnt);
List entities = pq.asList(options);
List keys = toKeyList(entities);
_dstore.delete(keys);
return keys.size();
}

/**
* Entity がまだ残っている場合は true を返します。
* @throws IllegalStateException
*/
public boolean hasLeft() {
PreparedQuery pq = _dstore.prepare(new Query(_kind));
return pq.countEntities() > 0;
}

private List toKeyList(List entities) {
List ret = new ArrayList(entities.size());
for (Entity entity : entities) {
ret.add(entity.getKey());
}
return ret;
}
}

DataStoreCleanServlet.java


import java.io.IOException;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import DataStoreCleaner;
import com.google.appengine.api.labs.taskqueue.Queue;
import com.google.appengine.api.labs.taskqueue.QueueFactory;
import com.google.appengine.api.labs.taskqueue.TaskOptions;

public class DataStoreCleanServlet extends HttpServlet {

private String _queue;

@Override
public void init(ServletConfig config) throws ServletException {
_queue = config.getInitParameter("queue");
if (_queue == null) {
_queue = Queue.DEFAULT_QUEUE;
}
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {

String[] kinds = req.getParameterValues("kind");
if (kinds == null) {
return;
}

int cnt = getDeleteCount(req);
for (String kind : kinds) {
clean(req.getRequestURI(), kind, cnt);
}
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {

String uri = req.getRequestURI();
String kind = req.getParameter("kind");
String cnt = req.getParameter("cnt");
clean(uri, kind, Integer.parseInt(cnt));
}

private void clean(String uri, String kind, int cnt) {
boolean hasLeft = cleanStore(kind, cnt);
if (hasLeft) {
enqueueTask(uri, kind, cnt);
}
}

private int getDeleteCount(HttpServletRequest req) {
String cnt = req.getParameter("cnt");
if (cnt == null || cnt.length() == 0) {
return 500;
}
return Integer.valueOf(cnt).intValue();
}

private boolean cleanStore(String kind, int cnt) {
DataStoreCleaner cleaner = new DataStoreCleaner(kind);
int nDelete = cleaner.clean(cnt);
return cleaner.hasLeft();
}

private void enqueueTask(String url, String kind, int cnt) {
TaskOptions ops = createOptions(url, kind, cnt);
Queue q = QueueFactory.getQueue(_queue);
q.add(ops);
}

private TaskOptions createOptions(String url, String kind, int cnt) {
TaskOptions ops = TaskOptions.Builder.url(url);
ops = ops.param("kind", kind);
ops = ops.param("cnt", "" + cnt);
return ops;
}
}

URL

servlet-name を "clean" にした場合
http://anywhere.appspot.com/clean?kind=kind名

kind は複数指定も可
http://anywhere.appspot.com/clean?kind=kind1&kind=kind2

web.xml

DataStoreCleanServlet は起動時の引数で削除処理に使う TaskQueue の名前を指定できる。
省略した場合はデフォルトのキューを使用する。

【サンプル】



DataStoreCleaner
DataStoreCleanServlet


queue
clean


DataStoreCleaner
/clean