Skip to content

Commit

Permalink
perf: fix some N+1 issues in program related serializers (#4283)
Browse files Browse the repository at this point in the history
* perf: fix some N+1 issues in program related serializers
* perf: program retrieve serializer n+1 fixups
* test: update query counts
  • Loading branch information
DawoudSheraz committed Mar 7, 2024
1 parent 09c3269 commit f10beb9
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 22 deletions.
50 changes: 30 additions & 20 deletions course_discovery/apps/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,7 @@ def prefetch_queryset(cls, queryset=None):
return queryset.select_related(
'course__level_type',
'course__video__image',
'course__additional_metadata',
'language',
'video',
'expected_program_type'
Expand Down Expand Up @@ -1183,8 +1184,12 @@ def prefetch_queryset(cls, queryset=None, course_runs=None):
# queryset passed in happens to be empty.
queryset = queryset if queryset is not None else Course.objects.all()

return queryset.select_related('partner', 'type', 'canonical_course_run').prefetch_related(
return queryset.select_related(
'partner', 'type', 'canonical_course_run'
).prefetch_related(
'authoring_organizations',
'canonical_course_run__seats__type',
'canonical_course_run__seats__currency',
Prefetch('entitlements', queryset=CourseEntitlementSerializer.prefetch_queryset()),
cls.prefetch_course_runs(MinimalCourseRunSerializer, course_runs),
)
Expand Down Expand Up @@ -1998,7 +2003,6 @@ def prefetch_queryset(cls, partner, queryset=None):
# `ProgramType` on `Program`.
'type__applicable_seat_types',
'type__translations',
'authoring_organizations',
'degree__costs',
'degree__deadlines',
'curricula',
Expand All @@ -2010,6 +2014,7 @@ def prefetch_queryset(cls, partner, queryset=None):
'degree__quick_facts',
'labels',
Prefetch('courses', queryset=MinimalProgramCourseSerializer.prefetch_queryset()),
Prefetch('authoring_organizations', queryset=OrganizationSerializer.prefetch_queryset(partner)),
)

class Meta:
Expand Down Expand Up @@ -2154,21 +2159,10 @@ class MinimalExtendedProgramSerializer(MinimalProgramSerializer):

@classmethod
def prefetch_queryset(cls, partner, queryset=None):
# Explicitly check if the queryset is None before selecting related
queryset = queryset if queryset is not None else Program.objects.filter(partner=partner)
queryset = super().prefetch_queryset(partner=partner, queryset=queryset)

return queryset.select_related('type', 'partner').prefetch_related(
'excluded_course_runs',
return queryset.prefetch_related(
'expected_learning_items',
# `type` is serialized by a third-party serializer. Providing this field name allows us to
# prefetch `applicable_seat_types`, a m2m on `ProgramType`, through `type`, a foreign key to
# `ProgramType` on `Program`.
'type__applicable_seat_types',
'type__translations',
'authoring_organizations',
'degree',
'curricula',
Prefetch('courses', queryset=MinimalProgramCourseSerializer.prefetch_queryset()),
)

class Meta(MinimalProgramSerializer.Meta):
Expand Down Expand Up @@ -2225,17 +2219,33 @@ def prefetch_queryset(cls, partner, queryset=None):
'geolocation',
'in_year_value',
'product_source',
'location_restriction'
'location_restriction',
'degree',
'language_override',
'level_type_override',
'primary_subject_override',
'degree__additional_metadata'
).prefetch_related(
'excluded_course_runs',
'expected_learning_items',
'faq',
'job_outlook_items',
'instructor_ordering',
# `type` is serialized by a third-party serializer. Providing this field name allows us to
# prefetch `applicable_seat_types`, a m2m on `ProgramType`, through `type`, a foreign key to
# `ProgramType` on `Program`.
'type__applicable_seat_types',
'type__translations',
'degree__costs',
'degree__deadlines',
'curricula',
'subscription__prices__currency',
'primary_subject_override__translations',
'level_type_override__translations',
'degree__specializations',
'degree__rankings',
'degree__quick_facts',
'labels',
'expected_learning_items',
'faq',
'job_outlook_items',
'instructor_ordering',
# We need the full Course prefetch here to get CourseRun information that methods on the Program
# model iterate across (e.g. language). These fields aren't prefetched by the minimal Course serializer.
Prefetch('courses', queryset=CourseSerializer.prefetch_queryset(partner=partner)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def test_retrieve_basic_curriculum(self, django_assert_num_queries):
program = self.create_program(courses=[])
self.create_curriculum(program)

with django_assert_num_queries(FuzzyInt(56, 3)):
with django_assert_num_queries(FuzzyInt(52, 3)):
response = self.assert_retrieve_success(program)
assert response.data == self.serialize_program(program)

Expand Down Expand Up @@ -188,7 +188,7 @@ def test_retrieve_without_course_runs(self, django_assert_num_queries):
""" Verify the endpoint returns data for a program even if the program's courses have no course runs. """
course = CourseFactory(partner=self.partner)
program = ProgramFactory(courses=[course], partner=self.partner)
with django_assert_num_queries(FuzzyInt(43, 2)):
with django_assert_num_queries(FuzzyInt(40, 2)):
response = self.assert_retrieve_success(program)
assert response.data == self.serialize_program(program)

Expand Down

0 comments on commit f10beb9

Please sign in to comment.