perf(rover-planning): create_mission returns immediately, batch path / Oban inserts
Two stacked wins for the form-submit hot path: 1) create_mission now hands path-matrix population to the same async reconcile worker update_mission uses, so the request returns right after the Mission row insert. For a mission with hundreds of (rover x station x band) tuples this used to block the LiveView for many seconds. 2) Inside reconcile/enqueue_paths_for, the per-pair Multi.insert loop is replaced with one Repo.insert_all for the Path rows and one Oban.insert_all for the worker jobs. N round-trips collapse to two regardless of pair count.
This commit is contained in:
parent
a4f0e171e8
commit
3060eddfb2
1 changed files with 36 additions and 26 deletions
|
|
@ -13,7 +13,6 @@ defmodule Microwaveprop.RoverPlanning do
|
|||
|
||||
import Ecto.Query
|
||||
|
||||
alias Ecto.Multi
|
||||
alias Microwaveprop.Accounts.User
|
||||
alias Microwaveprop.Repo
|
||||
alias Microwaveprop.Rover.Location
|
||||
|
|
@ -60,7 +59,13 @@ defmodule Microwaveprop.RoverPlanning do
|
|||
case Repo.insert(changeset) do
|
||||
{:ok, mission} ->
|
||||
mission = Repo.preload(mission, [:user, :stations])
|
||||
:ok = enqueue_paths_for(mission)
|
||||
# Async: a brand-new mission can have hundreds of (rover x
|
||||
# station x band) tuples. Hand the matrix population off to
|
||||
# the reconcile worker so the form-submit request returns
|
||||
# immediately instead of blocking on N inserts + N Oban
|
||||
# enqueues per pair. The worker calls reconcile_mission_paths,
|
||||
# which is identical to enqueue_paths_for for an empty matrix.
|
||||
:ok = enqueue_reconcile(mission)
|
||||
{:ok, mission}
|
||||
|
||||
{:error, _} = error ->
|
||||
|
|
@ -226,43 +231,48 @@ defmodule Microwaveprop.RoverPlanning do
|
|||
defp insert_pending_paths_and_jobs([], _mission), do: :ok
|
||||
|
||||
defp insert_pending_paths_and_jobs(tuples, mission) when is_list(tuples) do
|
||||
{:ok, _} =
|
||||
tuples
|
||||
|> Enum.reduce(Multi.new(), fn {rover_id, station_id, band_mhz}, multi ->
|
||||
attrs = %{
|
||||
# Batch the path-row inserts and the Oban-job inserts into a
|
||||
# single SQL statement each. The previous Multi-of-N-inserts +
|
||||
# N x Oban.insert call took ~1.5s per pair × 900 pairs in the
|
||||
# worst-case mission — `insert_all` collapses both to one
|
||||
# roundtrip per kind regardless of pair count.
|
||||
now = DateTime.truncate(DateTime.utc_now(), :second)
|
||||
|
||||
path_rows =
|
||||
Enum.map(tuples, fn {rover_id, station_id, band_mhz} ->
|
||||
%{
|
||||
id: Ecto.UUID.generate(),
|
||||
mission_id: mission.id,
|
||||
rover_location_id: rover_id,
|
||||
station_id: station_id,
|
||||
band_mhz: band_mhz,
|
||||
status: :pending
|
||||
status: :pending,
|
||||
inserted_at: now,
|
||||
updated_at: now
|
||||
}
|
||||
end)
|
||||
|
||||
Multi.insert(
|
||||
multi,
|
||||
{:path, rover_id, station_id, band_mhz},
|
||||
Path.changeset(%Path{}, attrs),
|
||||
Repo.insert_all(Path, path_rows,
|
||||
on_conflict: :nothing,
|
||||
conflict_target: [:mission_id, :rover_location_id, :station_id, :band_mhz]
|
||||
)
|
||||
end)
|
||||
|> Repo.transaction()
|
||||
|
||||
enqueue_jobs(tuples, mission)
|
||||
:ok
|
||||
end
|
||||
|
||||
defp enqueue_jobs(tuples, mission) do
|
||||
for {rover_id, station_id, band_mhz} <- tuples do
|
||||
%{
|
||||
changesets =
|
||||
Enum.map(tuples, fn {rover_id, station_id, band_mhz} ->
|
||||
RoverPathProfileWorker.new(%{
|
||||
"mission_id" => mission.id,
|
||||
"rover_location_id" => rover_id,
|
||||
"station_id" => station_id,
|
||||
"band_mhz" => band_mhz
|
||||
}
|
||||
|> RoverPathProfileWorker.new()
|
||||
|> Oban.insert()
|
||||
end
|
||||
})
|
||||
end)
|
||||
|
||||
Oban.insert_all(changesets)
|
||||
:ok
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue