perf: fix medium-severity N+1 and inefficiency patterns

- pending_edit_for_user: add preload for [:user, :contact] to avoid
  lazy-loaded N+1 when templates access those associations
- ScoresController.encode_binary: combine 3 Enum.map passes into a
  single Enum.reduce, halving list traversals for score binary encoding
- find_duplicate_contact: push station-pair/grid matching into SQL
  WHERE clause so dedup uses the DB index instead of loading all
  matching rows + Enum.find in Elixir
This commit is contained in:
Graham McIntire 2026-06-01 15:32:36 -05:00
parent 473c2ab0ec
commit 9cce257db7
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
2 changed files with 13 additions and 16 deletions

View file

@ -757,25 +757,18 @@ defmodule Microwaveprop.Radio do
min_ts = DateTime.add(ts, -@dedup_window_seconds, :second)
max_ts = DateTime.add(ts, @dedup_window_seconds, :second)
expected_pair = Enum.sort([{s1, g1}, {s2, g2}])
from(c in Contact,
where:
c.band == ^band_decimal and
c.qso_timestamp >= ^min_ts and
c.qso_timestamp <= ^max_ts and
c.flagged_invalid == false
c.flagged_invalid == false and
fragment("LEAST(UPPER(station1), UPPER(station2)) = ?", ^min(s1, s2)) and
fragment("GREATEST(UPPER(station1), UPPER(station2)) = ?", ^max(s1, s2)) and
fragment("LEAST(UPPER(grid1), UPPER(grid2)) = ?", ^min(g1, g2)) and
fragment("GREATEST(UPPER(grid1), UPPER(grid2)) = ?", ^max(g1, g2))
)
|> Repo.all()
|> Enum.find(fn c ->
pair =
Enum.sort([
{String.upcase(c.station1), String.upcase(c.grid1)},
{String.upcase(c.station2), String.upcase(c.grid2)}
])
pair == expected_pair
end)
|> Repo.one()
end
@refinement_fields ~w(grid1 grid2 mode)a
@ -1101,6 +1094,7 @@ defmodule Microwaveprop.Radio do
def pending_edit_for_user(contact_id, user_id) do
ContactEdit
|> where([e], e.contact_id == ^contact_id and e.user_id == ^user_id and e.status == :pending)
|> preload([:user, :contact])
|> Repo.one()
end

View file

@ -103,9 +103,12 @@ defmodule MicrowavepropWeb.ScoresController do
header = <<"PSCR", 1::8, 0::8, 0::8, 0::8, cell_count::little-32>>
lats = scores |> Enum.map(&<<&1.lat * 1.0::little-float-32>>) |> IO.iodata_to_binary()
lons = scores |> Enum.map(&<<&1.lon * 1.0::little-float-32>>) |> IO.iodata_to_binary()
score_bytes = scores |> Enum.map(&<<clamp_score(&1.score)::8>>) |> IO.iodata_to_binary()
{lats, lons, score_bytes} =
Enum.reduce(scores, {[], [], []}, fn s, {lats_acc, lons_acc, scs_acc} ->
{[<<s.lat * 1.0::little-float-32>> | lats_acc],
[<<s.lon * 1.0::little-float-32>> | lons_acc],
[<<clamp_score(s.score)::8>> | scs_acc]}
end)
<<header::binary, lats::binary, lons::binary, score_bytes::binary>>
end