Cloud Parts

Actor model

Many small stateful objects, each with a mailbox, that handle one message at a time and are spread across a cluster by their ID.

4 technologies on this page ↓
Live model
What you’re watchingEach request carries a cart ID (its colour). The router hashes the ID, so every message for one cart reaches the same actor, which handles one at a time while the rest wait in its mailbox. Now and then cart-77 crashes; its supervisor restarts it, and messages that arrive meanwhile are lost unless the sender retries.

How it works

An actor is a small object that owns some private state and a mailbox. Nothing outside can read or change that state; other code can only send the actor a message, which lands in its mailbox. The runtime takes one message at a time from the mailbox and runs the actor’s handler on it, so the code inside reads like plain sequential code with no locks. Concurrency comes from having many actors: an idle actor is just an object in memory, so a single process can hold millions of them on a small pool of threads. While handling a message an actor can update its state, send messages, reply, or create child actors. Actors form a tree, and a parent supervises its children: when one fails, the parent restarts it with fresh state, stops it, or lets it carry on, instead of every caller handling the error. An actor reference works the same whether the actor is in the same process or on another machine (location transparency), so the runtime can spread actors over a cluster. With sharding, each actor has an ID such as cart-4812; the runtime hashes the ID to a shard, runs that shard on exactly one node, routes every message for that ID there and moves shards when nodes join or leave. Virtual actors, introduced by Orleans and also used by Service Fabric and Dapr, go further: an actor always exists logically, is activated on its first message, is removed from memory when idle and keeps its state in a database. Messages between actors are usually delivered at most once, in order per sender, so state that must survive a crash is persisted (often as events) and important requests are retried. Compared with a queue, which hands each job to any free worker, an actor system routes each message to the one object that owns that ID’s state.

Think of it as

An office where everyone has an in-tray. You never walk over and edit a colleague’s files; you drop a note in their tray. Each person works through their tray one note at a time, can pass notes on or bring in a helper, and a team lead replaces anyone who falls ill. The notes reach the right desk even after people move to another floor.

Reach for it when

  • Many independent things with their own changing state: shopping carts, game sessions, chat rooms, devices (“digital twins”)
  • State that should have exactly one writer at a time, without database locks or optimistic-retry loops
  • Hot state kept in memory for low latency, with changes persisted in the background as events or snapshots
  • Long-lived sessions or connections, such as WebSockets, that need a single home that can move when a node goes away

Think twice when

  • Stateless request handling; ordinary services or serverless functions are simpler
  • Queries across many entities (“all carts over $100”); those need a database or a projection, not thousands of actor calls
  • Simple background jobs where any worker will do; a queue and a worker pool is easier to run
  • Teams without time to learn asynchronous messaging: at-most-once delivery, message versioning, cluster membership and split-brain handling are real costs, and the runtime becomes a core dependency

A real example

A shop models each cart as an actor. The web tier sends “add sku-9” for cart-4812 to the cluster; the ID hashes to shard 17, which lives on node B. If the cart isn’t in memory, node B starts it and replays its stored events, then applies the command, persists a new event and replies in a few milliseconds. Two fast clicks from the same customer are handled one after the other, never at the same time. When node B is drained for an upgrade, its shards move to nodes A and C, and the next message for cart-4812 restarts the cart there from its events. Carts that see no messages for a few minutes are removed from memory.

Technologies

Specific products, and what each does differently.

What each provider calls it

AWS
No managed actor runtime. Teams run Akka, Apache Pekko, Orleans or Dapr on their own compute such as EKS; Orleans and Akka both have plugins that keep actor state in DynamoDB, Orleans can track cluster membership there too, and Akka’s own managed service runs in AWS regions
Google Cloud
No managed actor runtime. Teams run Akka, Apache Pekko, Orleans or Dapr on GKE or VMs; Akka’s managed Serverless service runs in Google Cloud regions too, and its BYOC option deploys into your own Google Cloud VPC
Azure
Service Fabric Reliable Actors (virtual actors with single-threaded turns); Dapr actors in Azure Container Apps (GA, though Microsoft notes Dapr’s actor SDK packages aren’t compatible yet); Orleans, Microsoft’s own open-source virtual-actor framework, runs on AKS, App Service or Container Apps and stores state in Azure Storage or Cosmos DB
Open source & others
Erlang/OTP and Elixir (processes and supervisors on the BEAM VM); Apache Pekko (JVM, Apache 2.0) and Akka (JVM, BSL since 2.7); Microsoft Orleans (.NET, virtual actors, MIT); Proto.Actor (.NET and Go); Dapr actors (a sidecar, any language); Ray actors (Python). Cloudflare Durable Objects is a hosted take on the same idea: one single-threaded object per name, with its own storage

In code · python

# pip install ray   (Ray actors: one Python object per cart; its calls run one at a time)
import ray

ray.init()

@ray.remote(max_restarts=3)  # restarted if its process dies; in-memory state starts over
class Cart:
    def __init__(self, cart_id: str):
        self.cart_id = cart_id
        self.items: dict[str, int] = {}

    def add(self, sku: str, qty: int = 1) -> dict[str, int]:
        # no lock needed: calls to this actor run serially, in the order they were sent
        self.items[sku] = self.items.get(sku, 0) + qty
        return dict(self.items)

def cart(cart_id: str):
    # address the actor by name: created on first use, reused afterwards
    return Cart.options(name=f"cart-{cart_id}", get_if_exists=True).remote(cart_id)

c = cart("4812")
refs = [c.add.remote("sku-1"), c.add.remote("sku-2"), c.add.remote("sku-1")]  # sent at once
print(ray.get(refs[-1]))                          # {'sku-1': 2, 'sku-2': 1}
print(ray.get(cart("4812").add.remote("sku-3")))  # the same actor, found by name

Often confused with

Where it came from1973–2022

In 1973 Carl Hewitt, Peter Bishop and Richard Steiger proposed building artificial-intelligence programs from a single kind of object, the actor. Numbers, procedures and data structures would all be active agents that do nothing except react to messages, and the formalism had no need for goto, interrupts or semaphores.

Erlang reached the same shape from practical needs. From 1986 Joe Armstrong and colleagues at the Ericsson Computer Science Laboratory looked for a better way to program telephone exchanges, which juggle huge numbers of calls at once and must keep running while faulty parts are fixed. Erlang processes are lightweight, share nothing and talk by asynchronous messages, and the OTP libraries added supervision trees that restart failed processes. Armstrong’s history of the language never mentions Hewitt’s actors. Ericsson built its AXD301 switch this way and released Erlang as open source in December 1998.

Akka brought Erlang-style actors and supervision to the JVM in 2009. In 2014 Microsoft Research described Orleans, whose virtual actors are activated on demand and never explicitly created or destroyed, an idea Service Fabric and Dapr also use. Cloudflare’s Durable Objects (2020) sell the same model as a hosted service.

Why it’s built this way

Share nothing; communicate only by messages
Shared memory guarded by locks is hard to get right and doesn’t stretch across machines. A message works the same whether the receiver is in the same process or on another server, which is what lets an actor system spread over a cluster.
Handle one message at a time
Each actor’s code can be written as simple sequential code that updates its own state without locks. Parallelism comes from running many actors at once, not from threads inside one.
Let it crash, and let a supervisor restart it
In a telephone exchange some errors are inevitable. Erlang/OTP arranged processes in supervision trees, where each supervisor monitors its children and restarts them on failure, so error handling lives in one place and the system keeps running.
Virtual actors that always exist
The Orleans team found that popular actor platforms still left developers to handle placement, activation and failure themselves. Making actors logical entities that the runtime activates, places and removes took those problems off the developer.

Timeline

  1. 1973

    Hewitt, Bishop and Steiger propose the actor formalism at IJCAI

  2. 1986

    Work on Erlang begins at the Ericsson Computer Science Laboratory

  3. 1998

    Ericsson announces the AXD301 switch; Open Source Erlang released in December

  4. 2009

    Akka 0.5 brings Erlang-style actors to the JVM

  5. 2014

    Microsoft Research describes Orleans and its virtual actors

  6. 2020

    Cloudflare launches Durable Objects

  7. 2022

    Akka moves to the BSL; the Apache Pekko fork enters incubation

Sources22

Improve this page on GitHub