Cluster
Most of Effect lets you build a single process. Cluster lets you take stateful services and spread them across many machines — without changing the way you write your business logic. You define an entity (a sharded, addressable actor), give it an RPC protocol, and the cluster routes every message for a given id to exactly one live instance somewhere in the fleet. State lives in memory while the entity is active, and is recreated on demand after idle passivation or failover.
The cluster modules are unstable and import from effect/unstable/cluster. The
transport and storage wiring lives in the platform packages — for Node you assemble
a cluster with NodeClusterSocket.layer from @effect/platform-node.
import { Effect, Ref, Schema } from "effect"import { Entity } from "effect/unstable/cluster"import { Rpc } from "effect/unstable/rpc"
// 1. Describe the messages the entity can handle, as RPCs.const Increment = Rpc.make("Increment", { payload: { amount: Schema.Number }, success: Schema.Number})
// 2. Pair a stable entity-type name with its RPC protocol.const Counter = Entity.make("Counter", [Increment])
// 3. Implement the handlers. State (here a Ref) lives for as long as the// entity instance is active on its owning runner.const CounterLayer = Counter.toLayer( Effect.gen(function*() { const count = yield* Ref.make(0) return Counter.of({ Increment: ({ payload }) => Ref.updateAndGet(count, (n) => n + payload.amount) }) }))
// 4. From anywhere in the cluster, get a client for a specific entity id and// send it a message. Routing to the live instance is handled for you.const program = Effect.gen(function*() { const clientFor = yield* Counter.client const total = yield* clientFor("counter-123").Increment({ amount: 1 }) yield* Effect.log(`counter-123 is now ${total}`)})Why entities?
Section titled “Why entities?”An entity is the cluster-facing version of a stateful service. Two properties make it different from a plain service:
- Single owner per id. For any entity id (
"counter-123"), the cluster guarantees that messages are processed by a single live instance at a time, on whichever runner currently owns that id’s shard. You get serialized access to in-memory state without locks. - Location transparency. Callers never address machines. They ask for a client by entity id and send typed RPCs; the cluster handles routing, failover, and passivation (stopping idle entities and recreating them on the next message).
Because the protocol is described with RPC and Schema, every message is type-safe end to end and can be serialized for the wire or persisted to durable storage.
The pieces of a cluster
Section titled “The pieces of a cluster”A running cluster is a handful of cooperating modules. You usually only touch
Entity and the platform layer that bundles the rest, but it helps to know what is
underneath:
- Entity (
Entity) — the addressable actor and its RPC protocol. You define the messages, implement the handlers (holding in-memory state), and obtain a client by id. See Entities. - Sharding (
Sharding,ShardingConfig) — the runtime service. It hashes entity ids into shard ids within a shard group, places those shards on a hash ring of healthy runners, and routes each message to the runner that currently owns the target shard. See Sharding & runners. - Runners & transport (
SocketRunner,HttpRunner,SingleRunner,TestRunner) — the per-process node and the wire protocol between nodes. The platform packages bundle a transport:NodeClusterSocket.layer(raw TCP) and the HTTP/WebSocket runners for HTTP deployments.SingleRunnerruns the whole cluster in one process;TestRunnerruns it in memory for tests. See Sharding & runners. - Storage (
MessageStorage,RunnerStorage) — where the cluster keeps its coordination state and (optionally) durable mailboxes. The SQL-backed layers (SqlMessageStorage,SqlRunnerStorage) give you durability across restarts; the in-memory layers are for development and tests. See Message storage. - Singleton & ClusterCron (
Singleton,ClusterCron) — register work that should have exactly one owner across the whole fleet, with ownership following shard placement so it fails over automatically. See Singletons & cron.
The boundary that matters most is volatile vs persisted messaging: with the in-memory storage layers a message lives only as long as the process, while with the SQL-backed layers a message is written to durable storage before it is acknowledged, so it survives a runner crash and is redelivered on failover. That trade-off, and how to opt into persistence per message, is covered in Message storage.
In this section
Section titled “In this section”- Entities — Define an entity’s RPCs, implement handlers that keep in-memory state, send messages from a client, and wire everything into a runnable cluster (including an in-memory test setup).
- Sharding & runners — How
Shardinghashes ids into shards and places them on runners, theShardingConfigknobs, and the available runner transports (SocketRunner,HttpRunner,SingleRunner,TestRunner, plusNodeClusterSocket). - Singletons & cron — Run an effect with a
single fleet-wide owner using
Singleton, and schedule clustered recurring work withClusterCron. - Message storage — Volatile vs persisted
messaging,
MessageStorage/RunnerStorage, and the SQL-backed durable layers. - Entity proxies — Expose an entity over an external
protocol (e.g. an HTTP/RPC server) with
EntityProxy/EntityProxyServer. - Reference — The remaining modules at a glance:
addresses and ids (
EntityAddress,EntityId,RunnerAddress,ShardId,SingletonAddress),Envelope/Message/Reply,Snowflake,ClusterError,ClusterMetrics, and more.
Related modules
Section titled “Related modules”Beyond the pieces above, the effect/unstable/cluster namespace also ships
lower-level building blocks you reach for less often:
Snowflake— the time-ordered unique id generator used for message ids.ClusterMetrics— predefined metrics for shard counts, message rates, and storage activity.ClusterWorkflowEngine— runs the Workflow engine on top of the cluster so durable workflows execute across the fleet.
Cluster builds directly on RPC, Schema, Services & Layers, and the Platform packages — read those first if any of the snippets here are unfamiliar.