fix(rover): bound Overpass with hard 8s deadline + 6h ETS cache by bbox
Prod logs showed road_proximity took 186 s on a single Calculate. Req's receive_timeout only catches TCP idle, not slow streaming, so a trickling Overpass response could not be cancelled. Wrap the call in a Task.yield/shutdown deadline and cache successful results by 0.05°-rounded bbox so adjacent Calculates skip Overpass entirely.
This commit is contained in:
parent
6a79458ea9
commit
af2ab3c969
1 changed files with 44 additions and 11 deletions
|
|
@ -12,12 +12,16 @@ defmodule Microwaveprop.Rover.RoadProximity do
|
||||||
`:unavailable` and the scorer falls back to ignoring road proximity.
|
`:unavailable` and the scorer falls back to ignoring road proximity.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
alias Microwaveprop.Cache
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@overpass_url "https://overpass-api.de/api/interpreter"
|
@overpass_url "https://overpass-api.de/api/interpreter"
|
||||||
@road_types ~w(motorway trunk primary secondary tertiary unclassified residential)
|
@road_types ~w(motorway trunk primary secondary tertiary unclassified residential)
|
||||||
@overpass_query_timeout_s 10
|
@overpass_query_timeout_s 6
|
||||||
@overpass_receive_timeout_ms 12_000
|
@overpass_receive_timeout_ms 8_000
|
||||||
|
@overpass_hard_deadline_ms 8_000
|
||||||
|
@cache_ttl_ms 6 * 60 * 60 * 1_000
|
||||||
|
|
||||||
@type latlon :: {float(), float()}
|
@type latlon :: {float(), float()}
|
||||||
@type segment :: {latlon(), latlon()}
|
@type segment :: {latlon(), latlon()}
|
||||||
|
|
@ -42,7 +46,21 @@ defmodule Microwaveprop.Rover.RoadProximity do
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec fetch_segments(bbox()) :: {:ok, [segment()]} | {:error, term()}
|
@spec fetch_segments(bbox()) :: {:ok, [segment()]} | {:error, term()}
|
||||||
def fetch_segments(%{"south" => s, "west" => w, "north" => n, "east" => e}) do
|
def fetch_segments(%{"south" => _, "west" => _, "north" => _, "east" => _} = bbox) do
|
||||||
|
key = {:rover_road_segments, cache_key(bbox)}
|
||||||
|
|
||||||
|
case Cache.fetch_or_store(key, @cache_ttl_ms, fn -> do_fetch_segments(bbox) end) do
|
||||||
|
{:ok, _segments} = ok ->
|
||||||
|
ok
|
||||||
|
|
||||||
|
{:error, _} = err ->
|
||||||
|
# Don't poison the cache with errors — subsequent Calculates retry.
|
||||||
|
Cache.invalidate(key)
|
||||||
|
err
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_fetch_segments(%{"south" => s, "west" => w, "north" => n, "east" => e}) do
|
||||||
types = Enum.join(@road_types, "|")
|
types = Enum.join(@road_types, "|")
|
||||||
|
|
||||||
query = """
|
query = """
|
||||||
|
|
@ -51,22 +69,37 @@ defmodule Microwaveprop.Rover.RoadProximity do
|
||||||
out geom;
|
out geom;
|
||||||
"""
|
"""
|
||||||
|
|
||||||
case Req.post(@overpass_url,
|
task =
|
||||||
form: [data: query],
|
Task.async(fn ->
|
||||||
receive_timeout: @overpass_receive_timeout_ms,
|
Req.post(@overpass_url,
|
||||||
retry: false
|
form: [data: query],
|
||||||
) do
|
receive_timeout: @overpass_receive_timeout_ms,
|
||||||
{:ok, %{status: 200, body: %{"elements" => elements}}} ->
|
retry: false
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
|
case Task.yield(task, @overpass_hard_deadline_ms) || Task.shutdown(task, :brutal_kill) do
|
||||||
|
{:ok, {:ok, %{status: 200, body: %{"elements" => elements}}}} ->
|
||||||
{:ok, segments_from_elements(elements)}
|
{:ok, segments_from_elements(elements)}
|
||||||
|
|
||||||
{:ok, %{status: status}} ->
|
{:ok, {:ok, %{status: status}}} ->
|
||||||
{:error, {:http, status}}
|
{:error, {:http, status}}
|
||||||
|
|
||||||
{:error, reason} ->
|
{:ok, {:error, reason}} ->
|
||||||
{:error, reason}
|
{:error, reason}
|
||||||
|
|
||||||
|
nil ->
|
||||||
|
{:error, :overpass_deadline_exceeded}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Round to 0.05° (~5 km) so adjacent Calculate calls share cache hits.
|
||||||
|
defp cache_key(%{"south" => s, "west" => w, "north" => n, "east" => e}) do
|
||||||
|
{round_to(s, 0.05), round_to(w, 0.05), round_to(n, 0.05), round_to(e, 0.05)}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp round_to(v, step), do: Float.round(v / step) * step
|
||||||
|
|
||||||
defp segments_from_elements(elements) do
|
defp segments_from_elements(elements) do
|
||||||
Enum.flat_map(elements, fn
|
Enum.flat_map(elements, fn
|
||||||
%{"type" => "way", "geometry" => geom} when is_list(geom) and length(geom) >= 2 ->
|
%{"type" => "way", "geometry" => geom} when is_list(geom) and length(geom) >= 2 ->
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue