Let’s say you have a bunch of entities stored in Google’s AppEngine Datastore, and you want to run a query over all of them, but you don’t really mind if a few are missing. For example, computing summary statistics over log files.
class LogRecord(ndb.Model): message = ndb.StringProperty() query = LogRecord.query() for record in query: update_statistics(record.message)
This code will work fine for small batches of entities, but when you start computing over hundreds of thousands, appengine will eventually raise an exception for some reason or another. And then you have to start over.
What we need is a resilient query. One that will iterate over the query results and simply discard any entities that throw exceptions. We want as much data as we can get, but we don’t want to stop iterating if we hit a few bumps along the way. The show must go on!
def resilient_query(query, max_failures=10): failure_count = 0 query_iter = iter(query) while True: try: if failure_count >= max_failures: raise StopIteration("Failure threshold crossed.") next_result = query_iter.next() yield next_result except StopIteration: raise except Exception, e: failure_count += 1
And now we can just need to…
query = resilient_query(LogRecord.query()) for record in query: update_statistics(record.message)
One downside of this is that it will omit entities that threw an exception, even if that exception was transient and would have disappeared with a retry.
It’s also interesting to note that the resilient_query function will work for any iterator that may throw exceptions in the middle, as long as that iterator knows where to pick up where it left off. You can see that and some more details in this gist.