I just recently landed some patches on toocool that implements and interesting pattern that is seen more and more these days. I call it: Persistent caching with fire-and-forget updates
Basically, the implementation is this: You issue a request that requires information about a Twitter user: E.g. http://toocoolfor.me/following/chucknorris/vs/peterbe
The app looks into its MongoDB for information about the tweeter and if it can't find this user it goes onto the Twitter REST API and looks it up and saves the result in MongoDB.
The next time the same information is requested, and the data is available in the MongoDB it instead checks if the modify_date
or more than an hour and if so, it sends a job to the message queue (Celery with Redis in my case) to perform an update on this tweeter.
You can basically see the code here but just to reiterate and abbreviate, it looks like this:
tweeter = self.db.Tweeter.find_one({'username': username})
if not tweeter:
result = yield tornado.gen.Task(...)
if result:
tweeter = self.save_tweeter_user(result)
else:
# deal with the error!
elif age(tweeter['modify_date']) > 3600:
tasks.refresh_user_info.delay(username, ...)
# render the template!
What the client gets, i.e. the user using the site, is it that apart from the very first time that URL is request is instant results but data is being maintained and refreshed.
This pattern works great for data that doesn't have to be up-to-date to the second but that still needs a way to cache invalidate and re-fetch. This works because my limit of 1 hour is quite arbitrary. An alternative implementation would be something like this:
tweeter = self.db.Tweeter.find_one({'username': username})
if not tweeter or (tweeter and age(tweeter) > 3600 * 24 * 7):
# re-fetch from Twitter REST API
elif age(tweeter) > 3600:
# fire-and-forget update
That way you don't suffer from persistently cached data that is too old.
Comments
What you describe is really a specific implementation of Memoization - http://en.wikipedia.org/wiki/Memoization. You're right, it's a very powerful design.
I'm just testing something here.
Can you explain the use of "yield" in that code, or link to something that explains it? This doesn't look like a generator. I don't think I've seen the value of a yield expression assigned to a local before.
It's a Tornado thing. It's awesome because it's an alternative to using callbacks basically. Unlike callbacks which need a new function/method with new parameters and scope, this method just carries on on the next line like any procedural program. I was turned on by Tornado before this was added and now it just makes it even sexier.