Since you mentioned ofy, here are some useful tips which helped us to solve similar issues:
1) if you are making lot's of queries and don't need to cache, best is to disable cache for query, with :
ofy().cache(false).
2) use iterator to process big data sets in chunks.
Here is sample code, which works very robustly for us. It will process chunks for 8 minutes and will pass the rest to be run on next task. It uses deferred tasks.
chunks defined 'limit' variable, entity type by 'modelCalss', cursor by 'startCursor' which is null to start with. There are some other variables you can figure out yourself.
Hope this helps.
try {
do {
Query<T> query = ofy().cache(false).load()
.type(this.modelClass)
.limit(this.limit)
.chunkAll();
if (this.startCursor != null) {
query = query.startAt(this.startCursor);
}
QueryResultIterator<T> iterator = null;
try {
iterator = query.iterator();
if (!iterator.hasNext()) {
stillNotFinished = false;
}
while (iterator.hasNext()) {
T entity = iterator.next();
process(entity);
this.countSoFar++;
}
} catch (Exception dxe) {
log.error("Exception error should stop from {}", this.countSoFar, dxe);
isException = true;
} finally {
ofy().flush();
if (iterator != null) {
this.startCursor = iterator.getCursor();
ofy().clear();
}
}
log.info("Processed so far {} items. ", this.countSoFar);
} while (stillNotFinished && sw.elapsed(TimeUnit.MINUTES) < 8);
} finally {
sw.stop();
if (stillNotFinished) {
if (isException || !this.spawnNewProcessOnDisruption) {
log.warn("Has Stopped on item {}", this.countSoFar);
} else {
log.info("Rerunning from {}", this.countSoFar);
TaskController.addToTaskQueue(this);