Skip to content

Commit

Permalink
feat: cache stops to routes for use in alerts cache filter expansion (#…
Browse files Browse the repository at this point in the history
…2132)

* feat: Cache stop to route relationships

Adds a Nebulex cache for stops -> routes to weaken the dependency on the
V3 API when querying the Alerts cache.

* feat: use stop to route cache in alerts filter expansion
  • Loading branch information
sloanelybutsurely authored Aug 16, 2024
1 parent bb01c30 commit 9e564bc
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 15 deletions.
2 changes: 2 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,8 @@ config :screens, Screens.ScreenApiResponseCache,
gc_interval: :timer.hours(1),
allocated_memory: 250_000_000

config :screens, Screens.Stops.StopsToRoutes, adapter: Nebulex.Adapters.Local

# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"
4 changes: 3 additions & 1 deletion config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ config :screens,
blue_bikes_station_information_url: [:no_api_requests_allowed_during_testing],
blue_bikes_station_status_url: [:no_api_requests_allowed_during_testing],
blue_bikes_api_client: Screens.BlueBikes.FakeClient,
alerts_cache_filter_route_mod: Screens.Routes.Route.Mock,
stops_to_routes_route_mod: Screens.Routes.Route.Mock,
dup_headsign_replacements: %{
"Test 1" => "T1"
},
Expand Down Expand Up @@ -161,3 +161,5 @@ config :screens, :screens_by_alert,
screens_by_alert_ttl_seconds: 2,
screens_last_updated_ttl_seconds: 2,
screens_ttl_seconds: 1

config :screens, Screens.Stops.StopsToRoutes, adapter: Nebulex.Adapters.Nil
16 changes: 3 additions & 13 deletions lib/screens/alerts/cache/filter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@ defmodule Screens.Alerts.Cache.Filter do
@moduledoc """
Logic to apply filters to a list of `Screens.Alerts.Alert` structs.
"""

@route_mod Application.compile_env(
:screens,
:alerts_cache_filter_route_mod,
Screens.Routes.Route
)
alias Screens.Stops.StopsToRoutes

@default_activities ~w[BOARD EXIT RIDE]

Expand Down Expand Up @@ -92,15 +87,10 @@ defmodule Screens.Alerts.Cache.Filter do
end

defp build_matcher({:stops, values}, acc) when is_list(values) do
routes =
values
|> Enum.flat_map(fn stop_id ->
{:ok, routes} = @route_mod.serving_stop(stop_id)
routes
end)
route_ids = StopsToRoutes.stops_to_routes(values)

route_matchers =
for %{id: route_id} <- routes,
for route_id <- route_ids,
stop_id <- [nil | values] do
%{route: route_id, stop: stop_id}
end
Expand Down
3 changes: 2 additions & 1 deletion lib/screens/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ defmodule Screens.Application do
Screens.OlCrowding.DynamicSupervisor,
{Screens.OlCrowding.Agent, %{}},
{Screens.ScreenApiResponseCache, []},
Screens.Streams.Alerts
Screens.Streams.Alerts,
Screens.Stops.StopsToRoutes
]

# See https://hexdocs.pm/elixir/Supervisor.html
Expand Down
54 changes: 54 additions & 0 deletions lib/screens/stops/stops_to_routes.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
defmodule Screens.Stops.StopsToRoutes do
@moduledoc """
Cache of stop ids to route ids. Information for stop ids missing from the
cache is fetched automatically from the V3 API when requested.
"""
use Nebulex.Cache,
otp_app: :screens,
adapter: Application.compile_env(:screens, [__MODULE__, :adapter])

@route_mod Application.compile_env(:screens, :stops_to_routes_route_mod, Screens.Routes.Route)

@base_ttl :timer.hours(1)

@spec stops_to_routes([stop_id :: String.t()]) :: [route_id :: String.t()]
def stops_to_routes(stop_ids) do
from_cache = get_all(stop_ids)
missing_stop_ids = stop_ids -- Map.keys(from_cache)

from_api =
if Enum.empty?(missing_stop_ids) do
%{}
else
from_api =
for stop_id <- missing_stop_ids, into: %{} do
{:ok, routes} = @route_mod.serving_stop(stop_id)
route_ids = Enum.map(routes, & &1.id)

{stop_id, route_ids}
end

put_all(from_api, ttl: ttl())

from_api
end

[from_cache, from_api]
|> Enum.map(&ungroup_values/1)
|> Enum.concat()
|> Enum.uniq()
end

defp ungroup_values(map) do
map
|> Map.values()
|> List.flatten()
|> Enum.uniq()
end

# Set random TTLs from 1hr to 1.5hrs to alleviate the thundering herd problem
defp ttl do
additional_minutes = :rand.uniform(30)
@base_ttl + :timer.minutes(additional_minutes)
end
end

0 comments on commit 9e564bc

Please sign in to comment.