Add batch loading for sensor/interface stats to eliminate N+1 queries
Added two new functions to Snmp context: - get_latest_sensor_readings_batch/1 - Loads latest readings for multiple sensors in one query - get_latest_interface_stats_batch/1 - Loads latest stats for multiple interfaces in one query Both use PostgreSQL DISTINCT ON to efficiently get only the latest record per ID. Updated EquipmentLive.Show.load_snmp_data/1 to use batch functions: - Equipment with 20 sensors: 20 queries → 1 query (20x reduction) - Equipment with 10 interfaces: 10 queries → 1 query (10x reduction) - Total improvement: 30 queries → 2 queries for typical equipment page load This significantly improves page load performance for equipment with many sensors/interfaces.
This commit is contained in:
parent
ccae030569
commit
723f064315
2 changed files with 68 additions and 4 deletions
|
|
@ -227,6 +227,35 @@ defmodule Towerops.Snmp do
|
|||
|> Repo.one()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the latest sensor readings for multiple sensors in a single query.
|
||||
|
||||
Returns a map of %{sensor_id => reading} for efficient batch loading.
|
||||
Sensors without readings will not be present in the map.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_latest_sensor_readings_batch([sensor_id1, sensor_id2])
|
||||
%{sensor_id1 => %SensorReading{}, sensor_id2 => %SensorReading{}}
|
||||
"""
|
||||
def get_latest_sensor_readings_batch(sensor_ids) when is_list(sensor_ids) do
|
||||
if Enum.empty?(sensor_ids) do
|
||||
%{}
|
||||
else
|
||||
# Use DISTINCT ON to get only the latest reading per sensor
|
||||
query =
|
||||
from(r in SensorReading,
|
||||
where: r.sensor_id in ^sensor_ids,
|
||||
distinct: r.sensor_id,
|
||||
order_by: [asc: r.sensor_id, desc: r.checked_at]
|
||||
)
|
||||
|
||||
query
|
||||
|> Repo.all()
|
||||
|> Map.new(fn reading -> {reading.sensor_id, reading} end)
|
||||
end
|
||||
end
|
||||
|
||||
# Interface stats queries
|
||||
|
||||
@doc """
|
||||
|
|
@ -268,6 +297,35 @@ defmodule Towerops.Snmp do
|
|||
|> Repo.one()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the latest interface stats for multiple interfaces in a single query.
|
||||
|
||||
Returns a map of %{interface_id => stat} for efficient batch loading.
|
||||
Interfaces without stats will not be present in the map.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_latest_interface_stats_batch([if_id1, if_id2])
|
||||
%{if_id1 => %InterfaceStat{}, if_id2 => %InterfaceStat{}}
|
||||
"""
|
||||
def get_latest_interface_stats_batch(interface_ids) when is_list(interface_ids) do
|
||||
if Enum.empty?(interface_ids) do
|
||||
%{}
|
||||
else
|
||||
# Use DISTINCT ON to get only the latest stat per interface
|
||||
query =
|
||||
from(s in InterfaceStat,
|
||||
where: s.interface_id in ^interface_ids,
|
||||
distinct: s.interface_id,
|
||||
order_by: [asc: s.interface_id, desc: s.checked_at]
|
||||
)
|
||||
|
||||
query
|
||||
|> Repo.all()
|
||||
|> Map.new(fn stat -> {stat.interface_id, stat} end)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Records a new sensor reading.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -215,17 +215,23 @@ defmodule ToweropsWeb.EquipmentLive.Show do
|
|||
%{device: nil, interfaces: [], sensors: []}
|
||||
|
||||
device ->
|
||||
# Preload latest readings for sensors
|
||||
# Batch load latest readings for all sensors in a single query
|
||||
sensor_ids = Enum.map(device.sensors, & &1.id)
|
||||
latest_readings_map = Snmp.get_latest_sensor_readings_batch(sensor_ids)
|
||||
|
||||
sensors_with_readings =
|
||||
Enum.map(device.sensors, fn sensor ->
|
||||
latest_reading = Snmp.get_latest_sensor_reading(sensor.id)
|
||||
latest_reading = Map.get(latest_readings_map, sensor.id)
|
||||
Map.put(sensor, :latest_reading, latest_reading)
|
||||
end)
|
||||
|
||||
# Preload latest stats for interfaces
|
||||
# Batch load latest stats for all interfaces in a single query
|
||||
interface_ids = Enum.map(device.interfaces, & &1.id)
|
||||
latest_stats_map = Snmp.get_latest_interface_stats_batch(interface_ids)
|
||||
|
||||
interfaces_with_stats =
|
||||
Enum.map(device.interfaces, fn interface ->
|
||||
latest_stat = Snmp.get_latest_interface_stat(interface.id)
|
||||
latest_stat = Map.get(latest_stats_map, interface.id)
|
||||
Map.put(interface, :latest_stat, latest_stat)
|
||||
end)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue