Skip to content

improvement(schedules): jitter scheduled execution starts by 30s#4750

Merged
TheodoreSpeaks merged 1 commit into
stagingfrom
feat/schedule-execution-jitter
May 27, 2026
Merged

improvement(schedules): jitter scheduled execution starts by 30s#4750
TheodoreSpeaks merged 1 commit into
stagingfrom
feat/schedule-execution-jitter

Conversation

@TheodoreSpeaks
Copy link
Copy Markdown
Collaborator

Summary

Cron schedules all fire on the same boundary (e.g. every :00), stampeding the Postgres connection pool at the top of each minute/hour. Spread each due schedule's start across a [0, 30s) window via trigger.dev's delay option (no compute billed during the delay). Wires the previously-unused EnqueueOptions.delayMs through the trigger.dev backend.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Other: ___________

Testing

  • Minimal change. Will validate using native canaries in staging.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

Screenshots/Videos

Cron schedules all fire on the same boundary (e.g. every :00), stampeding
the Postgres connection pool at the top of each minute/hour. Spread each due
schedule's start across a [0, 30s) window via trigger.dev's delay option
(no compute billed during the delay). Wires the previously-unused
EnqueueOptions.delayMs through the trigger.dev backend.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped May 27, 2026 1:52am

Request Review

@cursor
Copy link
Copy Markdown

cursor Bot commented May 27, 2026

PR Summary

Low Risk
Small scheduling change on enqueue only; inline execution path is unchanged and job-type schedules are not jittered.

Overview
Adds 0–30s random jitter when enqueueing workflow schedule-execution jobs so many cron-bound schedules do not all start at the same second and spike the Postgres connection pool.

The scheduled execute API now sets delayMs to Math.floor(Math.random() * SCHEDULE_JITTER_MAX_MS) on enqueue (idempotency/concurrency keys unchanged). The trigger.dev job backend maps that existing EnqueueOptions.delayMs field to triggerOptions.delay, so delayed runs are scheduled without starting compute until the delay elapses.

Reviewed by Cursor Bugbot for commit 1aa959a. Bugbot is set up for automated code reviews on this repo. Configure here.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 27, 2026

Greptile Summary

This PR spreads scheduled workflow executions across a random [0, 30s) window to prevent DB connection-pool stampedes at the top of each cron boundary. The change is minimal and surgical: a SCHEDULE_JITTER_MAX_MS constant feeds a random delayMs into the existing EnqueueOptions, and the trigger.dev backend now converts that field into an absolute-timestamp delay for tasks.trigger.

  • route.ts: adds SCHEDULE_JITTER_MAX_MS = 30_000 and passes Math.floor(Math.random() * SCHEDULE_JITTER_MAX_MS) as delayMs when enqueueing each schedule-execution job; job-sourced schedules and the inline database path are unaffected because they never pass through this code path.
  • trigger-dev.ts: wires options.delayMs to triggerOptions.delay = new Date(Date.now() + options.delayMs) behind an > 0 guard; delayMs was already present in EnqueueOptions but was previously unused by this backend.

Confidence Score: 5/5

Safe to merge. The change is narrow and additive — it only affects how trigger.dev schedules the start of each execution, not the execution logic itself.

Two files changed, both in single-digit lines. The delayMs field was already typed in EnqueueOptions and is silently ignored by the database backend, so dev/test environments are unaffected. The trigger.dev guard (delayMs > 0) is correct, the absolute-timestamp approach to expressing the delay is valid, and the idempotency key path means duplicate enqueues during the delay window are handled correctly.

No files require special attention.

Important Files Changed

Filename Overview
apps/sim/app/api/schedules/execute/route.ts Adds SCHEDULE_JITTER_MAX_MS constant and passes a random [0, 30s) delayMs to enqueue() for each workflow schedule item; job schedule items (inline path) are unaffected.
apps/sim/lib/core/async-jobs/backends/trigger-dev.ts Wires the previously-ignored EnqueueOptions.delayMs through to trigger.dev's delay option using an absolute Date timestamp; guarded correctly by options.delayMs > 0.

Sequence Diagram

sequenceDiagram
    participant Cron as Cron Trigger
    participant Route as /api/schedules/execute
    participant DB as Postgres
    participant Queue as TriggerDevJobQueue
    participant TDev as trigger.dev

    Cron->>Route: GET (every minute, all schedules fire at :00)
    Route->>DB: claimWorkflowSchedules (SELECT FOR UPDATE SKIP LOCKED)
    DB-->>Route: [schedule1, schedule2, ..., scheduleN]

    loop for each claimed schedule
        Route->>Queue: "enqueue('schedule-execution', payload, { delayMs: rand[0,30s) })"
        Queue->>TDev: "tasks.trigger(taskId, payload, { delay: Date.now() + delayMs })"
        TDev-->>Queue: handle.id
        Queue-->>Route: jobId
    end

    Note over TDev: Executions spread across :00-:30 instead of all hitting DB at :00
Loading

Reviews (1): Last reviewed commit: "improvement(schedules): jitter scheduled..." | Re-trigger Greptile

@TheodoreSpeaks TheodoreSpeaks merged commit a1aa168 into staging May 27, 2026
14 checks passed
@TheodoreSpeaks TheodoreSpeaks deleted the feat/schedule-execution-jitter branch May 27, 2026 01:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant