proxy-pool/internal/adapters/redisactivity/scripts/target_health.lua
youfak 19cf3e32fa
Some checks are pending
ci / proto (push) Waiting to run
ci / test (ubuntu-latest) (push) Waiting to run
ci / test (windows-latest) (push) Waiting to run
ci / race (push) Waiting to run
ci / integration (push) Waiting to run
feat: persist target health profiles
2026-07-31 18:04:05 +08:00

117 lines
4.0 KiB
Lua

local records_key = KEYS[1]
local target_health_key = KEYS[2]
local operation_key = KEYS[3]
local observed_at_ms = tonumber(ARGV[1])
local latency_ns = tonumber(ARGV[2])
local proxy_id = ARGV[3]
local profile_key = ARGV[4]
local success = ARGV[5]
local max_consecutive_failures = tonumber(ARGV[6])
local task_id = ARGV[7]
local observation_digest = ARGV[8]
local operation_ttl_ms = tonumber(ARGV[9])
local committed = redis.call('GET', operation_key)
if committed then
return committed
end
local function finish(reply, hard_expiry_ms)
local encoded = cjson.encode(reply)
redis.call('SET', operation_key, encoded, 'PX', operation_ttl_ms)
if hard_expiry_ms then
local operation_expiry_ms = redis.call('PEXPIRETIME', operation_key)
if operation_expiry_ms > hard_expiry_ms then
redis.call('PEXPIREAT', operation_key, hard_expiry_ms)
end
end
return encoded
end
if not observed_at_ms or observed_at_ms <= 0 or not latency_ns or latency_ns < 0 or
type(proxy_id) ~= 'string' or proxy_id == '' or type(profile_key) ~= 'string' or
string.len(profile_key) ~= 64 or (success ~= '0' and success ~= '1') or
not max_consecutive_failures or max_consecutive_failures <= 0 or type(task_id) ~= 'string' or
task_id == '' or type(observation_digest) ~= 'string' or string.len(observation_digest) ~= 64 or
not operation_ttl_ms or operation_ttl_ms <= 0 then
return finish({status = 'invalid'})
end
local raw_proxy = redis.call('HGET', records_key, proxy_id)
if not raw_proxy then
return finish({status = 'not_found'})
end
local proxy = cjson.decode(raw_proxy)
if type(proxy.expiresAtMs) ~= 'number' or proxy.expiresAtMs <= observed_at_ms then
redis.call('DEL', target_health_key)
return finish({status = 'not_found'})
end
local raw_target = redis.call('HGET', target_health_key, profile_key)
local target
if raw_target then
local decoded, value = pcall(cjson.decode, raw_target)
if not decoded or type(value) ~= 'table' then
return finish({status = 'invalid'})
end
target = value
else
target = {
version = 1,
status = 'UNKNOWN',
consecutiveFailures = 0,
latencyNs = 0,
}
end
if target.version ~= 1 or type(target.status) ~= 'string' or
type(target.consecutiveFailures) ~= 'number' or target.consecutiveFailures < 0 or
type(target.latencyNs) ~= 'number' or target.latencyNs < 0 then
return finish({status = 'invalid'})
end
local last_task_id = target.lastHealthTaskId or ''
local last_digest = target.lastHealthDigest or ''
local last_observed_at_ms = tonumber(target.lastHealthObservedAtMs or '0')
if not last_observed_at_ms or last_observed_at_ms < 0 then
return finish({status = 'invalid'})
end
if last_observed_at_ms > 0 then
if type(last_task_id) ~= 'string' or last_task_id == '' or type(last_digest) ~= 'string' or
string.len(last_digest) ~= 64 then
return finish({status = 'invalid'})
end
if last_task_id == task_id then
if last_digest == observation_digest then
return finish({status = 'ok', target = raw_target}, proxy.expiresAtMs)
end
return finish({status = 'conflict'})
end
if observed_at_ms <= last_observed_at_ms then
return finish({status = 'stale'})
end
end
target.lastHealthTaskId = task_id
target.lastHealthDigest = observation_digest
target.lastHealthObservedAtMs = observed_at_ms
target.latencyNs = latency_ns
if success == '1' then
target.status = 'AVAILABLE'
target.consecutiveFailures = 0
target.lastSuccessAtMs = observed_at_ms
else
target.consecutiveFailures = target.consecutiveFailures + 1
if target.consecutiveFailures >= max_consecutive_failures then
target.status = 'UNHEALTHY'
else
target.status = 'SUSPECT'
end
end
local encoded = cjson.encode(target)
redis.call('HSET', target_health_key, profile_key, encoded)
redis.call('PEXPIREAT', target_health_key, proxy.expiresAtMs)
return finish({status = 'ok', target = encoded}, proxy.expiresAtMs)