perf: fix high-severity LiveView blocking queries
- rover_planning_live/show.ex: use pre-loaded @rover_sites assign instead of Repo.all(Location) on every keystroke - rover_locations_live/map.ex: cache locations query with 30s TTL (was loading + JSON-encoding all good locations in every mount) - status_live.ex: consolidate 5 separate stat fetches into single cached blob, so PubSub-triggered refreshes hit cache instead of running 10+ DB queries each time
This commit is contained in:
parent
26be066f77
commit
473c2ab0ec
3 changed files with 47 additions and 34 deletions
|
|
@ -7,17 +7,17 @@ defmodule MicrowavepropWeb.RoverLocationsLive.Map do
|
|||
|
||||
import Ecto.Query
|
||||
|
||||
alias Microwaveprop.Cache
|
||||
alias Microwaveprop.Radio.Maidenhead
|
||||
alias Microwaveprop.Repo
|
||||
alias Microwaveprop.Rover.Location
|
||||
|
||||
@cache_key {__MODULE__, :points}
|
||||
@cache_ttl_ms 30_000
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
points =
|
||||
Location
|
||||
|> where([l], l.status == :good)
|
||||
|> Repo.all()
|
||||
|> Enum.map(&point_payload/1)
|
||||
points = cached_points()
|
||||
|
||||
{:ok,
|
||||
assign(socket,
|
||||
|
|
@ -27,6 +27,15 @@ defmodule MicrowavepropWeb.RoverLocationsLive.Map do
|
|||
)}
|
||||
end
|
||||
|
||||
defp cached_points do
|
||||
Cache.fetch_or_store(@cache_key, @cache_ttl_ms, fn ->
|
||||
Location
|
||||
|> where([l], l.status == :good)
|
||||
|> Repo.all()
|
||||
|> Enum.map(&point_payload/1)
|
||||
end)
|
||||
end
|
||||
|
||||
defp point_payload(%Location{} = loc) do
|
||||
%{
|
||||
id: loc.id,
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ defmodule MicrowavepropWeb.RoverPlanningLive.Show do
|
|||
{:noreply,
|
||||
assign(socket,
|
||||
rover_site_input: input,
|
||||
rover_site_input_matches: find_grid_matches(input)
|
||||
rover_site_input_matches: find_grid_matches(input, socket.assigns.rover_sites)
|
||||
)}
|
||||
end
|
||||
|
||||
|
|
@ -112,20 +112,14 @@ defmodule MicrowavepropWeb.RoverPlanningLive.Show do
|
|||
|
||||
defp authenticated(_), do: {:error, :unauthenticated}
|
||||
|
||||
# Resolve the user-typed input and surface any existing rover-locations
|
||||
# that share its 6-char Maidenhead grid (sub-grid resolution ≈ 5 km).
|
||||
# Cheap enough for ~hundreds of locations: we map them in memory rather
|
||||
# than maintaining a stored grid column.
|
||||
defp find_grid_matches(input) when is_binary(input) do
|
||||
defp find_grid_matches(input, locations) when is_binary(input) do
|
||||
trimmed = String.trim(input)
|
||||
|
||||
case trimmed != "" && LocationResolver.resolve(trimmed) do
|
||||
{:ok, %{lat: lat, lon: lon}} when is_number(lat) and is_number(lon) ->
|
||||
target = Maidenhead.from_latlon(lat, lon, 6)
|
||||
|
||||
Location
|
||||
|> Repo.all()
|
||||
|> Enum.filter(fn loc ->
|
||||
Enum.filter(locations, fn loc ->
|
||||
Maidenhead.from_latlon(loc.lat, loc.lon, 6) == target
|
||||
end)
|
||||
|
||||
|
|
@ -134,7 +128,7 @@ defmodule MicrowavepropWeb.RoverPlanningLive.Show do
|
|||
end
|
||||
end
|
||||
|
||||
defp find_grid_matches(_), do: []
|
||||
defp find_grid_matches(_, _), do: []
|
||||
|
||||
defp can_modify?(%{current_scope: %{user: %User{is_admin: true}}}, _), do: true
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ defmodule MicrowavepropWeb.StatusLive do
|
|||
alias Microwaveprop.Repo
|
||||
alias Microwaveprop.Weather.NarrClient
|
||||
|
||||
@all_stats_cache_key {__MODULE__, :all_stats}
|
||||
@stats_cache_ttl_ms 2_000
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
_ =
|
||||
|
|
@ -19,24 +22,20 @@ defmodule MicrowavepropWeb.StatusLive do
|
|||
# Rust prop-grid-rs emits NOTIFY propagation_ready on completion —
|
||||
# PropagationNotifyListener fans it out as `propagation:updated` so
|
||||
# the grid_tasks panel transitions from "running" → "done" without
|
||||
# a manual refresh.
|
||||
# a single refresh.
|
||||
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "propagation:updated")
|
||||
end
|
||||
|
||||
stats = fetch_stats()
|
||||
unprocessed = count_unprocessed()
|
||||
db_stats = fetch_db_stats()
|
||||
grid_tasks = fetch_grid_tasks_stats()
|
||||
hrrr_point_tasks = fetch_hrrr_point_tasks_stats()
|
||||
all = fetch_all_stats()
|
||||
|
||||
{:ok,
|
||||
assign(socket,
|
||||
page_title: "Status",
|
||||
stats: stats,
|
||||
unprocessed: unprocessed,
|
||||
db_stats: db_stats,
|
||||
grid_tasks: grid_tasks,
|
||||
hrrr_point_tasks: hrrr_point_tasks,
|
||||
stats: all.stats,
|
||||
unprocessed: all.unprocessed,
|
||||
db_stats: all.db_stats,
|
||||
grid_tasks: all.grid_tasks,
|
||||
hrrr_point_tasks: all.hrrr_point_tasks,
|
||||
refresh_timer: nil
|
||||
)}
|
||||
end
|
||||
|
|
@ -55,25 +54,36 @@ defmodule MicrowavepropWeb.StatusLive do
|
|||
end
|
||||
|
||||
def handle_info(:refresh_stats, socket) do
|
||||
all = fetch_all_stats()
|
||||
|
||||
{:noreply,
|
||||
assign(socket,
|
||||
stats: fetch_stats(),
|
||||
unprocessed: count_unprocessed(),
|
||||
db_stats: fetch_db_stats(),
|
||||
grid_tasks: fetch_grid_tasks_stats(),
|
||||
hrrr_point_tasks: fetch_hrrr_point_tasks_stats(),
|
||||
stats: all.stats,
|
||||
unprocessed: all.unprocessed,
|
||||
db_stats: all.db_stats,
|
||||
grid_tasks: all.grid_tasks,
|
||||
hrrr_point_tasks: all.hrrr_point_tasks,
|
||||
refresh_timer: nil
|
||||
)}
|
||||
end
|
||||
|
||||
defp fetch_all_stats do
|
||||
Cache.fetch_or_store(@all_stats_cache_key, @stats_cache_ttl_ms, fn ->
|
||||
%{
|
||||
stats: fetch_stats(),
|
||||
unprocessed: count_unprocessed(),
|
||||
db_stats: fetch_db_stats(),
|
||||
grid_tasks: fetch_grid_tasks_stats(),
|
||||
hrrr_point_tasks: fetch_hrrr_point_tasks_stats()
|
||||
}
|
||||
end)
|
||||
end
|
||||
|
||||
defp schedule_refresh(%{assigns: %{refresh_timer: ref}} = socket) when is_reference(ref) do
|
||||
# Already scheduled, skip
|
||||
socket
|
||||
end
|
||||
|
||||
defp schedule_refresh(socket) do
|
||||
# 2s debounce — bulk enrichment runs produce hundreds of status_changed
|
||||
# events per second, and each stats refresh fires 8+ count queries.
|
||||
ref = Process.send_after(self(), :refresh_stats, 2_000)
|
||||
assign(socket, refresh_timer: ref)
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue