mirror of
https://github.com/ansible/awx.git
synced 2026-07-02 20:08:02 -02:30
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:
@@ -1665,11 +1665,11 @@ class JobAccess(BaseAccess):
|
|||||||
def filtered_queryset(self):
|
def filtered_queryset(self):
|
||||||
qs = self.model.objects
|
qs = self.model.objects
|
||||||
|
|
||||||
qs_jt = qs.filter(job_template__in=JobTemplate.access_qs(self.user, 'view'))
|
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'))
|
||||||
org_access_qs = Organization.objects.filter(Q(admin_role__members=self.user) | Q(auditor_role__members=self.user))
|
)
|
||||||
if not org_access_qs.exists():
|
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()
|
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):
|
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
|
model = JobEvent
|
||||||
@@ -2320,8 +2320,8 @@ class JobEventAccess(BaseAccess):
|
|||||||
|
|
||||||
def filtered_queryset(self):
|
def filtered_queryset(self):
|
||||||
return self.model.objects.filter(
|
return self.model.objects.filter(
|
||||||
Q(host__inventory__in=Inventory.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__job_template__in=JobTemplate.accessible_pk_qs(self.user, 'read_role'))
|
| Q(job_id__in=Job.objects.filter(job_template__in=JobTemplate.access_ids_qs(self.user, 'view')).values('pk'))
|
||||||
)
|
)
|
||||||
|
|
||||||
def can_add(self, data):
|
def can_add(self, data):
|
||||||
@@ -2451,7 +2451,11 @@ class UnifiedJobTemplateAccess(BaseAccess):
|
|||||||
def filtered_queryset(self):
|
def filtered_queryset(self):
|
||||||
return self.model.objects.filter(
|
return self.model.objects.filter(
|
||||||
Q(pk__in=self.model.accessible_pk_qs(self.user, 'read_role'))
|
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):
|
def can_start(self, obj, validate_license=True):
|
||||||
@@ -2497,12 +2501,20 @@ class UnifiedJobAccess(BaseAccess):
|
|||||||
# )
|
# )
|
||||||
|
|
||||||
def filtered_queryset(self):
|
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(
|
qs = self.model.objects.filter(
|
||||||
Q(unified_job_template_id__in=UnifiedJobTemplate.accessible_pk_qs(self.user, 'read_role'))
|
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(
|
||||||
| Q(adhoccommand__inventory__id__in=inv_pk_qs)
|
pk__in=InventoryUpdate.objects.filter(
|
||||||
| Q(organization__in=Organization.accessible_pk_qs(self.user, 'auditor_role'))
|
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
|
return qs
|
||||||
|
|
||||||
@@ -2622,9 +2634,13 @@ class LabelAccess(BaseAccess):
|
|||||||
|
|
||||||
def filtered_queryset(self):
|
def filtered_queryset(self):
|
||||||
return self.model.objects.filter(
|
return self.model.objects.filter(
|
||||||
Q(organization__in=Organization.accessible_pk_qs(self.user, 'read_role'))
|
Q(organization__in=Organization.access_ids_qs(self.user, 'view'))
|
||||||
| Q(unifiedjobtemplate_labels__in=UnifiedJobTemplate.accessible_pk_qs(self.user, 'read_role'))
|
| Q(
|
||||||
).distinct()
|
pk__in=UnifiedJobTemplate.labels.through.objects.filter(
|
||||||
|
unifiedjobtemplate_id__in=UnifiedJobTemplate.accessible_pk_qs(self.user, 'read_role'),
|
||||||
|
).values('label_id')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@check_superuser
|
@check_superuser
|
||||||
def can_add(self, data):
|
def can_add(self, data):
|
||||||
|
|||||||
Reference in New Issue
Block a user