Skip to content

Commit 74db5a3

Browse files
authored
docs: document cron schedule windows (#4657)
1 parent 7e67700 commit 74db5a3

1 file changed

Lines changed: 45 additions & 0 deletions

File tree

docs/tasks/scheduled.mdx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,50 @@ To use imperative schedules you need to do two things:
140140
1. Define a task in your code using `schedules.task()`.
141141
2. Attach 1+ schedules to the task either using the dashboard or the SDK.
142142

143+
## Spreading runs with windows
144+
145+
By default a schedule runs at its exact cron time. When many schedules share the same cron pattern, such as a daily `0 9 * * *`, they all fire at the same time and load your downstream systems as well as ours. A **window** spreads those runs out: each schedule is assigned a stable time within the window after its cron time, so the load is smoothed while each schedule keeps firing at a predictable, repeatable moment.
146+
147+
The assigned time is deterministic. A given schedule always lands at the same offset for a given interval, so runs don't jump around between occurrences. And you can always see exactly when the next run will start.
148+
149+
Set a `window` as either:
150+
151+
- **An absolute duration** in whole minutes or hours, up to 24 hours: `"30m"`, `"2h"`, `"24h"`. Absolute windows are capped at the next cron time, so a run is never delayed past its following occurrence.
152+
- **A percentage** of the interval between runs: `"30%"`, `"100%"`. A `"50%"` window on an hourly schedule spreads runs across the first 30 minutes of each hour.
153+
154+
Set `"0m"` (or `"0%"`) for no spreading — the run fires at its exact cron time.
155+
156+
Declarative schedules set the window on the `cron` object:
157+
158+
```ts
159+
export const dailyReport = schedules.task({
160+
id: "daily-report",
161+
cron: {
162+
pattern: "0 0 * * *",
163+
// spread this run across the 30 minutes after midnight
164+
window: "30m",
165+
},
166+
run: async (payload) => {},
167+
});
168+
```
169+
170+
Imperative schedules set it when creating or updating a schedule, either in the dashboard form or through the SDK:
171+
172+
```ts
173+
const createdSchedule = await schedules.create({
174+
task: dailyReport.id,
175+
cron: "0 0 * * *",
176+
window: "30m",
177+
deduplicationKey: "user_123456-daily-report",
178+
});
179+
```
180+
181+
<Note>
182+
The payload `timestamp` and `upcoming` values are always the nominal cron times, not the assigned
183+
times. When you retrieve a schedule, `nextRun` is the nominal cron time and `nextRunEffectiveAt`
184+
is the assigned time the run will actually start.
185+
</Note>
186+
143187
## Supported cron syntax
144188

145189
```
@@ -198,6 +242,7 @@ These are the options when creating a schedule:
198242
| Task | The id of the task you want to attach to. |
199243
| Cron pattern | The schedule in cron format. You can also describe it in natural language and press "Generate" to fill this in. |
200244
| Timezone | The timezone the schedule will run in. Defaults to "UTC" |
245+
| Window | An optional [window](#spreading-runs-with-windows) to spread runs after their cron time, e.g. `30m`, `2h`, or `50%`. |
201246
| External id | An optional external id, usually you'd use a userId. |
202247
| Deduplication key | An optional deduplication key. If you pass the same value, it will update rather than create. Scoped per project, not per environment. |
203248
| Environments | The environments this schedule will run in. |

0 commit comments

Comments
 (0)