Cache template when posting a notification

This commit proposes two types of caching to speed up the post
notification processing time.

Since every notification needs a template, if we can avoid going to the
db to get the template then this should be faster.

The first cache is an LRU cache, which means that, for a given
`template_id`, `service_id` and `version` the app will go to memory
rather than the database to get the template. This should be much
faster, and we should be guaranteed that a combination of those three
parameters should always return the same result.

But, we don’t know the template version at the time of making the call.
So this commit also adds another layer of caching to find the most
recent version number of a template. Since this can be changed by other
instances we need to cache it externally, so this cache uses Redis.
Redis will be a lot slower than going to memory, but hopefully quicker
than going to the database. By only caching the minimal amount of data
possible in Redis (just the version as a number) we’re hopefully
minimising the performance hit from going to an external service.
This commit is contained in:
Chris Hill-Scott
2020-03-30 12:40:56 +01:00
parent eaf38dd6a7
commit 9400e48165
2 changed files with 38 additions and 5 deletions

View File

@@ -1,10 +1,12 @@
from datetime import datetime
from functools import lru_cache
import uuid
from flask import current_app
from sqlalchemy import asc, desc
from sqlalchemy import asc, desc, func
from sqlalchemy.orm.exc import NoResultFound
from app import db
from app import db, redis_store
from app.models import (
LETTER_TYPE,
SECOND_CLASS,
@@ -20,6 +22,9 @@ from app.dao.dao_utils import (
from app.dao.users_dao import get_user_by_id
SEVEN_DAYS_IN_SECONDS = 604_800
@transactional
@version_class(
VersionOptions(Template, history_class=TemplateHistory)
@@ -49,7 +54,7 @@ def dao_create_template(template):
def dao_update_template(template):
if template.archived:
template.folder = None
redis_store.delete(f'template-{template.id}-version')
db.session.add(template)
@@ -92,6 +97,33 @@ def dao_redact_template(template, user_id):
db.session.add(template.template_redacted)
def dao_get_template_version(template_id):
cache_key = f'template-{template_id}-version'
version = redis_store.get(cache_key)
if not version:
version = db.session.query(
func.max(TemplateHistory.version)
).filter_by(
id=template_id,
hidden=False,
).scalar()
if not version:
raise NoResultFound
redis_store.set(cache_key, version, ex=SEVEN_DAYS_IN_SECONDS)
return version
@lru_cache(maxsize=1024)
def dao_get_template_by_id_and_service_id_and_version(*, template_id, service_id, version):
return dao_get_template_by_id_and_service_id(template_id, service_id, version)
def dao_get_template_by_id_and_service_id(template_id, service_id, version=None):
if version is not None:
return TemplateHistory.query.filter_by(

View File

@@ -140,9 +140,10 @@ def check_notification_content_is_not_empty(template_with_content):
def validate_template(template_id, personalisation, service, notification_type):
try:
template = templates_dao.dao_get_template_by_id_and_service_id(
template = templates_dao.dao_get_template_by_id_and_service_id_and_version(
template_id=template_id,
service_id=service.id
service_id=service.id,
version=templates_dao.dao_get_template_version(template_id),
)
except NoResultFound:
message = 'Template not found'