-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Profile page performance issue Fix #5472
Profile page performance issue Fix #5472
Conversation
This seems like a elegant fix for the optimization. I did not know about solving the perfomance issue using the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized the project_list_detailed.html
template is also used to display the pages for tags (eg. /projects/tags/tag-slug/
). As-is, since on that page there is no project.good_build
attribute, it shows all the projects with no builds.
I see two possibilities going forward here:
- We also need to change the tags view to have a similar annotation
- We can make the annotation write to a "private" variable (like
_good_build
) and then theproject.has_good_build
method can check if that variable exists and use it if it does and fallback to making an extra query.
This is becoming less of a good first issue and if you need help with it I'm happy to take it back and build on top of what you've done already.
@davidfischer I would like to give it a try if its okay with you. |
@davidfischer Updated The PR please have a look and let me know if the implementation is what you wanted. and what do you think I should do for the other annotation |
I like the solution for I think we can use a similar approach for |
@davidfischer Great! Actually I Didn't use |
This was a little trickier than I thought but you can control the prefetching with a from readthedocs.projects.models import Project
from readthedocs.builds.models import Build
from django.db.models import Prefetch
latest_build = Prefetch('builds', queryset=Build.objects.filter(state='finished', type='html'), to_attr='_latest_build')
# Executes 2 queries
projects = list(Project.objects.all().prefetch_related(latest_build))
# This executes 0 queries
for project in projects:
print(project.slug)
if project._latest_build:
# It is a little awkward that "_latest_build" is a list... and it could be empty
print(' - {}'.format(project._latest_build[0].date)) The above code isn't ideal but it should give you some tips on working this out. |
@davidfischer The queryset Do you think its good enough for fetching the latest build data that we need or fetch the whole object? |
Hmmmm, you're right. Maybe there's a way to limit it to just 1 result. |
A lot of things get simpler if we can fetch the whole build object. However, we don't need to fetch 100s of build objects if possible. |
Thanks for looking at this @safwanrahman. I think this will do it if you wouldn't mind taking another look: from django.db.models import OuterRef, Subquery, Prefetch
from readthedocs.projects.models import Project
from readthedocs.builds.models import Build
subquery = Subquery(Build.objects.filter(state='finished', type='html', project=OuterRef('project_id')).values_list('id', flat=True)[:1])
latest_build = Prefetch('builds', Build.objects.filter(pk__in=subquery), to_attr='_latest_build')
projects = list(Project.objects.all().prefetch_related(latest_build)) |
@davidfischer Thanks a lot for your help. I tried out your code in the shell and also in the application but I can't seem to reduce the number of Database Queries. When ever >>> from django.db import connection
>>> from django.db.models import OuterRef, Subquery, Prefetch
>>> from readthedocs.projects.models import Project
>>> from readthedocs.builds.models import Build
>>>
>>> subquery = Subquery(Build.objects.filter(state='finished', type='html', project=OuterRef('project_id')).values_list('id', flat=True)[:1])
>>> latest_build = Prefetch('builds', Build.objects.filter(pk__in=subquery), to_attr='_latest_build')
>>> # Executes 2 queries
>>> projects = list(Project.objects.all().prefetch_related(latest_build))
>>>
>>> # Executes 8 queries as 8 projects had builds.
>>> for project in projects:
... if project._latest_build:
... project._latest_build
...
[<Build: Build Cue for AnonymousUser admin (11)>]
[<Build: Build Django Test Utils for AnonymousUser admin (13)>]
[<Build: Build Fabric for AnonymousUser admin (15)>]
[<Build: Build Kong for AnonymousUser admin (21)>]
[<Build: Build Pinax for AnonymousUser admin (19)>]
[<Build: Build Read The Docs for AnonymousUser admin (9)>]
[<Build: Build South for AnonymousUser admin (20)>]
[<Build: Build Tastypie for AnonymousUser admin (16)>]
>>> len(connection.queries)
10 |
Those queries are coming from querying the |
@davidfischer I have Updated the PR according to your recommendations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This worked well and performed much better locally for me!
Great |
I'm interested in getting this merged soon if possible. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't test it locally, but changes look good to me.
closes #5468