Created
September 5, 2016 07:46
-
-
Save rafaelcapucho/571a5ce63bc0fb0d4c43b445324ae58c to your computer and use it in GitHub Desktop.
Speeding up your MongoDB queries with Tornado Mixin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
from __future__ import print_function | |
from __future__ import unicode_literals | |
from hashlib import md5 | |
import datetime | |
import tornado.web | |
from tornado import gen | |
from tornado.options import define, options | |
from handlers import DBMixin | |
define("cache", default="True", type=bool, help="Enable/Disable the internal cache") | |
_cache = {} | |
class DBMixin(object): | |
@property | |
def db(self): return self.application.db | |
class CacheHandler(tornado.web.RequestHandler, DBMixin): | |
def __init__(self, *args, **kwargs): | |
super(CacheHandler, self).__init__(*args, **kwargs) | |
self.FIND_ONE = 1 | |
self.FIND = 2 | |
self.AGGREGATE = 3 | |
@gen.coroutine | |
def cache(self, type, col, *args, **kwargs): | |
"""Samples: | |
products = yield self.cache(self.FIND, 'products', {}, {'_id': 1}, to_list=50, memory=True) | |
brand = yield self.cache(self.FIND_ONE, 'brands', find_one={'name': 'Acquaflora'}, memory=True) | |
pipeline = [ | |
{'$match': | |
{'cats_ids': {'$all': [ ObjectId(category['_id']) ]}} | |
}, | |
{'$project': | |
{'products': 1, 'min_price': 1, 'max_price': 1, 'n_products': 1, '_id': 1} | |
}, | |
{'$sort': sort }, # Sort first to evaluete all prices | |
{'$skip': self.calculate_page_skip(limit=limit)}, | |
{'$limit': limit} | |
] | |
groups = yield self.cache(self.AGGREGATE, 'products_groups', memory=False, pipeline=pipeline) | |
""" | |
memory = kwargs.pop('memory', True) # Local Memory | |
timeout = kwargs.pop('timeout', 60) | |
sort = kwargs.pop('sort', None) | |
signature = str(type)+col+str(args)+str(kwargs) | |
key = md5(signature.encode('utf-8')).hexdigest() | |
@gen.coroutine | |
def get_key(key): | |
if not options.cache: | |
raise gen.Return(False) | |
if memory: | |
if _cache.get(key, False): | |
raise gen.Return(_cache[key]) | |
else: | |
raise gen.Return(False) | |
else: | |
data = yield self.db['_cache'].find_one({'key': key}) | |
raise gen.Return(data) | |
@gen.coroutine | |
def set_key(key, value): | |
delta = datetime.datetime.now() + datetime.timedelta(seconds=timeout) | |
if memory: | |
_cache[key] = { | |
'd': value, | |
't': delta | |
} | |
else: | |
yield self.db['_cache'].insert({ | |
'key': key, | |
'd': value, | |
't': delta | |
}) | |
@gen.coroutine | |
def del_key(key): | |
if memory: | |
if _cache.get(key, False): del _cache[key] | |
else: | |
yield self.db['_cache'].remove({'key': key}) | |
_key = yield get_key(key) | |
if _key: | |
# If the time in the future is still bigger than now | |
if _key['t'] >= datetime.datetime.now(): | |
raise gen.Return(_key['d']) | |
else: # Invalid | |
yield del_key(key) | |
# otherwise (key not exist) | |
if type == self.FIND_ONE: | |
data = yield self.db[col].find_one(*args, **kwargs) | |
elif type == self.FIND: | |
if sort: | |
cursor = self.db[col].find(*args, **kwargs).sort(sort) | |
else: | |
cursor = self.db[col].find(*args, **kwargs) | |
data = yield cursor.to_list(kwargs.pop('to_list', None)) | |
elif type == self.AGGREGATE: | |
cursor = self.db[col].aggregate( | |
kwargs.pop('pipeline', []), | |
*args, | |
cursor = kwargs.pop('cursor', {}), | |
**kwargs | |
) | |
data = yield cursor.to_list(kwargs.pop('to_list', None)) | |
if options.cache: | |
# Persist the key | |
yield set_key(key, data) | |
raise gen.Return(data) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment