server/search: cache results till non-GET request

250 ms per page --> 30 ms per page (save for the first render). I'd say
it's pretty good
This commit is contained in:
rr- 2016-05-31 11:36:22 +02:00
parent 349f32ccf2
commit 446f4d6611
5 changed files with 72 additions and 1 deletions

View File

@ -80,6 +80,7 @@ def create_app():
request_type=api.Request, request_type=api.Request,
middleware=[ middleware=[
middleware.RequireJson(), middleware.RequireJson(),
middleware.CachePurger(),
middleware.ContextAdapter(), middleware.ContextAdapter(),
middleware.DbSession(), middleware.DbSession(),
middleware.Authenticator(), middleware.Authenticator(),

View File

@ -0,0 +1,56 @@
from datetime import datetime
class LruCacheItem(object):
def __init__(self, key, value):
self.key = key
self.value = value
self.timestamp = datetime.now()
class LruCache(object):
def __init__(self, length, delta=None):
self.length = length
self.delta = delta
self.hash = {}
self.item_list = []
def insert_item(self, item):
if item.key in self.hash:
item_index = next(i for i, v in enumerate(self.item_list) if v.key == item.key)
self.item_list[:] = self.item_list[:item_index] + self.item_list[item_index+1:]
self.item_list.insert(0, item)
else:
if len(self.item_list) > self.length:
self.remove_item(self.item_list[-1])
self.hash[item.key] = item
self.item_list.insert(0, item)
def remove_all(self):
self.hash = {}
self.item_list = []
def remove_item(self, item):
del self.hash[item.key]
del self.item_list[self.item_list.index(item)]
def validate_item(self):
def _outdated_items():
now = datetime.now()
for item in self.item_list:
time_delta = now - item.timestamp
if time_delta.seconds > self.delta:
yield item
map(lambda x: self.remove_item(x), _outdated_items())
_cache = LruCache(length=100)
def purge():
_cache.remove_all()
def has(key):
return key in _cache.hash
def get(key):
return _cache.hash[key].value
def put(key, value):
_cache.insert_item(LruCacheItem(key, value))

View File

@ -4,3 +4,4 @@ from szurubooru.middleware.authenticator import Authenticator
from szurubooru.middleware.context_adapter import ContextAdapter from szurubooru.middleware.context_adapter import ContextAdapter
from szurubooru.middleware.require_json import RequireJson from szurubooru.middleware.require_json import RequireJson
from szurubooru.middleware.db_session import DbSession from szurubooru.middleware.db_session import DbSession
from szurubooru.middleware.cache_purger import CachePurger

View File

@ -0,0 +1,7 @@
import falcon
from szurubooru.func import cache
class CachePurger(object):
def process_request(self, request, _response):
if request.method != 'GET':
cache.purge()

View File

@ -1,6 +1,7 @@
import re import re
import sqlalchemy import sqlalchemy
from szurubooru import db, errors from szurubooru import db, errors
from szurubooru.func import cache
from szurubooru.search import criteria from szurubooru.search import criteria
class SearchExecutor(object): class SearchExecutor(object):
@ -13,6 +14,9 @@ class SearchExecutor(object):
self.config = search_config self.config = search_config
def execute(self, query_text, page, page_size): def execute(self, query_text, page, page_size):
key = (id(self.config), query_text, page, page_size)
if cache.has(key):
return cache.get(key)
''' '''
Parse input and return tuple containing total record count and filtered Parse input and return tuple containing total record count and filtered
entities. entities.
@ -33,7 +37,9 @@ class SearchExecutor(object):
.with_only_columns([sqlalchemy.func.count()]) \ .with_only_columns([sqlalchemy.func.count()]) \
.order_by(None) .order_by(None)
count = db.session.execute(count_statement).scalar() count = db.session.execute(count_statement).scalar()
return (count, entities) ret = (count, entities)
cache.put(key, ret)
return ret
def execute_and_serialize(self, ctx, serializer): def execute_and_serialize(self, ctx, serializer):
query = ctx.get_param_as_string('query') query = ctx.get_param_as_string('query')