Waking Up Distributed State

Durable Objects have given developers a stateful building block for distributed applications. Each object is a globally unique instance of a JavaScript class, reachable via a unique ID. A banking app might keep one Durable Object per account, with methods for balance changes and transfers. Because all requests for a given object land on the same instance, and each instance is single-threaded with access to a stateful storage API, consistent distributed systems become tractable. Teams have built collaborative whiteboards and CRDT-based coordination layers on top of this model.

The missing piece, however, has been initiation: nothing could programmatically wake an object that wasn't currently receiving client requests. A Durable Object that stopped running — because its machine was unplugged, its datacenter failed, or it hit a memory limit and was reset — would only be recreated lazily on the next request. Durable Objects Alarms close that gap by letting objects schedule their own future execution.

How Alarms Work

From inside a Durable Object, you can schedule a wake-up time via the storage API. When that time arrives, the object's alarm() handler fires. If the handler throws, the alarm retries with exponential backoff. Execution is guaranteed at-least-once.

Alarms are far more granular than Workers Cron Triggers. A single Worker service is capped at three Cron Triggers, each with a fixed schedule configured through the dashboard or centralized APIs. Alarms, by contrast, are set programmatically per object — and while each object can hold only one active alarm, you can have an unlimited number of objects, each with its own.

Setting an Alarm

To use alarms, first enable the durable_object_alarms compatibility flag in wrangler.toml.

compatibility_flags = ["durable_object_alarms"]

Next, implement the alarm() handler. Anywhere else in the object, call state.storage.setAlarm() with the desired run time. Use state.storage.getAlarm() to read the currently scheduled time.

export default {
  async fetch(request, env) {
    let id = env.BATCHER.idFromName("foo");
    return await env.BATCHER.get(id).fetch(request);
  },
};

const SECONDS = 1000;

export class Batcher {
  constructor(state, env) {
    this.state = state;
    this.storage = state.storage;
    this.state.blockConcurrencyWhile(async () => {
      let vals = await this.storage.list({ reverse: true, limit: 1 });
      this.count = vals.size == 0 ? 0 : parseInt(vals.keys().next().value);
    });
  }
  async fetch(request) {
    this.count++;

    // If there is no alarm currently set, set one for 10 seconds from now
    // Any further POSTs in the next 10 seconds will be part of this kh.
    let currentAlarm = await this.storage.getAlarm();
    if (currentAlarm == null) {
      this.storage.setAlarm(Date.now() + 10 * SECONDS);
    }

    // Add the request to the batch.
    await this.storage.put(this.count, await request.text());
    return new Response(JSON.stringify({ queued: this.count }), {
      headers: {
        "content-type": "application/json;charset=UTF-8",
      },
    });
  }
  async alarm() {
    let vals = await this.storage.list();
    await fetch("http://example.com/some-upstream-service", {
      method: "POST",
      body: Array.from(vals.values()),
    });
    await this.storage.deleteAll();
    this.count = 0;
  }
}

The example above shows a batching pattern: the alarm wakes the object every ten seconds to drain a queue only when enough work has accumulated. If an unexpected error kills the object, it is re-instantiated elsewhere after a short delay and resumes processing.

Alarm persistence sits on the same storage layer as object data. Reads and writes follow identical rules — writes coalesce, reads have a defined ordering — and they commit atomically with regular storage operations. The caching architecture behind this is described in the original Durable Objects storage post.

Fault-Tolerance Design

Alarms are engineered without a single point of failure and run entirely at the edge. Every Cloudflare datacenter that supports Durable Objects can execute alarms and can take over responsibility for objects from unhealthy datacenters when needed. A single failure should recover in under 30 seconds; multiple simultaneous failures may take longer.

Under the hood, alarm state lives in the same distributed datastore that backs object storage. That design gives alarms the same replication and atomicity guarantees as other object data. Within each datacenter, multiple processes track and trigger upcoming alarms for fault tolerance and scaling. A single elected leader per datacenter monitors peer datacenters for failure and reassigns their alarms to local processes. If the leader itself fails, a new leader is elected, preserving at-least-once execution.

Primitives for Deferred Work

Alarms make it practical to build durable primitives like queues on top of Durable Objects. They also guarantee that work gets done without relying on an external request to nudge an object awake.

To get started, enable Durable Objects in the Cloudflare dashboard and add the compatibility flag to your project. Documentation and community support are available through the developer docs and Discord.