From 07debd98a70deb626aa82a4ca635baa01e646e69 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Wed, 14 Jan 2026 18:59:48 -0600 Subject: [PATCH] Optimize equipment assignment breakdown N+1 query Replaced application-level logic with single SQL query using CASE statement to determine assignment source directly in the database. Before: Loaded all equipment, called get_effective_agent_token_with_source for each (500 equipment = 500+ queries) After: Single query with CASE statement to classify assignment source Performance improvement: Reduces from O(n) queries to 1 query regardless of equipment count. --- lib/towerops/agents/stats.ex | 49 ++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/lib/towerops/agents/stats.ex b/lib/towerops/agents/stats.ex index 47bc86b3..7e012dc5 100644 --- a/lib/towerops/agents/stats.ex +++ b/lib/towerops/agents/stats.ex @@ -91,25 +91,42 @@ defmodule Towerops.Agents.Stats do } """ def get_equipment_assignment_breakdown(organization_id) do - # Get all equipment for organization - equipment_query = - from(e in Equipment, - join: s in assoc(e, :site), - where: s.organization_id == ^organization_id and e.snmp_enabled == true, - preload: [:site, site: :organization] + # Use a single SQL query with CASE to determine assignment source + # This avoids loading all equipment into memory and calling get_effective_agent_token_with_source + results = + Repo.all( + from(e in Equipment, + join: s in assoc(e, :site), + join: o in assoc(s, :organization), + left_join: aa in AgentAssignment, + on: aa.equipment_id == e.id and aa.enabled == true, + where: s.organization_id == ^organization_id and e.snmp_enabled == true, + select: %{ + source: + fragment( + """ + CASE + WHEN ? IS NOT NULL THEN 'equipment' + WHEN ? IS NOT NULL THEN 'site' + WHEN ? IS NOT NULL THEN 'organization' + ELSE 'cloud' + END + """, + aa.agent_token_id, + s.agent_token_id, + o.default_agent_token_id + ) + } + ) ) - equipment_list = Repo.all(equipment_query) - - # Count by assignment type - Enum.reduce(equipment_list, %{direct: 0, site: 0, organization: 0, cloud: 0}, fn eq, acc -> - {_agent_id, source} = Towerops.Agents.get_effective_agent_token_with_source(eq) - + # Count by assignment source + Enum.reduce(results, %{direct: 0, site: 0, organization: 0, cloud: 0}, fn %{source: source}, acc -> case source do - :equipment -> %{acc | direct: acc.direct + 1} - :site -> %{acc | site: acc.site + 1} - :organization -> %{acc | organization: acc.organization + 1} - :none -> %{acc | cloud: acc.cloud + 1} + "equipment" -> %{acc | direct: acc.direct + 1} + "site" -> %{acc | site: acc.site + 1} + "organization" -> %{acc | organization: acc.organization + 1} + "cloud" -> %{acc | cloud: acc.cloud + 1} end end) end