AAP-81082 — Eliminate LEFT OUTER JOINs in unified job RBAC query (#16527)

* AAP-81082 Eliminate LEFT OUTER JOINs in unified job RBAC query

UnifiedJobAccess.filtered_queryset() used field-traversal Q objects
(inventoryupdate__inventory_source__inventory__id__in=...) which Django
translates into LEFT OUTER JOINs. This forces PostgreSQL to scan all
rows in main_unifiedjob before filtering — at scale, 99.84% are
discarded. Replace with pk__in subqueries that generate IN (SELECT ...)
instead, allowing PostgreSQL to skip the unconditional join. EXPLAIN
ANALYZE shows a 28% reduction in execution time (496ms -> 355ms), with
larger gains expected under concurrent load.

* AAP-81082 Eliminate LEFT OUTER JOINs in RBAC filtered_queryset methods

Replace field-traversal Q objects with pk__in subqueries across all Access
classes that query polymorphic or M2M tables, preventing Django from
generating unconditional LEFT OUTER JOINs. Also migrate legacy
_accessible_pk_qs / accessible_pk_qs calls to DAB RBAC access_ids_qs.

Affected: UnifiedJobAccess, UnifiedJobTemplateAccess, JobAccess,
JobEventAccess, LabelAccess.

* Fix docstring
This commit is contained in:
Dirk Julich
2026-07-01 13:18:09 +02:00
committed by GitHub
parent 8ab5deb54a
commit f8fa690de3

View File

@@ -1665,11 +1665,11 @@ class JobAccess(BaseAccess):
def filtered_queryset(self):
qs = self.model.objects
qs_jt = qs.filter(job_template__in=JobTemplate.access_qs(self.user, 'view'))
org_access_qs = Organization.objects.filter(Q(admin_role__members=self.user) | Q(auditor_role__members=self.user))
org_access_qs = Organization.objects.filter(
Q(pk__in=Organization.access_ids_qs(self.user, 'change')) | Q(pk__in=Organization.access_ids_qs(self.user, 'audit_organization'))
)
if not org_access_qs.exists():
return qs_jt
return qs.filter(job_template__in=JobTemplate.access_qs(self.user, 'view'))
return qs.filter(Q(job_template__in=JobTemplate.access_qs(self.user, 'view')) | Q(organization__in=org_access_qs)).distinct()
@@ -2309,7 +2309,7 @@ class JobHostSummaryAccess(BaseAccess):
class JobEventAccess(BaseAccess):
"""
I can see job event records whenever I can read both job and host.
I can see job event records whenever I can read the job or the host.
"""
model = JobEvent
@@ -2320,8 +2320,8 @@ class JobEventAccess(BaseAccess):
def filtered_queryset(self):
return self.model.objects.filter(
Q(host__inventory__in=Inventory.accessible_pk_qs(self.user, 'read_role'))
| Q(job__job_template__in=JobTemplate.accessible_pk_qs(self.user, 'read_role'))
Q(host_id__in=Host.objects.filter(inventory__in=Inventory.access_ids_qs(self.user, 'view')).values('pk'))
| Q(job_id__in=Job.objects.filter(job_template__in=JobTemplate.access_ids_qs(self.user, 'view')).values('pk'))
)
def can_add(self, data):
@@ -2451,7 +2451,11 @@ class UnifiedJobTemplateAccess(BaseAccess):
def filtered_queryset(self):
return self.model.objects.filter(
Q(pk__in=self.model.accessible_pk_qs(self.user, 'read_role'))
| Q(inventorysource__inventory__id__in=Inventory._accessible_pk_qs(Inventory, self.user, 'read_role'))
| Q(
pk__in=InventorySource.objects.filter(
inventory__id__in=Inventory.access_ids_qs(self.user, 'view'),
).values('unifiedjobtemplate_ptr_id')
)
)
def can_start(self, obj, validate_license=True):
@@ -2497,12 +2501,20 @@ class UnifiedJobAccess(BaseAccess):
# )
def filtered_queryset(self):
inv_pk_qs = Inventory._accessible_pk_qs(Inventory, self.user, 'read_role')
inv_pk_qs = Inventory.access_ids_qs(self.user, 'view')
qs = self.model.objects.filter(
Q(unified_job_template_id__in=UnifiedJobTemplate.accessible_pk_qs(self.user, 'read_role'))
| Q(inventoryupdate__inventory_source__inventory__id__in=inv_pk_qs)
| Q(adhoccommand__inventory__id__in=inv_pk_qs)
| Q(organization__in=Organization.accessible_pk_qs(self.user, 'auditor_role'))
| Q(
pk__in=InventoryUpdate.objects.filter(
inventory_source__inventory__id__in=inv_pk_qs,
).values('pk')
)
| Q(
pk__in=AdHocCommand.objects.filter(
inventory__id__in=inv_pk_qs,
).values('pk')
)
| Q(organization__in=Organization.access_ids_qs(self.user, 'audit_organization'))
)
return qs
@@ -2622,9 +2634,13 @@ class LabelAccess(BaseAccess):
def filtered_queryset(self):
return self.model.objects.filter(
Q(organization__in=Organization.accessible_pk_qs(self.user, 'read_role'))
| Q(unifiedjobtemplate_labels__in=UnifiedJobTemplate.accessible_pk_qs(self.user, 'read_role'))
).distinct()
Q(organization__in=Organization.access_ids_qs(self.user, 'view'))
| Q(
pk__in=UnifiedJobTemplate.labels.through.objects.filter(
unifiedjobtemplate_id__in=UnifiedJobTemplate.accessible_pk_qs(self.user, 'read_role'),
).values('label_id')
)
)
@check_superuser
def can_add(self, data):