28
loading...
This website collects cookies to deliver better user experience
00:02
It’s come to my attention that in our community app, listings of past meetings (that were recently added by a new team member) feel notably slower than renderings of future meetings, and a quick glance at database utilization shows a rapid increase of queries since the new feature has been introduced.
00:22
I’ll use a custom test that will amplify the performance difference between the two cases, simulating fetching and rendering of 1000 meetings.
To observe the behavior and measure the performance differences, I’m going to use the free and open source framework AppMap.
00:39
Now let me run the test and record my code behavior with AppMap:
This is interesting, because the future meetings took .16s while past meetings took a lot more time, .42s.
00:54
Let’s drill down and compare the code flows of the two requests.
This is a Django app and I can see that the renderer took .14s for future and .40s for past meetings. I can also instantly see that the first code flow has four children vs. 1505 of the second one.
01:15
Let’s look at the first flow, first.
01:33
Let’s drill down to the second flow, the request to view all past meetings.
The second sequence starts with fetching all past meetings. What follows is very different though: The ORM runs three queries for each past meeting in the database -- that’s 1500 trips to the database total for 500 past meetings created by the test. Even on my dev environment with no other database load, the performance difference between the two SQL command patterns is pretty remarkable.
02:02
Let’s see what happens in the code that sets up the data entities. A quick glance in the view implementation reveals that the past meetings view lacks the select_related
and prefetch_related
method calls, causing the observed N+1 problem.
class FutureMeetingsAll(ListView):
template_name = "meetings/future_meetings_all.html"
queryset = Meeting.objects.filter(
when__gt=datetime.datetime.now() - datetime.timedelta(hours=3)
).order_by("when").select_related("where").prefetch_related("topics", "meeting_sponsors")
class PastMeetingsAll(ListView):
template_name = "meetings/past_meetings_all.html"
queryset = Meeting.objects.filter(
when__lt=datetime.datetime.now() - datetime.timedelta(hours=3)
)
Note that despite the obvious code issue, the tests were passing previously -- functionally, the existing code works -- but it’s causing extra costs and risk, and puts an unnecessary strain on the database.
02:34
Let’s fix and run the test again.
class PastMeetingsAll(ListView):
template_name = "meetings/past_meetings_all.html"
queryset = Meeting.objects.filter(
when__lt=datetime.datetime.now() - datetime.timedelta(hours=3)
).order_by("when").select_related("where").prefetch_related("topics", "meeting_sponsors")