Skip to main content

How can I prevent timers from bunching up?

· 4 min read
Jeremy Scott
Co-founder

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:

  • runOnce for one-time startup work
  • trigger.timer.delay for a specific timer
  • bootstrap.startupPriority for deployment activation order
  • bootstrap.workflow.timers.staggerPct for 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 runOnce work 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:

  • 0 disables staggering
  • 25 means up to 25 percent of the period
  • 100 means 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 startupPriority to order deployments
  • use runOnce for one-time startup work that should complete before periodic timers begin
  • use delay for a specific timer that should start later
  • use staggerPct to 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.