[superlog] Fix 65s basket hang by resetting connected state on Redpanda send failure#469
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
The latest updates on your projects. Learn more about Unkey Deploy
|
Greptile SummaryThis PR fixes a production hang where Redpanda send failures caused all subsequent analytics ingestion requests to block for ~65 seconds before falling back to ClickHouse. The root cause was that
Confidence Score: 4/5The change is safe to merge — the two added lines directly fix the documented production hang and introduce no new failure modes on the hot path. The core fix is correct and well-scoped: connected: false + lastRetry: Date.now() in the KafkaSendError handler breaks the tight loop that caused lock-queue buildup. The only trade-off introduced is that shutDown now skips kafka.disconnect() when a send failure preceded graceful shutdown, because the disconnect gate reads post.connected. This is a narrow, low-impact path (process exit cleans up OS connections anyway), but it is a real behavioral change worth revisiting if the shutdown sequence ever needs to be more precise. apps/basket/src/lib/producer.ts — specifically the shutDown function and its post.connected guard. Important Files Changed
Sequence DiagramsequenceDiagram
participant R as Request
participant SV as sendViaKafka
participant C as connect()
participant K as KafkaJS Producer
participant CH as ClickHouse Buffer
Note over R,CH: After fix — KafkaSendError path
R->>SV: sendViaKafka(topic, messages)
SV->>C: connect()
C->>C: "s.connected == false, check cooldown"
C-->>SV: return false (within 60s cooldown)
SV->>CH: bufferAll(fallbackEvents)
Note over R,CH: Before fix — KafkaSendError path (hang)
R->>SV: sendViaKafka(topic, messages)
SV->>C: connect()
C->>C: "s.connected == true, return true immediately"
SV->>K: kafka.send() queues behind idempotent lock
K-->>SV: KafkaJSLockTimeout after ~65s
SV->>CH: bufferAll(fallbackEvents)
|
izadoesdev
left a comment
There was a problem hiding this comment.
Reviewed against staging. I agree with the production hot-path intent: after KafkaSendError, clearing connected and setting lastRetry prevents subsequent requests from queueing behind KafkaJS idempotent-send locks.
Holding this unmerged for two reasons:
apps/basket/src/lib/producer.ts: after this patch,shutDownonly callskafka.disconnect()whenpost.connectedis true. A send failure now setsconnected: false, so shutdown can skip disconnecting an initialized Kafka producer. The send-eligibility state and cleanup state should be separated, or shutdown should key off producer existence/initialization rather than theconnectedflag alone.- CI is not green (
LintandInsights Health Checkare failing). The failures look unrelated to this two-line patch, but they are still active PR failures.
Also worth adding a regression test for: first Kafka send fails, next send within reconnect cooldown skips Kafka and buffers immediately.
This PR should remain the survivor over #467 because it targets staging, but I am leaving it unmerged until the cleanup edge and checks are handled.
izadoesdev
left a comment
There was a problem hiding this comment.
Updated the branch to current staging and fixed the lifecycle blocker from the previous review. The producer now tracks whether Kafka was initialized separately from whether it is currently healthy for sends, so KafkaSendError can mark connected: false for cooldown/fallback without causing shutdown to skip kafka.disconnect(). Added producer.kafka.test.ts covering: first Kafka send fails, second send during cooldown buffers without another Kafka send, and shutdown still disconnects. Verification: cd apps/basket && bun run test src/lib/producer.test.ts src/lib/producer.kafka.test.ts, cd apps/basket && bun run check-types, bun run lint, merge-commit hook bun run check-types, and pre-push full bun run test all pass. Approving for staging merge.
Summary
When Redpanda becomes unavailable, the basket analytics ingestion service was blocking every
/batch,/vitals, and/trackrequest for ~65 seconds before falling back to ClickHouse. This affected all customer websites using the analytics SDK.The root cause is in
apps/basket/src/lib/producer.ts. After aKafkaSendError, the producer state update only setconnectionFailed: truebut leftconnected: trueandlastRetry: 0. Theconnect()helper short-circuits toreturn truewhenconnectedis set, so every subsequent concurrent request bypassed the reconnect cooldown and attempted anotherkafka.send(). Withidempotent: trueon the KafkaJS producer, these sends queue behind an internal lock. KafkaJS's lock timeout is hardcoded at ~65,536 ms, so 200–1700+ queued requests each waited the full lock timeout before receiving aKafkaJSLockTimeoutand falling back to ClickHouse.The
KafkaConnectionErrorpath already correctly setconnected: falseandlastRetry: Date.now()— this fix mirrors that same pattern in theKafkaSendErrorpath. After the first failed send, all subsequent requests immediately skip Kafka and buffer to ClickHouse without any lock contention.Alternatively, disabling
idempotent: trueon the KafkaJS producer would also eliminate the lock mechanism entirely (analytics is tolerant of rare duplicates), which could be a simpler long-term fix.Incident on Superlog
Was this PR helpful? Leave feedback — goes straight to the Superlog team.
Summary by cubic
Fixes 65s hangs in basket analytics when Redpanda is down by resetting producer state after send failures, and ensures the Kafka producer disconnects cleanly on shutdown. Requests now skip Kafka immediately and buffer to ClickHouse.
KafkaSendError, setconnected: false,lastRetry = Date.now(), and trackproducerInitializedto allow safe disconnect on shutdown (mirrorsKafkaConnectionError).connect()short-circuit and KafkaJS idempotent send lock buildup, removing the ~65s wait./batch,/vitals, and/trackno longer block; events buffer to ClickHouse until Redpanda recovers.Written for commit fea369b. Summary will update on new commits.