From 30525a18f3a99681c2f687c0f54a167a4e2a5f94 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Mon, 20 Apr 2026 16:56:14 -0500 Subject: [PATCH] perf(status): collapse 7 contact-count queries to a single FILTER scan count_unprocessed_raw fired seven separate COUNT(*) scans on the contacts table every 2 s refresh (total, terrain, hrrr, weather, iemre, radar, all-done). Each one went through the 58k+ row table independently, so the status page was hitting Postgres with ~3.5 full scans per second of connected viewer time. A single query with COUNT(*) FILTER clauses pulls every count in one sequential scan. Same result, roughly 1/7 the CPU on Postgres. --- lib/microwaveprop_web/live/status_live.ex | 59 ++++++++++------------- 1 file changed, 25 insertions(+), 34 deletions(-) diff --git a/lib/microwaveprop_web/live/status_live.ex b/lib/microwaveprop_web/live/status_live.ex index bb895fea..f3dc2c5b 100644 --- a/lib/microwaveprop_web/live/status_live.ex +++ b/lib/microwaveprop_web/live/status_live.ex @@ -83,21 +83,35 @@ defmodule MicrowavepropWeb.StatusLive do end defp count_unprocessed_raw do - incomplete = [:pending, :queued, :processing, :failed] - done = [:complete, :unavailable] - - total = Repo.one(from(c in Contact, where: not is_nil(c.pos1), select: count())) - terrain = count_incomplete(:terrain_status, incomplete) - hrrr = count_incomplete(:hrrr_status, incomplete) - weather = count_incomplete(:weather_status, incomplete) - iemre = count_incomplete(:iemre_status, incomplete) - radar = count_incomplete(:radar_status, incomplete) + # Previously issued 7 separate `SELECT COUNT(*) FROM contacts WHERE + # IN (...)` queries. Postgres ran 7 index scans over the + # same 58k+ row table on every 2s status refresh; collapse into a + # single scan that returns every needed count via FILTER clauses. + %{rows: [[total, terrain, hrrr, weather, iemre, radar, all_done]]} = + Repo.query!(""" + SELECT + COUNT(*) FILTER (WHERE pos1 IS NOT NULL), + COUNT(*) FILTER (WHERE pos1 IS NOT NULL + AND terrain_status::text IN ('pending','queued','processing','failed')), + COUNT(*) FILTER (WHERE pos1 IS NOT NULL + AND hrrr_status::text IN ('pending','queued','processing','failed')), + COUNT(*) FILTER (WHERE pos1 IS NOT NULL + AND weather_status::text IN ('pending','queued','processing','failed')), + COUNT(*) FILTER (WHERE pos1 IS NOT NULL + AND iemre_status::text IN ('pending','queued','processing','failed')), + COUNT(*) FILTER (WHERE pos1 IS NOT NULL + AND radar_status::text IN ('pending','queued','processing','failed')), + COUNT(*) FILTER (WHERE pos1 IS NOT NULL + AND hrrr_status::text IN ('complete','unavailable') + AND weather_status::text IN ('complete','unavailable') + AND terrain_status::text IN ('complete','unavailable') + AND iemre_status::text IN ('complete','unavailable')) + FROM contacts + """) narr_candidates = count_narr_candidates() narr_done = count_narr_done() - all_done = count_all_done(done) - %{ terrain: terrain, hrrr: hrrr, @@ -159,29 +173,6 @@ defmodule MicrowavepropWeb.StatusLive do done end - defp count_incomplete(status_field, statuses) do - Repo.one( - from(c in Contact, - where: field(c, ^status_field) in ^statuses and not is_nil(c.pos1), - select: count() - ) - ) - end - - defp count_all_done(done) do - Repo.one( - from(c in Contact, - where: - not is_nil(c.pos1) and - c.hrrr_status in ^done and - c.weather_status in ^done and - c.terrain_status in ^done and - c.iemre_status in ^done, - select: count() - ) - ) - end - defp fetch_grid_tasks_stats do Cache.fetch_or_store({__MODULE__, :grid_tasks_stats}, 2_000, fn -> fetch_grid_tasks_stats_raw() end) end