How can I prevent timers from bunching up?
Opscotch 3.1.7 adds bootstrap.workflow.timers.staggerPct, a startup-time control for repeating workflow timers. It spreads timer start times across a randomized window so you do not end up with a burst of timers all landing on the same tick.
In the same startup path, runOnce timers now run to completion before periodic timers are started. That keeps one-time startup work ahead of the steady-state timer cadence.
If you start several timers together, they tend to stay aligned:
- a 30 second timer lands on the half-minute and minute marks
- a minutely timer lands on each minute
- an hourly timer lands on each hour
That can create an avoidable resource spike, especially when multiple bootstraps activate multiple workflows at the same time.
It also affects downstream services. If a group of timers all fire on the same tick, they often make HTTP calls at the same time too. Staggering helps distribute those outbound requests across ticks instead of pushing them all at once.
The goal of staggering is not to change the timer cadence. It is to distribute the first fire so the repeated executions are offset from each other.
The four controls
Opscotch gives you four different ways to spread the load:
runOncefor one-time startup worktrigger.timer.delayfor a specific timerbootstrap.startupPriorityfor deployment activation orderbootstrap.workflow.timers.staggerPctfor randomized timer startup within a bootstrap
They solve different parts of the problem.
trigger.timer.delay
If you need one timer to start later, use delay.
{
"trigger": {
"timer": {
"delay": 15000,
"period": 30000
}
}
}
delay shifts the first invocation. The repeating period is unchanged.
Use this when you want deterministic offsetting for one timer or one workflow step.
bootstrap.startupPriority
If you have multiple deployments in the same bootstrap file, startupPriority controls which ones activate first.
[
{
"deploymentId": "core",
"remoteConfiguration": "core.config.json",
"startupPriority": 1
},
{
"deploymentId": "worker",
"remoteConfiguration": "worker.config.json",
"startupPriority": 10
}
]
Lower numbers activate first.
This does not randomize timer timing, but it does give you a predictable way to spread workflow activation across deployments.
See How can I optimize Opscotch startup time for the broader boot-time controls around activation order and deferred loading.
runOnce
In 3.1.7, startup runOnce timers complete before periodic timers begin.
That matters when a workflow uses a one-time startup action alongside repeating timers:
- the
runOncework finishes first - the periodic timers begin after that startup work completes
- staggered periodic timers still benefit from
staggerPct
Use this when you want startup initialization to finish before the recurring timer load starts.
bootstrap.workflow.timers.staggerPct
This is the new control in 3.1.7.
{
"workflow": {
"timers": {
"staggerPct": 25
}
}
}
staggerPct applies a one-time randomized startup delay to repeating workflow timers.
The value is a percentage of the timer period:
0disables staggering25means up to 25 percent of the period100means up to one full period
So for a 60 second timer, staggerPct: 25 can push the first fire by up to 15 seconds.
A timer with an explicit delay is left alone. The bootstrap stagger only applies when the timer itself did not already ask for a startup delay.
Using them together
The useful pattern is to mix deterministic and randomized offsetting:
- use
startupPriorityto order deployments - use
runOncefor one-time startup work that should complete before periodic timers begin - use
delayfor a specific timer that should start later - use
staggerPctto spread the remaining repeating timers across a startup window
That gives you a way to keep a burst of timers from all waking up at the same moment, without changing what each timer does after it starts.
Practical guidance
Use timer staggering when:
- several workflows start at the same time
- many repeating timers have the same or related period
- startup load matters more than a perfectly synchronized first tick
- you want to reduce contention on shared resources like databases, APIs, queues, or internal services
Do not use it as a substitute for capacity planning. It helps flatten the spike, but it does not remove the workload.
What this is not
staggerPct is not a per-fire random jitter system.
It is a startup-time offset for repeating timers. Once the timer is started, it continues on its configured period.
That makes it a good fit for reducing the chance that a large set of timers all fire together immediately after startup.