launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #13794
[Merge] lp:~julian-edwards/maas/1.2-slow-page-bug-1066775 into lp:maas/1.2
Julian Edwards has proposed merging lp:~julian-edwards/maas/1.2-slow-page-bug-1066775 into lp:maas/1.2.
Commit message:
Backport r1289 from trunk - fix slow page load (bug 1066775)
Requested reviews:
Julian Edwards (julian-edwards)
Related bugs:
Bug #1066775 in MAAS: "Main page slow to load with many nodes"
https://bugs.launchpad.net/maas/+bug/1066775
For more details, see:
https://code.launchpad.net/~julian-edwards/maas/1.2-slow-page-bug-1066775/+merge/131569
--
https://code.launchpad.net/~julian-edwards/maas/1.2-slow-page-bug-1066775/+merge/131569
Your team MAAS Maintainers is subscribed to branch lp:maas/1.2.
=== modified file 'src/maasserver/api.py'
--- src/maasserver/api.py 2012-10-26 08:13:16 +0000
+++ src/maasserver/api.py 2012-10-26 10:22:30 +0000
@@ -824,6 +824,9 @@
request.user, NODE_PERMISSION.VIEW, ids=match_ids)
if match_macs is not None:
nodes = nodes.filter(macaddress__mac_address__in=match_macs)
+ # Prefetch related macaddresses and tags.
+ nodes = nodes.prefetch_related('macaddress_set__node')
+ nodes = nodes.prefetch_related('tags')
return nodes.order_by('id')
@operation(idempotent=True)
=== modified file 'src/maasserver/models/macaddress.py'
--- src/maasserver/models/macaddress.py 2012-06-25 12:51:06 +0000
+++ src/maasserver/models/macaddress.py 2012-10-26 10:22:30 +0000
@@ -21,6 +21,7 @@
from maasserver import DefaultMeta
from maasserver.fields import MACAddressField
from maasserver.models.cleansave import CleanSave
+from maasserver.models.managers import BulkManager
from maasserver.models.timestampedmodel import TimestampedModel
@@ -38,6 +39,8 @@
mac_address = MACAddressField(unique=True)
node = ForeignKey('Node', editable=False)
+ objects = BulkManager()
+
class Meta(DefaultMeta):
verbose_name = "MAC address"
verbose_name_plural = "MAC addresses"
=== added file 'src/maasserver/models/managers.py'
--- src/maasserver/models/managers.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/models/managers.py 2012-10-26 10:22:30 +0000
@@ -0,0 +1,30 @@
+# Copyright 2012 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Custom MAAS manager classes."""
+
+from __future__ import (
+ absolute_import,
+ print_function,
+ unicode_literals,
+ )
+
+__metaclass__ = type
+__all__ = [
+ 'BulkManager',
+ ]
+
+
+from django.db.models import Manager
+
+
+class BulkManager(Manager):
+ """A Manager which loads objects from the cache if it's populated.
+
+ Even when iterator() is explicitely called (which happens in piston when
+ related collections are fetched), this manager will fetch objects in bulk
+ if the cache is populated (i.e. if prefetch_related was used).
+ """
+
+ def iterator(self):
+ return self.all()
=== modified file 'src/maasserver/models/node.py'
--- src/maasserver/models/node.py 2012-10-24 14:59:57 +0000
+++ src/maasserver/models/node.py 2012-10-26 10:22:30 +0000
@@ -492,7 +492,9 @@
return self.system_id
def tag_names(self):
- return self.tags.values_list('name', flat=True)
+ # We don't use self.tags.values_list here because this does not
+ # take advantage of the cache.
+ return [tag.name for tag in self.tags.all()]
def clean_status(self):
"""Check a node's status transition against the node-status FSM."""
=== modified file 'src/maasserver/tests/models.py'
--- src/maasserver/tests/models.py 2012-09-18 14:37:28 +0000
+++ src/maasserver/tests/models.py 2012-10-26 10:22:30 +0000
@@ -17,12 +17,14 @@
from django.db.models import (
CharField,
+ ForeignKey,
Model,
)
from maasserver.fields import (
JSONObjectField,
XMLField,
)
+from maasserver.models.managers import BulkManager
from maasserver.models.timestampedmodel import TimestampedModel
@@ -53,3 +55,13 @@
class FieldChangeTestModel(Model):
name1 = CharField(max_length=255, unique=False)
name2 = CharField(max_length=255, unique=False)
+
+
+class BulkManagerParentTestModel(Model):
+ pass
+
+
+class BulkManagerTestModel(Model):
+ parent = ForeignKey('BulkManagerParentTestModel', editable=False)
+
+ objects = BulkManager()
=== modified file 'src/maasserver/tests/test_api.py'
--- src/maasserver/tests/test_api.py 2012-10-26 08:13:16 +0000
+++ src/maasserver/tests/test_api.py 2012-10-26 10:22:30 +0000
@@ -1654,6 +1654,30 @@
[node1.system_id, node2.system_id],
extract_system_ids(parsed_result))
+ def create_nodes(self, nodegroup, nb):
+ [factory.make_node(nodegroup=nodegroup, mac=True)
+ for i in range(nb)]
+
+ def test_GET_list_nodes_issues_constant_number_of_queries(self):
+ nodegroup = factory.make_node_group()
+ self.create_nodes(nodegroup, 10)
+ num_queries1, response1 = self.getNumQueries(
+ self.client.get, self.get_uri('nodes/'), {'op': 'list'})
+ self.create_nodes(nodegroup, 10)
+ num_queries2, response2 = self.getNumQueries(
+ self.client.get, self.get_uri('nodes/'), {'op': 'list'})
+ # Make sure the responses are ok as it's not useful to compare the
+ # number of queries if they are not.
+ self.assertEqual(
+ [httplib.OK, httplib.OK, 10, 20],
+ [
+ response1.status_code,
+ response2.status_code,
+ len(extract_system_ids(json.loads(response1.content))),
+ len(extract_system_ids(json.loads(response2.content))),
+ ])
+ self.assertEqual(num_queries1, num_queries2)
+
def test_GET_list_without_nodes_returns_empty_list(self):
# If there are no nodes to list, the "list" op still works but
# returns an empty list.
=== added file 'src/maasserver/tests/test_managers.py'
--- src/maasserver/tests/test_managers.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/tests/test_managers.py 2012-10-26 10:22:30 +0000
@@ -0,0 +1,42 @@
+# Copyright 2012 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Test maasserver model managers."""
+
+from __future__ import (
+ absolute_import,
+ print_function,
+ unicode_literals,
+ )
+
+__metaclass__ = type
+__all__ = []
+
+from maasserver.testing.testcase import TestModelTestCase
+from maasserver.tests.models import (
+ BulkManagerParentTestModel, BulkManagerTestModel)
+
+
+class BulkManagerTest(TestModelTestCase):
+
+ app = 'maasserver.tests'
+
+ def test_manager_iterator_uses_cache(self):
+ parents = set()
+ for i in range(3):
+ parents.add(BulkManagerParentTestModel.objects.create())
+ for i in range(10):
+ for parent in parents:
+ BulkManagerTestModel.objects.create(parent=parent)
+ parents = BulkManagerParentTestModel.objects.all().prefetch_related(
+ 'bulkmanagertestmodel_set')
+ # Only two queries are used to fetch all the objects:
+ # One to fetch the parents, one to fetch the childrens (the query from
+ # the prefetch_related statement).
+ # Even if we call iterator() on the related objects, the cache is
+ # used because BulkManagerTestModel has a manager based on
+ # BulkManager.
+ self.assertNumQueries(
+ 2,
+ lambda: [list(parent.bulkmanagertestmodel_set.iterator())
+ for parent in parents])
Follow ups