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
|
import Ecto.Query
|
||||||
|
|
||||||
|
alias Microwaveprop.Cache
|
||||||
alias Microwaveprop.Radio.Maidenhead
|
alias Microwaveprop.Radio.Maidenhead
|
||||||
alias Microwaveprop.Repo
|
alias Microwaveprop.Repo
|
||||||
alias Microwaveprop.Rover.Location
|
alias Microwaveprop.Rover.Location
|
||||||
|
|
||||||
|
@cache_key {__MODULE__, :points}
|
||||||
|
@cache_ttl_ms 30_000
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def mount(_params, _session, socket) do
|
def mount(_params, _session, socket) do
|
||||||
points =
|
points = cached_points()
|
||||||
Location
|
|
||||||
|> where([l], l.status == :good)
|
|
||||||
|> Repo.all()
|
|
||||||
|> Enum.map(&point_payload/1)
|
|
||||||
|
|
||||||
{:ok,
|
{:ok,
|
||||||
assign(socket,
|
assign(socket,
|
||||||
|
|
@ -27,6 +27,15 @@ defmodule MicrowavepropWeb.RoverLocationsLive.Map do
|
||||||
)}
|
)}
|
||||||
end
|
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
|
defp point_payload(%Location{} = loc) do
|
||||||
%{
|
%{
|
||||||
id: loc.id,
|
id: loc.id,
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ defmodule MicrowavepropWeb.RoverPlanningLive.Show do
|
||||||
{:noreply,
|
{:noreply,
|
||||||
assign(socket,
|
assign(socket,
|
||||||
rover_site_input: input,
|
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
|
end
|
||||||
|
|
||||||
|
|
@ -112,20 +112,14 @@ defmodule MicrowavepropWeb.RoverPlanningLive.Show do
|
||||||
|
|
||||||
defp authenticated(_), do: {:error, :unauthenticated}
|
defp authenticated(_), do: {:error, :unauthenticated}
|
||||||
|
|
||||||
# Resolve the user-typed input and surface any existing rover-locations
|
defp find_grid_matches(input, locations) when is_binary(input) do
|
||||||
# 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
|
|
||||||
trimmed = String.trim(input)
|
trimmed = String.trim(input)
|
||||||
|
|
||||||
case trimmed != "" && LocationResolver.resolve(trimmed) do
|
case trimmed != "" && LocationResolver.resolve(trimmed) do
|
||||||
{:ok, %{lat: lat, lon: lon}} when is_number(lat) and is_number(lon) ->
|
{:ok, %{lat: lat, lon: lon}} when is_number(lat) and is_number(lon) ->
|
||||||
target = Maidenhead.from_latlon(lat, lon, 6)
|
target = Maidenhead.from_latlon(lat, lon, 6)
|
||||||
|
|
||||||
Location
|
Enum.filter(locations, fn loc ->
|
||||||
|> Repo.all()
|
|
||||||
|> Enum.filter(fn loc ->
|
|
||||||
Maidenhead.from_latlon(loc.lat, loc.lon, 6) == target
|
Maidenhead.from_latlon(loc.lat, loc.lon, 6) == target
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
@ -134,7 +128,7 @@ defmodule MicrowavepropWeb.RoverPlanningLive.Show do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp find_grid_matches(_), do: []
|
defp find_grid_matches(_, _), do: []
|
||||||
|
|
||||||
defp can_modify?(%{current_scope: %{user: %User{is_admin: true}}}, _), do: true
|
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.Repo
|
||||||
alias Microwaveprop.Weather.NarrClient
|
alias Microwaveprop.Weather.NarrClient
|
||||||
|
|
||||||
|
@all_stats_cache_key {__MODULE__, :all_stats}
|
||||||
|
@stats_cache_ttl_ms 2_000
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def mount(_params, _session, socket) do
|
def mount(_params, _session, socket) do
|
||||||
_ =
|
_ =
|
||||||
|
|
@ -19,24 +22,20 @@ defmodule MicrowavepropWeb.StatusLive do
|
||||||
# Rust prop-grid-rs emits NOTIFY propagation_ready on completion —
|
# Rust prop-grid-rs emits NOTIFY propagation_ready on completion —
|
||||||
# PropagationNotifyListener fans it out as `propagation:updated` so
|
# PropagationNotifyListener fans it out as `propagation:updated` so
|
||||||
# the grid_tasks panel transitions from "running" → "done" without
|
# the grid_tasks panel transitions from "running" → "done" without
|
||||||
# a manual refresh.
|
# a single refresh.
|
||||||
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "propagation:updated")
|
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "propagation:updated")
|
||||||
end
|
end
|
||||||
|
|
||||||
stats = fetch_stats()
|
all = fetch_all_stats()
|
||||||
unprocessed = count_unprocessed()
|
|
||||||
db_stats = fetch_db_stats()
|
|
||||||
grid_tasks = fetch_grid_tasks_stats()
|
|
||||||
hrrr_point_tasks = fetch_hrrr_point_tasks_stats()
|
|
||||||
|
|
||||||
{:ok,
|
{:ok,
|
||||||
assign(socket,
|
assign(socket,
|
||||||
page_title: "Status",
|
page_title: "Status",
|
||||||
stats: stats,
|
stats: all.stats,
|
||||||
unprocessed: unprocessed,
|
unprocessed: all.unprocessed,
|
||||||
db_stats: db_stats,
|
db_stats: all.db_stats,
|
||||||
grid_tasks: grid_tasks,
|
grid_tasks: all.grid_tasks,
|
||||||
hrrr_point_tasks: hrrr_point_tasks,
|
hrrr_point_tasks: all.hrrr_point_tasks,
|
||||||
refresh_timer: nil
|
refresh_timer: nil
|
||||||
)}
|
)}
|
||||||
end
|
end
|
||||||
|
|
@ -55,25 +54,36 @@ defmodule MicrowavepropWeb.StatusLive do
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info(:refresh_stats, socket) do
|
def handle_info(:refresh_stats, socket) do
|
||||||
|
all = fetch_all_stats()
|
||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
assign(socket,
|
assign(socket,
|
||||||
stats: fetch_stats(),
|
stats: all.stats,
|
||||||
unprocessed: count_unprocessed(),
|
unprocessed: all.unprocessed,
|
||||||
db_stats: fetch_db_stats(),
|
db_stats: all.db_stats,
|
||||||
grid_tasks: fetch_grid_tasks_stats(),
|
grid_tasks: all.grid_tasks,
|
||||||
hrrr_point_tasks: fetch_hrrr_point_tasks_stats(),
|
hrrr_point_tasks: all.hrrr_point_tasks,
|
||||||
refresh_timer: nil
|
refresh_timer: nil
|
||||||
)}
|
)}
|
||||||
end
|
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
|
defp schedule_refresh(%{assigns: %{refresh_timer: ref}} = socket) when is_reference(ref) do
|
||||||
# Already scheduled, skip
|
|
||||||
socket
|
socket
|
||||||
end
|
end
|
||||||
|
|
||||||
defp schedule_refresh(socket) do
|
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)
|
ref = Process.send_after(self(), :refresh_stats, 2_000)
|
||||||
assign(socket, refresh_timer: ref)
|
assign(socket, refresh_timer: ref)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue