Skip to content

Commit 1dd4d03

Browse files
authored
Fixed performance issue with SSoT Logs table view (#1057)
1 parent c9e8b58 commit 1dd4d03

File tree

3 files changed

+64
-2
lines changed

3 files changed

+64
-2
lines changed

changes/1055.fixed

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed a performance issue with the SSoT log entries table view.

nautobot_ssot/tests/test_views.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
from unittest import skip
55

66
from django.contrib.contenttypes.models import ContentType
7+
from django.db import connection
78
from django.test import override_settings
9+
from django.test.utils import CaptureQueriesContext
810
from django.urls import reverse
911
from nautobot.apps.testing import ViewTestCases
1012
from nautobot.core.testing.utils import disable_warnings
@@ -13,6 +15,7 @@
1315

1416
from nautobot_ssot.choices import SyncLogEntryActionChoices, SyncLogEntryStatusChoices
1517
from nautobot_ssot.models import Sync, SyncLogEntry
18+
from nautobot_ssot.views import SyncLogEntryUIViewSet
1619

1720

1821
class SyncViewsTestCase( # pylint: disable=too-many-ancestors
@@ -150,7 +153,7 @@ def setUpTestData(cls):
150153
target="Nautobot",
151154
start_time=datetime.now(),
152155
dry_run=False,
153-
diff={},
156+
diff={"foo": "bar"},
154157
job_result=job_result,
155158
)
156159

@@ -169,3 +172,45 @@ def setUpTestData(cls):
169172
@skip("Not implemented")
170173
def test_list_objects_with_constrained_permission(self):
171174
pass
175+
176+
def test_queryset_optimization(self):
177+
"""Test that the SyncLogEntry queryset uses select_related and only() correctly."""
178+
# Get the queryset from the viewset
179+
queryset = SyncLogEntryUIViewSet.queryset
180+
181+
# Evaluate the queryset to get all log entries
182+
# This should trigger a single query with select_related
183+
with CaptureQueriesContext(connection) as ctx:
184+
log_entries = list(queryset.all())
185+
186+
# Should have exactly 1 query (the main query with select_related)
187+
self.assertEqual(len(ctx.captured_queries), 1, "Queryset should use select_related to fetch in one query")
188+
189+
# Verify we have log entries
190+
self.assertGreater(len(log_entries), 0)
191+
192+
# Test that accessing sync-related fields doesn't trigger additional queries
193+
with CaptureQueriesContext(connection) as ctx:
194+
for entry in log_entries:
195+
# Access all the sync fields that are specified in the only() clause
196+
_ = entry.sync.id
197+
_ = entry.sync.source
198+
_ = entry.sync.target
199+
_ = entry.sync.start_time
200+
# Also test accessing the sync object itself (for __str__)
201+
_ = str(entry.sync)
202+
203+
# Should have 0 additional queries since select_related was used
204+
self.assertEqual(len(ctx.captured_queries), 0, "Accessing sync fields should not trigger additional queries")
205+
206+
# Verify that sync.diff was NOT loaded (it's not in the only() clause)
207+
# Accessing it should trigger an additional query
208+
with CaptureQueriesContext(connection) as ctx:
209+
_ = log_entries[0].sync.diff
210+
211+
# Should have 1 additional query to fetch the deferred diff field
212+
self.assertEqual(
213+
len(ctx.captured_queries),
214+
1,
215+
"Accessing sync.diff should trigger an additional query since it wasn't loaded in the original queryset",
216+
)

nautobot_ssot/views.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,23 @@ class SyncLogEntryUIViewSet(ObjectListViewMixin):
379379
filterset_class = SyncLogEntryFilterSet
380380
filterset_form_class = SyncLogEntryFilterForm
381381
lookup_field = "pk"
382-
queryset = SyncLogEntry.objects.all()
382+
queryset = SyncLogEntry.objects.select_related("sync").only(
383+
"id",
384+
"timestamp",
385+
"sync_id",
386+
"action",
387+
"status",
388+
"diff",
389+
"message",
390+
"synced_object_type",
391+
"synced_object_id",
392+
"object_repr",
393+
# Sync fields needed for __str__ and link rendering
394+
"sync__id",
395+
"sync__source",
396+
"sync__target",
397+
"sync__start_time",
398+
)
383399
serializer_class = serializers.SyncLogEntrySerializer
384400
table_class = SyncLogEntryTable
385401
action_buttons = ("export",)

0 commit comments

Comments
 (0)