Reduce memory pressure: Stream large collections, GC between phases
- Stream profile storage and score upsert instead of materializing full 20k+ item lists (propagation_grid_worker, propagation.ex) - GC between forecast hours and store/compute phases to reclaim ~400 MB of grid data between steps - Single-pass field extraction in scorer.ex path_integrated_conditions instead of 6 separate Enum traversals - Eliminate intermediate merged map in fetch_grid by combining merge + profile build into one pipe - Fix UUID bug: bingenerate → generate in native grid worker (same issue previously fixed in nexrad_worker)
This commit is contained in:
parent
2c9613ccc3
commit
33fae7b7c9
5 changed files with 47 additions and 39 deletions
|
|
@ -106,26 +106,24 @@ defmodule Microwaveprop.Propagation do
|
||||||
def upsert_scores(scores, opts \\ []) do
|
def upsert_scores(scores, opts \\ []) do
|
||||||
now = DateTime.truncate(DateTime.utc_now(), :second)
|
now = DateTime.truncate(DateTime.utc_now(), :second)
|
||||||
|
|
||||||
entries =
|
|
||||||
Enum.map(scores, fn s ->
|
|
||||||
%{
|
|
||||||
id: Ecto.UUID.generate(),
|
|
||||||
lat: s.lat,
|
|
||||||
lon: s.lon,
|
|
||||||
valid_time: s.valid_time,
|
|
||||||
band_mhz: s.band_mhz,
|
|
||||||
score: s.score,
|
|
||||||
factors: s.factors,
|
|
||||||
inserted_at: now,
|
|
||||||
updated_at: now
|
|
||||||
}
|
|
||||||
end)
|
|
||||||
|
|
||||||
result =
|
result =
|
||||||
Repo.transaction(
|
Repo.transaction(
|
||||||
fn ->
|
fn ->
|
||||||
entries
|
scores
|
||||||
|> Enum.chunk_every(500)
|
|> Stream.map(fn s ->
|
||||||
|
%{
|
||||||
|
id: Ecto.UUID.generate(),
|
||||||
|
lat: s.lat,
|
||||||
|
lon: s.lon,
|
||||||
|
valid_time: s.valid_time,
|
||||||
|
band_mhz: s.band_mhz,
|
||||||
|
score: s.score,
|
||||||
|
factors: s.factors,
|
||||||
|
inserted_at: now,
|
||||||
|
updated_at: now
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|> Stream.chunk_every(500)
|
||||||
|> Enum.reduce(0, fn chunk, acc ->
|
|> Enum.reduce(0, fn chunk, acc ->
|
||||||
{count, _} =
|
{count, _} =
|
||||||
Repo.insert_all(GridScore, chunk,
|
Repo.insert_all(GridScore, chunk,
|
||||||
|
|
|
||||||
|
|
@ -388,8 +388,18 @@ defmodule Microwaveprop.Propagation.Scorer do
|
||||||
def path_integrated_conditions(profiles, contact) do
|
def path_integrated_conditions(profiles, contact) do
|
||||||
lon = Kernel.||(contact.pos1["lon"] || contact.pos1["lng"], -97.0)
|
lon = Kernel.||(contact.pos1["lon"] || contact.pos1["lng"], -97.0)
|
||||||
|
|
||||||
temps = profiles |> Enum.map(& &1.surface_temp_c) |> Enum.reject(&is_nil/1)
|
# Single-pass extraction of all fields to avoid 6 separate Enum traversals
|
||||||
dewpoints = profiles |> Enum.map(& &1.surface_dewpoint_c) |> Enum.reject(&is_nil/1)
|
{temps, dewpoints, pressures, gradients, bl_depths, pwats} =
|
||||||
|
Enum.reduce(profiles, {[], [], [], [], [], []}, fn p, {t, d, pr, g, b, pw} ->
|
||||||
|
{
|
||||||
|
if(is_nil(p.surface_temp_c), do: t, else: [p.surface_temp_c | t]),
|
||||||
|
if(is_nil(p.surface_dewpoint_c), do: d, else: [p.surface_dewpoint_c | d]),
|
||||||
|
if(is_nil(p.surface_pressure_mb), do: pr, else: [p.surface_pressure_mb | pr]),
|
||||||
|
if(is_nil(p.min_refractivity_gradient), do: g, else: [p.min_refractivity_gradient | g]),
|
||||||
|
if(is_nil(p.hpbl_m), do: b, else: [p.hpbl_m | b]),
|
||||||
|
if(is_nil(p.pwat_mm), do: pw, else: [p.pwat_mm | pw])
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
if temps == [] or dewpoints == [] do
|
if temps == [] or dewpoints == [] do
|
||||||
nil
|
nil
|
||||||
|
|
@ -399,11 +409,6 @@ defmodule Microwaveprop.Propagation.Scorer do
|
||||||
avg_temp_f = c_to_f(avg_temp_c)
|
avg_temp_f = c_to_f(avg_temp_c)
|
||||||
avg_dewpoint_f = c_to_f(avg_dewpoint_c)
|
avg_dewpoint_f = c_to_f(avg_dewpoint_c)
|
||||||
|
|
||||||
pressures = profiles |> Enum.map(& &1.surface_pressure_mb) |> Enum.reject(&is_nil/1)
|
|
||||||
gradients = profiles |> Enum.map(& &1.min_refractivity_gradient) |> Enum.reject(&is_nil/1)
|
|
||||||
bl_depths = profiles |> Enum.map(& &1.hpbl_m) |> Enum.reject(&is_nil/1)
|
|
||||||
pwats = profiles |> Enum.map(& &1.pwat_mm) |> Enum.reject(&is_nil/1)
|
|
||||||
|
|
||||||
%{
|
%{
|
||||||
abs_humidity: absolute_humidity(avg_temp_c, avg_dewpoint_c),
|
abs_humidity: absolute_humidity(avg_temp_c, avg_dewpoint_c),
|
||||||
temp_f: avg_temp_f,
|
temp_f: avg_temp_f,
|
||||||
|
|
|
||||||
|
|
@ -63,10 +63,12 @@ defmodule Microwaveprop.Weather.HrrrClient do
|
||||||
%{}
|
%{}
|
||||||
end
|
end
|
||||||
|
|
||||||
merged = merge_grid_data(sfc_grid, prs_grid)
|
# Merge and build profiles in one pass to avoid a full intermediate map copy.
|
||||||
|
# Use Map.merge to include any pressure-only points, then map directly to profiles.
|
||||||
profiles =
|
profiles =
|
||||||
Map.new(merged, fn {point, data} ->
|
sfc_grid
|
||||||
|
|> Map.merge(prs_grid, fn _point, sfc_data, prs_data -> Map.merge(sfc_data, prs_data) end)
|
||||||
|
|> Map.new(fn {point, data} ->
|
||||||
profile = build_profile(data)
|
profile = build_profile(data)
|
||||||
{point, Map.put(profile, :run_time, hour_dt)}
|
{point, Map.put(profile, :run_time, hour_dt)}
|
||||||
end)
|
end)
|
||||||
|
|
@ -350,7 +352,7 @@ defmodule Microwaveprop.Weather.HrrrClient do
|
||||||
def download_grib_ranges_to_file(_url, [], _dest_path), do: :ok
|
def download_grib_ranges_to_file(_url, [], _dest_path), do: :ok
|
||||||
|
|
||||||
def download_grib_ranges_to_file(url, ranges, dest_path) do
|
def download_grib_ranges_to_file(url, ranges, dest_path) do
|
||||||
merged = merge_ranges(ranges) |> Enum.sort_by(&elem(&1, 0))
|
merged = ranges |> merge_ranges() |> Enum.sort_by(&elem(&1, 0))
|
||||||
|
|
||||||
file = File.open!(dest_path, [:write, :binary])
|
file = File.open!(dest_path, [:write, :binary])
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ defmodule Microwaveprop.Workers.HrrrNativeGridWorker do
|
||||||
|
|
||||||
[
|
[
|
||||||
Map.merge(profile, %{
|
Map.merge(profile, %{
|
||||||
id: Ecto.UUID.bingenerate(),
|
id: Ecto.UUID.generate(),
|
||||||
valid_time: valid_time,
|
valid_time: valid_time,
|
||||||
run_time: valid_time,
|
run_time: valid_time,
|
||||||
lat: lat,
|
lat: lat,
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,8 @@ defmodule Microwaveprop.Workers.PropagationGridWorker do
|
||||||
for fh <- 0..@max_forecast_hour do
|
for fh <- 0..@max_forecast_hour do
|
||||||
valid_time = DateTime.add(run_time, fh * 3600, :second)
|
valid_time = DateTime.add(run_time, fh * 3600, :second)
|
||||||
result = process_forecast_hour(points, run_time, fh, valid_time)
|
result = process_forecast_hour(points, run_time, fh, valid_time)
|
||||||
|
# Reclaim grid_data/profiles from this forecast hour before starting the next
|
||||||
|
:erlang.garbage_collect()
|
||||||
|
|
||||||
case result do
|
case result do
|
||||||
:ok -> valid_time
|
:ok -> valid_time
|
||||||
|
|
@ -74,6 +76,7 @@ defmodule Microwaveprop.Workers.PropagationGridWorker do
|
||||||
end) do
|
end) do
|
||||||
{:ok, grid_data} ->
|
{:ok, grid_data} ->
|
||||||
store_hrrr_profiles(grid_data, valid_time)
|
store_hrrr_profiles(grid_data, valid_time)
|
||||||
|
:erlang.garbage_collect()
|
||||||
|
|
||||||
Phoenix.PubSub.broadcast(
|
Phoenix.PubSub.broadcast(
|
||||||
Microwaveprop.PubSub,
|
Microwaveprop.PubSub,
|
||||||
|
|
@ -111,8 +114,9 @@ defmodule Microwaveprop.Workers.PropagationGridWorker do
|
||||||
defp format_duration(ms), do: "#{Float.round(ms / 1000, 1)}s"
|
defp format_duration(ms), do: "#{Float.round(ms / 1000, 1)}s"
|
||||||
|
|
||||||
defp store_hrrr_profiles(grid_data, valid_time) do
|
defp store_hrrr_profiles(grid_data, valid_time) do
|
||||||
stored =
|
count =
|
||||||
Enum.flat_map(grid_data, fn {{lat, lon}, profile} ->
|
grid_data
|
||||||
|
|> Stream.flat_map(fn {{lat, lon}, profile} ->
|
||||||
temp = profile.surface_temp_c
|
temp = profile.surface_temp_c
|
||||||
|
|
||||||
if temp && temp > -80 && temp < 60 do
|
if temp && temp > -80 && temp < 60 do
|
||||||
|
|
@ -150,14 +154,13 @@ defmodule Microwaveprop.Workers.PropagationGridWorker do
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|> Stream.chunk_every(500)
|
||||||
|
|> Enum.reduce(0, fn chunk, acc ->
|
||||||
|
Weather.upsert_hrrr_profiles_batch(chunk)
|
||||||
|
acc + length(chunk)
|
||||||
|
end)
|
||||||
|
|
||||||
stored
|
Logger.info("PropagationGrid: stored #{count} HRRR profiles")
|
||||||
|> Enum.chunk_every(500)
|
|
||||||
|> Enum.each(fn chunk ->
|
|
||||||
Weather.upsert_hrrr_profiles_batch(chunk)
|
|
||||||
end)
|
|
||||||
|
|
||||||
Logger.info("PropagationGrid: stored #{length(stored)} HRRR profiles")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp compute_scores(grid_data, valid_time) do
|
defp compute_scores(grid_data, valid_time) do
|
||||||
|
|
@ -178,7 +181,7 @@ defmodule Microwaveprop.Workers.PropagationGridWorker do
|
||||||
max_concurrency: System.schedulers_online() * 2,
|
max_concurrency: System.schedulers_online() * 2,
|
||||||
timeout: 30_000
|
timeout: 30_000
|
||||||
)
|
)
|
||||||
|> Enum.flat_map(fn
|
|> Stream.flat_map(fn
|
||||||
{:ok, results} -> results
|
{:ok, results} -> results
|
||||||
{:exit, _reason} -> []
|
{:exit, _reason} -> []
|
||||||
end)
|
end)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue