From 2973fe978b69868c74a31f854a78c58ef64dad41 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Fri, 10 Apr 2026 15:58:38 -0500 Subject: [PATCH] Download HRRR ranges sequentially to prevent memory accumulation The parallel download was holding all ~530MB of range responses in memory before writing to disk. Now each range is fetched and written one at a time, so only one chunk is in memory at a time. --- lib/microwaveprop/weather/hrrr_client.ex | 51 ++++++++---------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/lib/microwaveprop/weather/hrrr_client.ex b/lib/microwaveprop/weather/hrrr_client.ex index 5ef9922d..6962e184 100644 --- a/lib/microwaveprop/weather/hrrr_client.ex +++ b/lib/microwaveprop/weather/hrrr_client.ex @@ -347,46 +347,29 @@ defmodule Microwaveprop.Weather.HrrrClient do Returns `:ok` or `{:error, reason}`. """ - 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 - merged = merge_ranges(ranges) + merged = merge_ranges(ranges) |> Enum.sort_by(&elem(&1, 0)) - results = - merged - |> Task.async_stream( - fn {start, stop} -> - case Req.get(url, [{:headers, [{"Range", "bytes=#{start}-#{stop}"}]} | req_options()]) do - {:ok, %{status: 206, body: body}} -> {:ok, start, body} - {:ok, %{status: status}} -> {:error, "HRRR grib HTTP #{status}"} - {:error, reason} -> {:error, reason} - end - end, - max_concurrency: 4, - timeout: 120_000 - ) - |> Enum.map(fn - {:ok, result} -> result - {:exit, reason} -> {:error, reason} - end) + file = File.open!(dest_path, [:write, :binary]) - case Enum.find(results, &match?({:error, _}, &1)) do - {:error, reason} -> - {:error, reason} - - nil -> - sorted = Enum.sort_by(results, fn {:ok, offset, _} -> offset end) - file = File.open!(dest_path, [:write, :binary]) - - try do - Enum.each(sorted, fn {:ok, _offset, body} -> + try do + Enum.reduce_while(merged, :ok, fn {start, stop}, :ok -> + case Req.get(url, [{:headers, [{"Range", "bytes=#{start}-#{stop}"}]} | req_options()]) do + {:ok, %{status: 206, body: body}} -> IO.binwrite(file, body) - end) - after - File.close(file) - end + {:cont, :ok} - :ok + {:ok, %{status: status}} -> + {:halt, {:error, "HRRR grib HTTP #{status}"}} + + {:error, reason} -> + {:halt, {:error, reason}} + end + end) + after + File.close(file) end end