Sam Joyce

Wake on message

Services that sleep until a message arrives, wake with their state, and doze off again.

Wake on message

t 0.0 s · seed 7

Store

  1. −0.13savea1852
  2. −0.13savea5859
  3. −0.10loada5630
  4. −0.10savea5631
  5. −0.10savea225
  6. 0.00savea751
20/s
1.00
2.0 s
120 ms
Speed
In memory
22 of 64
Cold starts
7.5/s
Latency p50
8 ms
p99
128 ms
Memory, 4 kB each
88 kB
Describe this figure

At 0.0 s, 22 of 64 in memory, 7.5 cold starts a second, median 8 ms, p99 128 ms. In memory: a1, a2, a3, a6, a7, a8, a10, a11, a12, a13, a14, a16, a17, a18, a19, a20, a22, a33, a48, a49, a56, a58.

Settings: 20 messages a second, Zipf s 1.00, idle timeout 2.0 s, activation cost 120 ms. Latest store operations: save a18 stored 52; save a58 stored 59; load a56 got 30; save a56 stored 31; save a2 stored 25; save a7 stored 51.

Things to try

  • Press Play and let the readouts settle. Expect about 21 actors in memory and 6.7 cold starts a second. The median message takes 8 ms; the p99 sits at 128 ms, because roughly one message in three finds its actor asleep and waits 120 ms for a load.
  • Stretch the idle timeout to 10 s. Cold starts fall to about 1.7 a second, but about 48 actors now stay in memory. Pull it down to 0.5 s and the trade flips: 61% of messages arrive cold, so even the median waits for a load.
  • Pause, then click a dashed cell three times (or pick it with the arrow keys and press Enter three times), and Step twice. The actor wakes, loads its state and saves its three messages one at a time. Show the trick to see when the eight busiest actors were awake.

How it works

Each actor is a counter that lives in a store until something needs it. A message for a sleeping actor wakes it: its state loads, which takes the activation cost, and the message waits in the mailbox. An awake actor works through its mailbox strictly one message at a time. It adds one, saves the result, then takes the next. When nothing has arrived for the idle timeout it drops out of memory; its state is already saved, so nothing is lost.

Each message goes to an actor drawn from a Zipf distribution, where actor k is chosen with weight 1 / k^s. At s = 1 the busiest actor gets 21% of the traffic and the quietest well under 1%. Busy actors never sleep. Quiet ones wake for almost every message.

An actor that receives r messages a second is awake when one arrives with probability close to 1 - exp(-r * timeout). Summed over the grid, that predicts the readouts, and it shows the trade: a longer timeout keeps more actors in memory so that fewer messages pay for a wake. With evenly spread traffic, at s = 0, more than half of them pay.

The costs are illustrative: 8 ms to process and save a message, 60 ms to go back to sleep, 4 kB of memory per awake actor.

A toy model of the pattern. aktorz, one of my public repositories, provides lazily activated, single-threaded, stateful services with private durable storage for Zig, with SQLite persistence and TypeScript bindings.

Keyboard

Key Action
Tab Move between the grid, sliders and playback controls
Arrow keys Move between actors in the grid, or change the focused slider
Home, End Jump to the first or last actor in a row
Enter Send a message to the focused actor; Space works too
P Play or pause, while focus is inside the figure
S Step 100 ms, while focus is inside the figure

Related

Related public repository: aktorz in ProjectsZigaktorz on GitHub

Lazily activated, single-threaded, stateful services with private durable storage for Zig; SQLite persistence and TypeScript bindings.

This plate is a sketch of the general idea, not a description of how aktorz works.

Published

More from the lab

Nº 008Previous

Contrast on one hue

A twelve-step OKLCH ramp from one hue, with every WCAG ratio computed live.

lab · type

Nº 010Next

Fan-out tails

Why a job that waits on many workers is slower than any one of them.

lab · systems