Redix expects host to be a string, not a charlist. The to_charlist/1 conversion was causing FunctionClauseError in Redix.StartOptions.__validate_host__/1. This matches how Phoenix PubSub Redis and Exq are configured, which both use the string host directly without conversion.
118 lines
3.7 KiB
Elixir
118 lines
3.7 KiB
Elixir
defmodule Towerops.RedisHealthCheck do
|
|
@moduledoc """
|
|
Health check for Redis/Valkey connectivity.
|
|
|
|
Performs a simple PING command to verify Redis is reachable and responding.
|
|
Used by the health check endpoint to determine overall application health.
|
|
"""
|
|
|
|
require Logger
|
|
|
|
@doc """
|
|
Waits for Redis to become available with exponential backoff retries.
|
|
|
|
## Parameters
|
|
- `redis_config` - Keyword list with Redis connection config (host, port, password)
|
|
- `opts` - Options keyword list:
|
|
- `:max_attempts` - Maximum number of connection attempts (default: 10)
|
|
- `:backoff` - Initial backoff time in milliseconds (default: 500)
|
|
- `:timeout` - Connection timeout in milliseconds (default: 2000)
|
|
|
|
## Returns
|
|
- `:ok` if Redis becomes available
|
|
- `{:error, :max_attempts_exceeded}` if all attempts fail
|
|
"""
|
|
@spec wait_for_redis(keyword(), keyword()) :: :ok | {:error, :max_attempts_exceeded}
|
|
def wait_for_redis(redis_config, opts \\ []) do
|
|
max_attempts = Keyword.get(opts, :max_attempts, 10)
|
|
backoff = Keyword.get(opts, :backoff, 500)
|
|
timeout = Keyword.get(opts, :timeout, 2_000)
|
|
|
|
do_wait_for_redis(redis_config, timeout, max_attempts, backoff, 1)
|
|
end
|
|
|
|
defp do_wait_for_redis(_redis_config, _timeout, max_attempts, _backoff, attempt) when attempt > max_attempts do
|
|
Logger.warning("Redis connection failed after #{max_attempts} attempts")
|
|
{:error, :max_attempts_exceeded}
|
|
end
|
|
|
|
defp do_wait_for_redis(redis_config, timeout, max_attempts, backoff, attempt) do
|
|
case check_health(redis_config, timeout) do
|
|
:ok ->
|
|
if attempt > 1 do
|
|
Logger.info("Redis connection established after #{attempt} attempts")
|
|
end
|
|
|
|
:ok
|
|
|
|
{:error, reason} ->
|
|
wait_time = round(backoff * :math.pow(2, attempt - 1))
|
|
|
|
Logger.debug(
|
|
"Redis connection attempt #{attempt}/#{max_attempts} failed: #{inspect(reason)}, " <>
|
|
"retrying in #{wait_time}ms"
|
|
)
|
|
|
|
Process.sleep(wait_time)
|
|
do_wait_for_redis(redis_config, timeout, max_attempts, backoff, attempt + 1)
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Checks Redis health by attempting a PING command with a timeout.
|
|
|
|
## Parameters
|
|
- `redis_config` - Keyword list with Redis connection config (host, port, password)
|
|
- `timeout_ms` - Maximum time to wait for response in milliseconds
|
|
|
|
## Returns
|
|
- `:ok` if Redis responds to PONG
|
|
- `{:error, reason}` if connection fails or times out
|
|
"""
|
|
@spec check_health(keyword(), pos_integer()) :: :ok | {:error, term()}
|
|
def check_health(redis_config, timeout_ms \\ 2_000) do
|
|
host = Keyword.get(redis_config, :host, "localhost")
|
|
port = Keyword.get(redis_config, :port, 6379)
|
|
password = Keyword.get(redis_config, :password)
|
|
|
|
# Build connection options
|
|
conn_opts = [
|
|
host: host,
|
|
port: port,
|
|
timeout: timeout_ms,
|
|
socket_opts: [keepalive: true]
|
|
]
|
|
|
|
conn_opts =
|
|
if password && password != "" do
|
|
Keyword.put(conn_opts, :password, password)
|
|
else
|
|
conn_opts
|
|
end
|
|
|
|
# Start a temporary connection for health check
|
|
case Redix.start_link(conn_opts) do
|
|
{:ok, conn} ->
|
|
result =
|
|
case Redix.command(conn, ["PING"], timeout: timeout_ms) do
|
|
{:ok, "PONG"} -> :ok
|
|
{:error, reason} -> {:error, reason}
|
|
end
|
|
|
|
Redix.stop(conn)
|
|
result
|
|
|
|
{:error, reason} ->
|
|
Logger.debug("Redis health check connection failed: #{inspect(reason)}")
|
|
{:error, reason}
|
|
end
|
|
rescue
|
|
error ->
|
|
Logger.debug("Redis health check error: #{inspect(error)}")
|
|
{:error, error}
|
|
catch
|
|
:exit, reason ->
|
|
Logger.debug("Redis health check exit: #{inspect(reason)}")
|
|
{:error, reason}
|
|
end
|
|
end
|