Skip to content

Commit 09143e1

Browse files
committed
perf(mothership): adaptive backoff on the reconnect poll tail
The resume stream polled Postgres + Redis at a fixed 4 Hz per attached client for up to an hour. The tail now decays 250ms -> 2s while quiet and snaps back to full rate the moment an event flushes. Claude-Session: https://claude.ai/code/session_01CgaxNAaeD3taGdghbXn17w
1 parent f0d5157 commit 09143e1

1 file changed

Lines changed: 11 additions & 3 deletions

File tree

  • apps/sim/app/api/copilot/chat/stream

apps/sim/app/api/copilot/chat/stream/route.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const maxDuration = 3600
3535

3636
const logger = createLogger('CopilotChatStreamAPI')
3737
const POLL_INTERVAL_MS = 250
38+
const POLL_INTERVAL_MAX_MS = 2_000
3839
const REPLAY_KEEPALIVE_INTERVAL_MS = 15_000
3940
const MAX_STREAM_MS = 60 * 60 * 1000
4041

@@ -322,7 +323,7 @@ async function handleResumeRequestBody({
322323
}
323324
request.signal.addEventListener('abort', abortListener, { once: true })
324325

325-
const flushEvents = async () => {
326+
const flushEvents = async (): Promise<number> => {
326327
const events = await readEvents(streamId, cursor)
327328
if (events.length > 0) {
328329
logger.debug('[Resume] Flushing events', {
@@ -342,6 +343,7 @@ async function handleResumeRequestBody({
342343
sawTerminalEvent = true
343344
}
344345
}
346+
return events.length
345347
}
346348

347349
const emitTerminalIfMissing = (
@@ -390,6 +392,7 @@ async function handleResumeRequestBody({
390392

391393
await flushEvents()
392394

395+
let pollDelayMs = POLL_INTERVAL_MS
393396
while (!controllerClosed && Date.now() - startTime < MAX_STREAM_MS) {
394397
pollIterations += 1
395398
const currentRun = await getLatestRunForStream(streamId, authenticatedUserId).catch(
@@ -412,7 +415,12 @@ async function handleResumeRequestBody({
412415

413416
currentRequestId = extractRunRequestId(currentRun) || currentRequestId
414417

415-
await flushEvents()
418+
const flushed = await flushEvents()
419+
/* Adaptive tail: 4 Hz only while events are actually flowing; a quiet stream
420+
decays toward the cap so an attached client doesn't hammer Postgres + Redis
421+
at 4 Hz for up to an hour. Any flushed event snaps back to full rate. */
422+
pollDelayMs =
423+
flushed > 0 ? POLL_INTERVAL_MS : Math.min(pollDelayMs * 2, POLL_INTERVAL_MAX_MS)
416424

417425
if (controllerClosed) {
418426
break
@@ -440,7 +448,7 @@ async function handleResumeRequestBody({
440448
enqueueComment('keepalive')
441449
}
442450

443-
await sleep(POLL_INTERVAL_MS)
451+
await sleep(pollDelayMs)
444452
}
445453
if (!controllerClosed && Date.now() - startTime >= MAX_STREAM_MS) {
446454
emitTerminalIfMissing(MothershipStreamV1CompletionStatus.error, {

0 commit comments

Comments
 (0)