defmodule Vela.TTL.Manager do @moduledoc """ Periodically sweeps the cache and removes expired entries. Uses the TTL index table (an ordered_set keyed by {expires_at, key}) to find expired entries in O(expired) time instead of scanning everything. """ use GenServer # ---- Public API ---- def start_link(opts) do name = Keyword.fetch!(opts, :name) GenServer.start_link(__MODULE__, opts, name: manager_name(name)) end def manager_name(cache_name), do: :"vela_ttl_manager_#{cache_name}" # ---- GenServer Callbacks ---- @impl true def init(opts) do config = Keyword.fetch!(opts, :config) # Schedule the first sweep ref = schedule_sweep(config.sweep_interval) state = %{ config: config, sweep_ref: ref } {:ok, state} end @impl true def handle_info(:sweep, state) do now = System.monotonic_time(:millisecond) backend = state.config.backend backend_state = get_backend_state(state.config.name) count = case backend.flush_expired(backend_state, now) do {:ok, count, _new_bs} -> count _ -> 0 end :telemetry.execute( state.config.telemetry_prefix ++ [:ttl_sweep, :stop], %{evicted: count}, %{cache: state.config.name} ) # Schedule the next sweep ref = schedule_sweep(state.config.sweep_interval) {:noreply, %{state | sweep_ref: ref}} end defp schedule_sweep(interval) do Process.send_after(self(), :sweep, interval) end defp get_backend_state(cache_name) do ts = :persistent_term.get({:vela_topology_state, cache_name}) ts.backend_state end end