Skip to content

Commit dba99be

Browse files
perf(endpoint_manager): use prefetched status_finding_non_special to avoid extra DB queries
Add a named Prefetch to build_candidate_scope_queryset that fetches only non-special endpoint statuses (excluding false_positive, out_of_scope, risk_accepted) with their endpoint joined in via select_related. This replaces the two separate "status_finding" and "status_finding__endpoint" prefetches with a single query and avoids per-finding DB hits in update_endpoint_status and process_matched_special_status_finding. Update expected performance test counts to reflect the reduced query counts.
1 parent 12a1781 commit dba99be

4 files changed

Lines changed: 24 additions & 25 deletions

File tree

dojo/finding/deduplication.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from django.db.models.query_utils import Q
99

1010
from dojo.celery import app
11-
from dojo.models import Finding, System_Settings
11+
from dojo.models import Endpoint_Status, Finding, System_Settings
1212

1313
logger = logging.getLogger(__name__)
1414
deduplicationLogger = logging.getLogger("dojo.specific-loggers.deduplication")
@@ -295,12 +295,19 @@ def build_candidate_scope_queryset(test, mode="deduplication", service=None):
295295
# Base prefetches for both modes
296296
prefetch_list = ["endpoints", "vulnerability_id_set", "found_by"]
297297

298-
# Additional prefetches for reimport mode
298+
# Additional prefetches for reimport mode: fetch only non-special endpoint statuses with their
299+
# endpoint joined in, so endpoint_manager can read status_finding_non_special directly without
300+
# any extra DB queries
299301
if mode == "reimport":
300-
prefetch_list.extend([
301-
"status_finding",
302-
"status_finding__endpoint",
303-
])
302+
prefetch_list.append(
303+
Prefetch(
304+
"status_finding",
305+
queryset=Endpoint_Status.objects.exclude(
306+
Q(false_positive=True) | Q(out_of_scope=True) | Q(risk_accepted=True),
307+
).select_related("endpoint"),
308+
to_attr="status_finding_non_special",
309+
),
310+
)
304311

305312
return (
306313
queryset

dojo/importers/default_reimporter.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -766,12 +766,8 @@ def process_matched_mitigated_finding(
766766
else:
767767
# TODO: Delete this after the move to Locations
768768
# Reactivate mitigated endpoints that are not false positives, out of scope, or risk accepted
769-
endpoint_statuses = existing_finding.status_finding.exclude(
770-
Q(false_positive=True)
771-
| Q(out_of_scope=True)
772-
| Q(risk_accepted=True),
773-
)
774-
self.endpoint_manager.chunk_endpoints_and_reactivate(endpoint_statuses)
769+
# status_finding_non_special is prefetched by build_candidate_scope_queryset
770+
self.endpoint_manager.chunk_endpoints_and_reactivate(existing_finding.status_finding_non_special)
775771
existing_finding.notes.add(note)
776772
self.reactivated_items.append(existing_finding)
777773
# The new finding is active while the existing on is mitigated. The existing finding needs to

dojo/importers/endpoint_manager.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import logging
22

33
from django.core.exceptions import MultipleObjectsReturned, ValidationError
4-
from django.db.models import Q
54
from django.urls import reverse
65
from django.utils import timezone
76

@@ -158,12 +157,9 @@ def update_endpoint_status(
158157
"""Update the list of endpoints from the new finding with the list that is in the old finding"""
159158
# New endpoints are already added in serializers.py / views.py (see comment "# for existing findings: make sure endpoints are present or created")
160159
# So we only need to mitigate endpoints that are no longer present
161-
# Evaluate the queryset once into a list to avoid double DB evaluation below
162-
existing_finding_endpoint_status_list = list(
163-
existing_finding.status_finding.exclude(
164-
Q(false_positive=True) | Q(out_of_scope=True) | Q(risk_accepted=True),
165-
).select_related("endpoint"),
166-
)
160+
# status_finding_non_special is prefetched by build_candidate_scope_queryset with the
161+
# special-status exclusion and endpoint select_related already applied at the DB level
162+
existing_finding_endpoint_status_list = existing_finding.status_finding_non_special
167163
new_finding_endpoints_list = new_finding.unsaved_endpoints
168164
if new_finding.is_mitigated:
169165
# New finding is mitigated, so mitigate all old endpoints

unittests/test_importers_performance.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -270,9 +270,9 @@ def test_import_reimport_reimport_performance_pghistory_async(self):
270270
self._import_reimport_performance(
271271
expected_num_queries1=295,
272272
expected_num_async_tasks1=6,
273-
expected_num_queries2=232,
273+
expected_num_queries2=226,
274274
expected_num_async_tasks2=17,
275-
expected_num_queries3=114,
275+
expected_num_queries3=108,
276276
expected_num_async_tasks3=16,
277277
)
278278

@@ -292,9 +292,9 @@ def test_import_reimport_reimport_performance_pghistory_no_async(self):
292292
self._import_reimport_performance(
293293
expected_num_queries1=302,
294294
expected_num_async_tasks1=6,
295-
expected_num_queries2=239,
295+
expected_num_queries2=233,
296296
expected_num_async_tasks2=17,
297-
expected_num_queries3=121,
297+
expected_num_queries3=115,
298298
expected_num_async_tasks3=16,
299299
)
300300

@@ -315,9 +315,9 @@ def test_import_reimport_reimport_performance_pghistory_no_async_with_product_gr
315315
self._import_reimport_performance(
316316
expected_num_queries1=309,
317317
expected_num_async_tasks1=8,
318-
expected_num_queries2=246,
318+
expected_num_queries2=240,
319319
expected_num_async_tasks2=19,
320-
expected_num_queries3=125,
320+
expected_num_queries3=119,
321321
expected_num_async_tasks3=18,
322322
)
323323

0 commit comments

Comments
 (0)