fix(redis): tear down half-open client socket + alert on stuck Redis - #2185
Open
overthelex wants to merge 1 commit into
Open
fix(redis): tear down half-open client socket + alert on stuck Redis#2185overthelex wants to merge 1 commit into
overthelex wants to merge 1 commit into
Conversation
Prod incident 2026-07: the backend's node-redis client went half-open during a blue-green cutover. node-redis kept it "connected" but never tore the dead socket down, so it never reconnected and EVERY cache / rate-limit / chat-search op hit the 2500ms CacheAdapter timeout indefinitely (6+ min) until a manual restart. keepAlive + pingInterval (already present) were not enough on their own. Fix + observability: - redis-client: add socket.socketTimeout (60s) so a read-inactivity window closes a dead socket and triggers reconnectStrategy; PONG traffic from pingInterval (lowered 30s->20s) keeps a healthy link well under the timeout. Wire ready/reconnecting/end events to a connection-state hook. - cache-adapter: setMetricsCallback fired on every timed-out/errored op. - metrics: backend_redis_client_up gauge + redis_command_errors_total counter (named distinctly from redis_exporter's server-side redis_up). - http-server: wire the gauge (via state hook) and the error counter. - alert-rules: BackendRedisClientDown (gauge==0 2m) and BackendRedisCommandErrors (rate>0.2/s 3m) — the latter is the signal that catches a stuck client while it still reports "up". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
1 issue found across 6 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="mcp_backend/src/utils/redis-client.ts">
<violation number="1" location="mcp_backend/src/utils/redis-client.ts:58">
P3: The Redis client unit test is now stale: it still expects a `disconnect` handler, while this code registers `ready`, `reconnecting`, and `end`. Updating `src/utils/__tests__/redis-client.test.ts` to assert the new events would keep CI aligned with the intended node-redis event model.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
|
||
| redisClient.on('disconnect', () => { | ||
| logger.warn('[Redis] Disconnected'); | ||
| redisClient.on('ready', () => { |
Contributor
There was a problem hiding this comment.
P3: The Redis client unit test is now stale: it still expects a disconnect handler, while this code registers ready, reconnecting, and end. Updating src/utils/__tests__/redis-client.test.ts to assert the new events would keep CI aligned with the intended node-redis event model.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At mcp_backend/src/utils/redis-client.ts, line 58:
<comment>The Redis client unit test is now stale: it still expects a `disconnect` handler, while this code registers `ready`, `reconnecting`, and `end`. Updating `src/utils/__tests__/redis-client.test.ts` to assert the new events would keep CI aligned with the intended node-redis event model.</comment>
<file context>
@@ -36,11 +55,24 @@ export async function getRedisClient(): Promise<ReturnType<typeof createClient>
- redisClient.on('disconnect', () => {
- logger.warn('[Redis] Disconnected');
+ redisClient.on('ready', () => {
+ stateHook?.(true);
+ });
</file context>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Проблема (prod-інцидент 2026-07)
Після blue-green cutover постійний node-redis клієнт бекенда перейшов у half-open стан: TCP-сокет мертвий, але node-redis вважав з'єднання «connected» і не розривав його. Reconnect не спрацьовував, тому кожна операція cache / rate-limit / chat-search падала в 2500ms-таймаут
CacheAdapterбезкінечно (6+ хв), поки бекенд не перезапустили вручну. У UI це проявлялося як «Помилка мережі. Перевірте підключення.»Наявні
keepAlive+pingInterval(LEXAI-1795) самі по собі не рятували:pingIntervalшле PING, але без read-таймауту node-redis ніколи не закриває напівживий сокет, а TCP-keepAlive виявляє це надто повільно.Виправлення
redis-client.ts: доданоsocket.socketTimeout = 60s— вікно без вхідних байтів закриває мертвий сокет і запускаєreconnectStrategy. На живому з'єднанні PONG відpingInterval(знижено 30s→20s) тримає трафік значно нижче таймауту, тож спрацьовує лише на реально мертвому сокеті. Подіїready/reconnecting/endпід'єднані до хука стану.cache-adapter.ts:setMetricsCallback— викликається на кожній таймаут/помилці операції.metrics-service.ts:backend_redis_client_up(gauge) +redis_command_errors_total(counter). Назва gauge відрізняється від серверногоredis_upз redis_exporter.http-server.ts: під'єднання gauge (через хук стану) і лічильника помилок.alert-rules.yml: два алерти:BackendRedisClientDown—backend_redis_client_up == 0за 2 хв (critical)BackendRedisCommandErrors—rate(redis_command_errors_total) > 0.2/sза 3 хв (critical). Саме цей алерт ловить застряглий клієнт, поки gauge ще показує «up».Тести
cache-adapter.test.ts: +2 кейси на metrics callback (спрацьовує з лейблом операції на помилці; не спрацьовує на успіху). Усі 6 тестів проходять.Поза скоупом
redis-consultation-message-bus.ts(pub/sub) не чіпав —socketTimeoutна subscriber-з'єднанні має нюанси, а інцидент був у cache-клієнті.🤖 Generated with Claude Code
Summary by cubic
Hardened the backend
redisclient to auto-tear half-open sockets and reconnect. Added metrics and alerts to detect a stuck client and prevent cascading cache/rate-limit timeouts.Bug Fixes
socketTimeoutand lowerpingIntervalto 20s to close dead sockets and trigger reconnects.ready/reconnecting/end.New Features
backend_redis_client_upgauge andredis_command_errors_totalcounter; wired inhttp-servervia the state hook andCacheAdapter.setMetricsCallback; tests added for the callback.BackendRedisClientDown(2m) andBackendRedisCommandErrors(>0.2/s for 3m).Written for commit b7c0067. Summary will update on new commits.