From 60cf2dcbd7a7068d0a7300df5acf9e8e7fad3593 Mon Sep 17 00:00:00 2001 From: Jens Gutermuth Date: Mon, 23 Mar 2015 01:58:12 +0100 Subject: [PATCH 1/5] Fix three N+1 queries in pin loading Fetch the submitter with a join and get all the images and tags in one query each. This reduces the total query count for loading 50 pins from 304 to 158. See issue #85. --- pinry/core/api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pinry/core/api.py b/pinry/core/api.py index 56a4b21..334c019 100644 --- a/pinry/core/api.py +++ b/pinry/core/api.py @@ -135,7 +135,8 @@ class PinResource(ModelResource): filtering = { 'submitter': ALL_WITH_RELATIONS } - queryset = Pin.objects.all() + queryset = Pin.objects.all().select_related('submitter'). \ + prefetch_related('image', 'tags') resource_name = 'pin' include_resource_uri = False always_return_data = True From 3bcc6d315258b89e1287626137e081c741eeccb3 Mon Sep 17 00:00:00 2001 From: Jens Gutermuth Date: Mon, 23 Mar 2015 02:22:35 +0100 Subject: [PATCH 2/5] Loanding pins does no N+1 queries anymore This saves another three queries per pin, bringing to total query count down to 8. See issue #85 . --- pinry/core/api.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pinry/core/api.py b/pinry/core/api.py index 334c019..9590f9b 100644 --- a/pinry/core/api.py +++ b/pinry/core/api.py @@ -1,3 +1,4 @@ +from django.core.exceptions import ObjectDoesNotExist from tastypie import fields from tastypie.authorization import DjangoAuthorization from tastypie.constants import ALL, ALL_WITH_RELATIONS @@ -59,7 +60,10 @@ class UserResource(ModelResource): def filter_generator_for(size): def wrapped_func(bundle, **kwargs): - return bundle.obj.get_by_size(size) + for thumbnail in bundle.obj._prefetched_objects_cache['thumbnail']: + if thumbnail.size == size: + return thumbnail + raise DoesNotExist() return wrapped_func @@ -136,7 +140,7 @@ class PinResource(ModelResource): 'submitter': ALL_WITH_RELATIONS } queryset = Pin.objects.all().select_related('submitter'). \ - prefetch_related('image', 'tags') + prefetch_related('image__thumbnail_set', 'tags') resource_name = 'pin' include_resource_uri = False always_return_data = True From e4b876916846befa35e288d1a734bd562702cc48 Mon Sep 17 00:00:00 2001 From: Jens Gutermuth Date: Mon, 23 Mar 2015 02:28:21 +0100 Subject: [PATCH 3/5] DoesNotExist should be ObjectDoesNotExist, oops! --- pinry/core/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pinry/core/api.py b/pinry/core/api.py index 9590f9b..ea7d30d 100644 --- a/pinry/core/api.py +++ b/pinry/core/api.py @@ -63,7 +63,7 @@ def filter_generator_for(size): for thumbnail in bundle.obj._prefetched_objects_cache['thumbnail']: if thumbnail.size == size: return thumbnail - raise DoesNotExist() + raise ObjectDoesNotExist() return wrapped_func From 9d4791b65ad5bbcd01cd2cc21ec838a86896a7b7 Mon Sep 17 00:00:00 2001 From: Jens Gutermuth Date: Mon, 23 Mar 2015 02:30:54 +0100 Subject: [PATCH 4/5] image can be loaded with a join as well (saves another query) --- pinry/core/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pinry/core/api.py b/pinry/core/api.py index ea7d30d..6cfa069 100644 --- a/pinry/core/api.py +++ b/pinry/core/api.py @@ -139,7 +139,7 @@ class PinResource(ModelResource): filtering = { 'submitter': ALL_WITH_RELATIONS } - queryset = Pin.objects.all().select_related('submitter'). \ + queryset = Pin.objects.all().select_related('submitter', 'image'). \ prefetch_related('image__thumbnail_set', 'tags') resource_name = 'pin' include_resource_uri = False From deb04500c5ffd3d8e3e679f92d4d1aed7da4cdb2 Mon Sep 17 00:00:00 2001 From: Jens Gutermuth Date: Mon, 23 Mar 2015 03:30:13 +0100 Subject: [PATCH 5/5] Only use prefetched thumbnails if there are there The optimization broke image uploading, this fixes it again. --- pinry/core/api.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pinry/core/api.py b/pinry/core/api.py index 6cfa069..958d59c 100644 --- a/pinry/core/api.py +++ b/pinry/core/api.py @@ -60,10 +60,13 @@ class UserResource(ModelResource): def filter_generator_for(size): def wrapped_func(bundle, **kwargs): - for thumbnail in bundle.obj._prefetched_objects_cache['thumbnail']: - if thumbnail.size == size: - return thumbnail - raise ObjectDoesNotExist() + if hasattr(bundle.obj, '_prefetched_objects_cache') and 'thumbnail' in bundle.obj._prefetched_objects_cache: + for thumbnail in bundle.obj._prefetched_objects_cache['thumbnail']: + if thumbnail.size == size: + return thumbnail + raise ObjectDoesNotExist() + else: + return bundle.obj.get_by_size(size) return wrapped_func