From ae44ee56521bae5a7b38da5594733555205b3757 Mon Sep 17 00:00:00 2001 From: Ildar Iskhakov Date: Sat, 28 Jan 2023 12:50:41 +0800 Subject: [PATCH] Cache render_for_web field for alertgroups list serializer (#1236) # What this PR does This PR caches the field `render_for_web` with lifetime 1 day and cache becomes invalid if it was created before * last alert received * template changed ## Which issue(s) this PR fixes ## Checklist - [ ] Tests updated - [ ] Documentation added - [ ] `CHANGELOG.md` updated --- ...eceivechannel_web_templates_modified_at.py | 18 +++++++++++ .../alerts/models/alert_receive_channel.py | 1 + engine/apps/api/serializers/alert_group.py | 30 +++++++++++++++++-- .../api/serializers/alert_receive_channel.py | 4 +++ 4 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 engine/apps/alerts/migrations/0009_alertreceivechannel_web_templates_modified_at.py diff --git a/engine/apps/alerts/migrations/0009_alertreceivechannel_web_templates_modified_at.py b/engine/apps/alerts/migrations/0009_alertreceivechannel_web_templates_modified_at.py new file mode 100644 index 00000000..690f9e4b --- /dev/null +++ b/engine/apps/alerts/migrations/0009_alertreceivechannel_web_templates_modified_at.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.16 on 2023-01-27 07:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('alerts', '0008_alter_alertgrouplogrecord_type'), + ] + + operations = [ + migrations.AddField( + model_name='alertreceivechannel', + name='web_templates_modified_at', + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/engine/apps/alerts/models/alert_receive_channel.py b/engine/apps/alerts/models/alert_receive_channel.py index 076d9469..edafbf34 100644 --- a/engine/apps/alerts/models/alert_receive_channel.py +++ b/engine/apps/alerts/models/alert_receive_channel.py @@ -160,6 +160,7 @@ class AlertReceiveChannel(IntegrationOptionsMixin, MaintainableObject): web_title_template = models.TextField(null=True, default=None) web_message_template = models.TextField(null=True, default=None) web_image_url_template = models.TextField(null=True, default=None) + web_templates_modified_at = models.DateTimeField(blank=True, null=True) # email related fields are deprecated in favour of messaging backend based templates # these templates are stored in the messaging_backends_templates field diff --git a/engine/apps/api/serializers/alert_group.py b/engine/apps/api/serializers/alert_group.py index f25cd39b..5f3c854e 100644 --- a/engine/apps/api/serializers/alert_group.py +++ b/engine/apps/api/serializers/alert_group.py @@ -1,5 +1,7 @@ import logging +from django.core.cache import cache +from django.utils import timezone from rest_framework import serializers from apps.alerts.incident_appearance.renderers.classic_markdown_renderer import AlertGroupClassicMarkdownRenderer @@ -13,6 +15,7 @@ from .alert_receive_channel import FastAlertReceiveChannelSerializer from .user import FastUserSerializer logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) class ShortAlertGroupSerializer(serializers.ModelSerializer): @@ -92,10 +95,33 @@ class AlertGroupListSerializer(EagerLoadingMixin, serializers.ModelSerializer): if not obj.last_alert: return {} - return AlertGroupWebRenderer(obj, obj.last_alert).render() + web_templates_modified_at = obj.channel.web_templates_modified_at + last_alert_created_at = obj.last_alert.created_at + + CACHE_KEY = f"render_for_web_alert_group_{obj.id}" + CACHE_LIFEIME = 60 * 60 * 24 + cached_render_for_web = cache.get(CACHE_KEY, None) + + # use cache only if cache exists + # and cache was created after the last alert created + # and either web templates never modified + # or cache was created after templates were modified + if ( + cached_render_for_web is not None + and cached_render_for_web.get("cache_created_at") > last_alert_created_at + and ( + web_templates_modified_at is None + or cached_render_for_web.get("cache_created_at") > web_templates_modified_at + ) + ): + render_for_web = cached_render_for_web.get("render_for_web") + else: + render_for_web = AlertGroupWebRenderer(obj, obj.last_alert).render() + cache.set(CACHE_KEY, {"cache_created_at": timezone.now(), "render_for_web": render_for_web}, CACHE_LIFEIME) + + return render_for_web def get_render_for_classic_markdown(self, obj): - # alert group has no alerts if not obj.last_alert: return {} diff --git a/engine/apps/api/serializers/alert_receive_channel.py b/engine/apps/api/serializers/alert_receive_channel.py index 0df3d11d..b0e52c50 100644 --- a/engine/apps/api/serializers/alert_receive_channel.py +++ b/engine/apps/api/serializers/alert_receive_channel.py @@ -7,6 +7,7 @@ from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ValidationError as DjangoValidationError from django.core.validators import URLValidator from django.template.loader import render_to_string +from django.utils import timezone from jinja2 import TemplateSyntaxError from rest_framework import serializers from rest_framework.exceptions import ValidationError @@ -379,6 +380,7 @@ class AlertReceiveChannelTemplatesSerializer(EagerLoadingMixin, serializers.Mode self.instance.web_title_template = value.strip() elif default_template is not None and default_template.strip() == value.strip(): self.instance.web_title_template = None + self.instance.web_templates_modified_at = timezone.now() def get_web_message_template(self, obj): default_template = AlertReceiveChannel.INTEGRATION_TO_DEFAULT_WEB_MESSAGE_TEMPLATE[obj.integration] @@ -390,6 +392,7 @@ class AlertReceiveChannelTemplatesSerializer(EagerLoadingMixin, serializers.Mode self.instance.web_message_template = value.strip() elif default_template is not None and default_template.strip() == value.strip(): self.instance.web_message_template = None + self.instance.web_templates_modified_at = timezone.now() def get_web_image_url_template(self, obj): default_template = AlertReceiveChannel.INTEGRATION_TO_DEFAULT_WEB_IMAGE_URL_TEMPLATE[obj.integration] @@ -401,6 +404,7 @@ class AlertReceiveChannelTemplatesSerializer(EagerLoadingMixin, serializers.Mode self.instance.web_image_url_template = value.strip() elif default_template is not None and default_template.strip() == value.strip(): self.instance.web_image_url_template = None + self.instance.web_templates_modified_at = timezone.now() def get_telegram_title_template(self, obj): default_template = AlertReceiveChannel.INTEGRATION_TO_DEFAULT_TELEGRAM_TITLE_TEMPLATE[obj.integration]