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.
|
||||
"""
|
||||
|
||||
alias Microwaveprop.Cache
|
||||
|
||||
require Logger
|
||||
|
||||
@overpass_url "https://overpass-api.de/api/interpreter"
|
||||
@road_types ~w(motorway trunk primary secondary tertiary unclassified residential)
|
||||
@overpass_query_timeout_s 10
|
||||
@overpass_receive_timeout_ms 12_000
|
||||
@overpass_query_timeout_s 6
|
||||
@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 segment :: {latlon(), latlon()}
|
||||
|
|
@ -42,7 +46,21 @@ defmodule Microwaveprop.Rover.RoadProximity do
|
|||
end
|
||||
|
||||
@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, "|")
|
||||
|
||||
query = """
|
||||
|
|
@ -51,22 +69,37 @@ defmodule Microwaveprop.Rover.RoadProximity do
|
|||
out geom;
|
||||
"""
|
||||
|
||||
case Req.post(@overpass_url,
|
||||
form: [data: query],
|
||||
receive_timeout: @overpass_receive_timeout_ms,
|
||||
retry: false
|
||||
) do
|
||||
{:ok, %{status: 200, body: %{"elements" => elements}}} ->
|
||||
task =
|
||||
Task.async(fn ->
|
||||
Req.post(@overpass_url,
|
||||
form: [data: query],
|
||||
receive_timeout: @overpass_receive_timeout_ms,
|
||||
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, %{status: status}} ->
|
||||
{:ok, {:ok, %{status: status}}} ->
|
||||
{:error, {:http, status}}
|
||||
|
||||
{:error, reason} ->
|
||||
{:ok, {:error, reason}} ->
|
||||
{:error, reason}
|
||||
|
||||
nil ->
|
||||
{:error, :overpass_deadline_exceeded}
|
||||
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
|
||||
Enum.flat_map(elements, fn
|
||||
%{"type" => "way", "geometry" => geom} when is_list(geom) and length(geom) >= 2 ->
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue