Optimize object creation by getting fewer empty relationships (#12508)

This optimizes the ActivityStreamSerializer by only getting many-to-many
  relationships that are speculatively non-empty
  based on information we have in other fields

We run this every time we create an object as an on_commit action
  so it is expected this will have a major impact on response times for launching jobs
This commit is contained in:
Alan Rominger
2022-07-19 14:27:51 -04:00
committed by GitHub
parent 33e445f4f6
commit 2d310dc4e5
3 changed files with 12 additions and 8 deletions

View File

@@ -29,7 +29,6 @@ from django.utils.translation import gettext_lazy as _
from django.utils.encoding import force_str from django.utils.encoding import force_str
from django.utils.text import capfirst from django.utils.text import capfirst
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.functional import cached_property
# Django REST Framework # Django REST Framework
from rest_framework.exceptions import ValidationError, PermissionDenied from rest_framework.exceptions import ValidationError, PermissionDenied
@@ -5008,8 +5007,7 @@ class ActivityStreamSerializer(BaseSerializer):
object_association = serializers.SerializerMethodField(help_text=_("When present, shows the field name of the role or relationship that changed.")) object_association = serializers.SerializerMethodField(help_text=_("When present, shows the field name of the role or relationship that changed."))
object_type = serializers.SerializerMethodField(help_text=_("When present, shows the model on which the role or relationship was defined.")) object_type = serializers.SerializerMethodField(help_text=_("When present, shows the model on which the role or relationship was defined."))
@cached_property def _local_summarizable_fk_fields(self, obj):
def _local_summarizable_fk_fields(self):
summary_dict = copy.copy(SUMMARIZABLE_FK_FIELDS) summary_dict = copy.copy(SUMMARIZABLE_FK_FIELDS)
# Special requests # Special requests
summary_dict['group'] = summary_dict['group'] + ('inventory_id',) summary_dict['group'] = summary_dict['group'] + ('inventory_id',)
@@ -5029,7 +5027,13 @@ class ActivityStreamSerializer(BaseSerializer):
('workflow_approval', ('id', 'name', 'unified_job_id')), ('workflow_approval', ('id', 'name', 'unified_job_id')),
('instance', ('id', 'hostname')), ('instance', ('id', 'hostname')),
] ]
return field_list # Optimization - do not attempt to summarize all fields, pair down to only relations that exist
if not obj:
return field_list
existing_association_types = [obj.object1, obj.object2]
if 'user' in existing_association_types:
existing_association_types.append('role')
return [entry for entry in field_list if entry[0] in existing_association_types]
class Meta: class Meta:
model = ActivityStream model = ActivityStream
@@ -5113,7 +5117,7 @@ class ActivityStreamSerializer(BaseSerializer):
data = {} data = {}
if obj.actor is not None: if obj.actor is not None:
data['actor'] = self.reverse('api:user_detail', kwargs={'pk': obj.actor.pk}) data['actor'] = self.reverse('api:user_detail', kwargs={'pk': obj.actor.pk})
for fk, __ in self._local_summarizable_fk_fields: for fk, __ in self._local_summarizable_fk_fields(obj):
if not hasattr(obj, fk): if not hasattr(obj, fk):
continue continue
m2m_list = self._get_related_objects(obj, fk) m2m_list = self._get_related_objects(obj, fk)
@@ -5170,7 +5174,7 @@ class ActivityStreamSerializer(BaseSerializer):
def get_summary_fields(self, obj): def get_summary_fields(self, obj):
summary_fields = OrderedDict() summary_fields = OrderedDict()
for fk, related_fields in self._local_summarizable_fk_fields: for fk, related_fields in self._local_summarizable_fk_fields(obj):
try: try:
if not hasattr(obj, fk): if not hasattr(obj, fk):
continue continue

View File

@@ -409,7 +409,7 @@ def emit_activity_stream_change(instance):
from awx.api.serializers import ActivityStreamSerializer from awx.api.serializers import ActivityStreamSerializer
actor = None actor = None
if instance.actor: if instance.actor_id:
actor = instance.actor.username actor = instance.actor.username
summary_fields = ActivityStreamSerializer(instance).get_summary_fields(instance) summary_fields = ActivityStreamSerializer(instance).get_summary_fields(instance)
analytics_logger.info( analytics_logger.info(

View File

@@ -20,7 +20,7 @@ def test_activity_stream_related():
""" """
serializer_related = set( serializer_related = set(
ActivityStream._meta.get_field(field_name).related_model ActivityStream._meta.get_field(field_name).related_model
for field_name, stuff in ActivityStreamSerializer()._local_summarizable_fk_fields for field_name, stuff in ActivityStreamSerializer()._local_summarizable_fk_fields(None)
if hasattr(ActivityStream, field_name) if hasattr(ActivityStream, field_name)
) )