Packages
phoenix_kit
1.3.1
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/phoenix_kit/cache/registry.ex
defmodule PhoenixKit.Cache.Registry do
@moduledoc """
Registry for managing multiple PhoenixKit.Cache instances.
This module provides centralized management of cache instances, allowing
multiple named caches to run simultaneously with different configurations.
## Features
- Centralized cache instance management
- Support for multiple cache types (settings, roles, modules, etc.)
- Automatic cache startup and supervision
- Health checking and monitoring
## Usage
# Start the registry
{:ok, _} = PhoenixKit.Cache.Registry.start_link()
# Register cache instances
PhoenixKit.Cache.Registry.ensure_started(:settings, warmer: &Settings.load_all/0)
PhoenixKit.Cache.Registry.ensure_started(:user_roles, warmer: &UserRoles.load_all/0)
# List all cache instances
PhoenixKit.Cache.Registry.list_caches()
# Get cache health status
PhoenixKit.Cache.Registry.health_check()
"""
use GenServer
require Logger
@registry_name __MODULE__
defstruct caches: %{},
supervisors: %{}
@doc """
Starts the cache registry.
"""
@spec start_link(keyword()) :: GenServer.on_start()
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: @registry_name)
end
@doc """
Ensures a cache instance is started with the given configuration.
If the cache is already running, this function is a no-op.
If the cache is not running, it will be started automatically.
## Options
Same as PhoenixKit.Cache.start_link/1:
- `:warmer` - Function to warm the cache on startup
- `:ttl` - Time-to-live for cache entries
- `:max_size` - Maximum number of entries
## Examples
PhoenixKit.Cache.Registry.ensure_started(:settings)
PhoenixKit.Cache.Registry.ensure_started(:user_roles, warmer: &load_user_roles/0)
"""
@spec ensure_started(atom(), keyword()) :: :ok | {:error, term()}
def ensure_started(cache_name, opts \\ []) do
GenServer.call(@registry_name, {:ensure_started, cache_name, opts})
end
@doc """
Lists all managed cache instances with their status.
## Examples
PhoenixKit.Cache.Registry.list_caches()
# => %{
# settings: %{status: :running, pid: #PID<0.123.0>, stats: %{hits: 100, misses: 5}},
# user_roles: %{status: :running, pid: #PID<0.124.0>, stats: %{hits: 50, misses: 2}}
# }
"""
@spec list_caches() :: map()
def list_caches do
GenServer.call(@registry_name, :list_caches)
end
@doc """
Stops a cache instance.
## Examples
PhoenixKit.Cache.Registry.stop_cache(:settings)
"""
@spec stop_cache(atom()) :: :ok
def stop_cache(cache_name) do
GenServer.call(@registry_name, {:stop_cache, cache_name})
end
@doc """
Restarts a cache instance with new configuration.
## Examples
PhoenixKit.Cache.Registry.restart_cache(:settings, ttl: 60_000)
"""
@spec restart_cache(atom(), keyword()) :: :ok | {:error, term()}
def restart_cache(cache_name, new_opts \\ []) do
GenServer.call(@registry_name, {:restart_cache, cache_name, new_opts})
end
@doc """
Performs a health check on all managed caches.
Returns a map with each cache's health status and basic statistics.
## Examples
PhoenixKit.Cache.Registry.health_check()
# => %{
# overall_status: :healthy,
# caches: %{
# settings: %{status: :healthy, hit_rate: 0.95, uptime: 3600},
# user_roles: %{status: :healthy, hit_rate: 0.88, uptime: 3600}
# }
# }
"""
@spec health_check() :: map()
def health_check do
GenServer.call(@registry_name, :health_check)
end
@doc """
Gets statistics for a specific cache or all caches.
## Examples
PhoenixKit.Cache.Registry.stats(:settings)
PhoenixKit.Cache.Registry.stats() # All caches
"""
@spec stats(atom() | nil) :: map()
def stats(cache_name \\ nil) do
GenServer.call(@registry_name, {:stats, cache_name})
end
# GenServer Callbacks
@impl GenServer
def init(_opts) do
# Start the dynamic registry for cache processes if not already started
case Registry.start_link(keys: :unique, name: PhoenixKit.Cache.Registry) do
{:ok, _registry_pid} ->
Logger.info("Started PhoenixKit.Cache.Registry")
{:error, {:already_started, _pid}} ->
Logger.info("PhoenixKit.Cache.Registry already started")
{:error, reason} ->
Logger.error("Failed to start PhoenixKit.Cache.Registry: #{inspect(reason)}")
end
{:ok, %__MODULE__{}}
end
@impl GenServer
def handle_call({:ensure_started, cache_name, opts}, _from, state) do
case Map.get(state.caches, cache_name) do
nil ->
# Cache not started, start it
case start_cache(cache_name, opts) do
{:ok, pid} ->
new_caches =
Map.put(state.caches, cache_name, %{
pid: pid,
opts: opts,
started_at: System.monotonic_time(:second)
})
{:reply, :ok, %{state | caches: new_caches}}
{:error, reason} ->
Logger.error("Failed to start cache #{cache_name}: #{inspect(reason)}")
{:reply, {:error, reason}, state}
end
cache_info ->
# Cache already running, check if it's alive
if Process.alive?(cache_info.pid) do
{:reply, :ok, state}
else
# Process died, restart it
case start_cache(cache_name, opts) do
{:ok, pid} ->
new_cache_info = %{
cache_info
| pid: pid,
started_at: System.monotonic_time(:second)
}
new_caches = Map.put(state.caches, cache_name, new_cache_info)
{:reply, :ok, %{state | caches: new_caches}}
{:error, reason} ->
Logger.error("Failed to restart cache #{cache_name}: #{inspect(reason)}")
{:reply, {:error, reason}, state}
end
end
end
end
@impl GenServer
def handle_call(:list_caches, _from, state) do
cache_list =
Enum.reduce(state.caches, %{}, fn {name, info}, acc ->
status = if Process.alive?(info.pid), do: :running, else: :dead
stats =
if status == :running do
PhoenixKit.Cache.stats(name)
else
%{hits: 0, misses: 0, puts: 0, invalidations: 0, hit_rate: 0.0}
end
Map.put(acc, name, %{
status: status,
pid: info.pid,
started_at: info.started_at,
opts: info.opts,
stats: stats
})
end)
{:reply, cache_list, state}
end
@impl GenServer
def handle_call({:stop_cache, cache_name}, _from, state) do
case Map.get(state.caches, cache_name) do
nil ->
{:reply, :ok, state}
cache_info ->
if Process.alive?(cache_info.pid) do
Process.exit(cache_info.pid, :normal)
end
new_caches = Map.delete(state.caches, cache_name)
{:reply, :ok, %{state | caches: new_caches}}
end
end
@impl GenServer
def handle_call({:restart_cache, cache_name, new_opts}, _from, state) do
# Stop the existing cache
case Map.get(state.caches, cache_name) do
nil ->
# Cache not running, just start it
handle_call({:ensure_started, cache_name, new_opts}, nil, state)
cache_info ->
if Process.alive?(cache_info.pid) do
Process.exit(cache_info.pid, :normal)
end
# Start with new options
case start_cache(cache_name, new_opts) do
{:ok, pid} ->
new_cache_info = %{
pid: pid,
opts: new_opts,
started_at: System.monotonic_time(:second)
}
new_caches = Map.put(state.caches, cache_name, new_cache_info)
{:reply, :ok, %{state | caches: new_caches}}
{:error, reason} ->
Logger.error("Failed to restart cache #{cache_name}: #{inspect(reason)}")
{:reply, {:error, reason}, state}
end
end
end
@impl GenServer
def handle_call(:health_check, _from, state) do
{healthy_count, total_count, cache_health} =
Enum.reduce(state.caches, {0, 0, %{}}, fn {name, info}, {healthy, total, acc} ->
is_healthy = Process.alive?(info.pid)
stats =
if is_healthy do
PhoenixKit.Cache.stats(name)
else
%{hits: 0, misses: 0, puts: 0, invalidations: 0, hit_rate: 0.0}
end
uptime = System.monotonic_time(:second) - info.started_at
cache_status = %{
status: if(is_healthy, do: :healthy, else: :unhealthy),
hit_rate: stats.hit_rate,
uptime: uptime,
stats: stats
}
{
if(is_healthy, do: healthy + 1, else: healthy),
total + 1,
Map.put(acc, name, cache_status)
}
end)
overall_status =
cond do
total_count == 0 -> :no_caches
healthy_count == total_count -> :healthy
healthy_count > 0 -> :degraded
true -> :unhealthy
end
result = %{
overall_status: overall_status,
healthy_count: healthy_count,
total_count: total_count,
caches: cache_health
}
{:reply, result, state}
end
@impl GenServer
def handle_call({:stats, cache_name}, _from, state) do
result =
case cache_name do
nil ->
# Return stats for all caches
Enum.reduce(state.caches, %{}, fn {name, info}, acc ->
if Process.alive?(info.pid) do
Map.put(acc, name, PhoenixKit.Cache.stats(name))
else
Map.put(acc, name, %{hits: 0, misses: 0, puts: 0, invalidations: 0, hit_rate: 0.0})
end
end)
name ->
# Return stats for specific cache
case Map.get(state.caches, name) do
nil ->
%{error: :cache_not_found}
info ->
if Process.alive?(info.pid) do
PhoenixKit.Cache.stats(name)
else
%{error: :cache_not_running}
end
end
end
{:reply, result, state}
end
# Private Functions
defp start_cache(cache_name, opts) do
full_opts = Keyword.put(opts, :name, cache_name)
PhoenixKit.Cache.start_link(full_opts)
end
end