more redis cleanup

This commit is contained in:
Graham McIntire 2025-10-22 16:30:04 -05:00
parent 26d93e36c7
commit 537057ee9a
No known key found for this signature in database
2 changed files with 1 additions and 182 deletions

View file

@ -1,165 +0,0 @@
defmodule Aprsme.RedisRateLimiter do
@moduledoc """
Redis-backed distributed rate limiter using sliding window algorithm.
"""
require Logger
# 5 seconds timeout for Redis operations
@redis_timeout 5000
def child_spec(_opts) do
children = [
{Redix, name: __MODULE__, host: redis_host(), port: redis_port(), password: redis_password()}
]
%{
id: __MODULE__,
type: :supervisor,
start: {Supervisor, :start_link, [children, [strategy: :one_for_one, name: :"#{__MODULE__}_supervisor"]]}
}
end
@doc """
Check rate limit using sliding window algorithm.
## Parameters
- bucket: The rate limit bucket (e.g., "user:123" or "ip:192.168.1.1")
- limit: Maximum number of requests allowed
- window_ms: Time window in milliseconds
## Returns
- {:allow, count} if under limit
- {:deny, limit} if over limit
"""
def check_rate(bucket, limit, window_ms) do
now = System.system_time(:millisecond)
window_start = now - window_ms
key = make_key(bucket)
# Lua script for atomic sliding window rate limiting
lua_script = """
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window_start = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])
-- Remove old entries
redis.call('ZREMRANGEBYSCORE', key, 0, window_start)
-- Count current entries
local current = redis.call('ZCARD', key)
if current < limit then
-- Add new entry
redis.call('ZADD', key, now, now)
-- Set expiry
redis.call('EXPIRE', key, math.ceil(ARGV[4] / 1000))
return {1, current + 1}
else
return {0, limit}
end
"""
case Redix.command(__MODULE__, ["EVAL", lua_script, 1, key, now, window_start, limit, window_ms],
timeout: @redis_timeout
) do
{:ok, [1, count]} ->
{:allow, count}
{:ok, [0, limit]} ->
{:deny, limit}
{:error, reason} ->
Logger.error("Redis rate limit error for #{bucket}: #{inspect(reason)}")
# Fail open - allow request if Redis is down
{:allow, 1}
end
end
@doc """
Reset rate limit for a bucket
"""
def reset(bucket) do
key = make_key(bucket)
case Redix.command(__MODULE__, ["DEL", key], timeout: @redis_timeout) do
{:ok, _} ->
:ok
{:error, reason} ->
Logger.error("Redis rate limit reset error for #{bucket}: #{inspect(reason)}")
{:error, reason}
end
end
@doc """
Get current count for a bucket
"""
def count(bucket, window_ms) do
now = System.system_time(:millisecond)
window_start = now - window_ms
key = make_key(bucket)
# Clean up old entries and count
case Redix.pipeline(
__MODULE__,
[
["ZREMRANGEBYSCORE", key, 0, window_start],
["ZCARD", key]
],
timeout: @redis_timeout
) do
{:ok, [_, count]} ->
{:ok, count}
{:error, reason} ->
Logger.error("Redis rate limit count error for #{bucket}: #{inspect(reason)}")
{:error, reason}
end
end
@doc """
Get remaining requests for a bucket
"""
def remaining(bucket, limit, window_ms) do
case count(bucket, window_ms) do
{:ok, current} -> {:ok, max(0, limit - current)}
error -> error
end
end
# Private functions
defp make_key(bucket) do
"aprsme:rate_limit:#{bucket}"
end
defp redis_host do
redis_url = System.get_env("REDIS_URL", "redis://localhost:6379")
uri = URI.parse(redis_url)
uri.host || "localhost"
end
defp redis_port do
redis_url = System.get_env("REDIS_URL", "redis://localhost:6379")
uri = URI.parse(redis_url)
uri.port || 6379
end
defp redis_password do
redis_url = System.get_env("REDIS_URL", "redis://localhost:6379")
uri = URI.parse(redis_url)
case uri.userinfo do
nil ->
nil
userinfo ->
case String.split(userinfo, ":") do
[_, password] -> password
_ -> nil
end
end
end
end

View file

@ -69,8 +69,7 @@ defmodule AprsmeWeb.Plugs.HealthCheck do
defp full_health_checks do
# Comprehensive checks for readiness
with :ok <- check_database_connection(),
:ok <- check_redis_connection() do
with :ok <- check_database_connection() do
check_pubsub()
end
end
@ -84,21 +83,6 @@ defmodule AprsmeWeb.Plugs.HealthCheck do
_ -> {:error, "Database check failed"}
end
defp check_redis_connection do
if System.get_env("REDIS_URL") do
try do
case Redix.command(:query_cache_redis, ["PING"]) do
{:ok, "PONG"} -> :ok
_ -> {:error, "Redis connection failed"}
end
rescue
_ -> {:error, "Redis check failed"}
end
else
:ok
end
end
defp check_pubsub do
Phoenix.PubSub.broadcast(Aprsme.PubSub, "health_check", :ping)
:ok