temporalio/temporal
Membership
Active contributors: david, alex, yichaoyang
Purpose
A Temporal cluster is a herd of stateless and stateful processes. Stateful work — owning a History shard, owning a Matching task-queue partition — has to be assigned to exactly one host at any moment. Membership is how the cluster does this assignment.
The mechanism: a gossip ring built on top of Ringpop, forked from Uber's original. Hosts join the ring on startup, gossip their liveness, and vote on which host owns each (service, key) pair.
Directory layout
common/membership/
├── monitor.go # Membership monitor interface
├── ringpop_*.go # Ringpop-backed implementation
├── service_resolver.go # Resolves a key to a host within a service
├── static/ # Static (non-Ringpop) implementation for tests / single-node
└── ...Key abstractions
| Type | What it does |
|---|---|
Monitor |
Watches the ring; emits events when the membership changes. |
ServiceResolver |
Maps a key (e.g. shard ID, task-queue name) to the owning host within one service. |
Hosts (static) |
A statically-configured list of hosts. Used in tests and for single-host clusters. |
How it works
graph LR
HostA[History host A] -.gossip.- HostB[History host B]
HostA -.gossip.- HostC[History host C]
HostB -.gossip.- HostC
Cluster[Cluster] --> Ring[Hash ring]
ShardKey[ShardID 17] -->|hash| Ring
Ring --> HostB
HostB -->|owns shard| Lease[acquire shard, RangeID++]The hash function used to map a key to a host is consistent: when one host leaves the ring, only the keys it owned move; the rest stay where they were. This keeps shard-handoff churn low during routine deploys.
A consequence of consistent hashing is that ownership is eventually unique — there is a window during which two hosts may both think they own a key. This is why every History shard has a RangeID fence that the new owner increments on takeover; older owners' writes hit *persistence.ShardOwnershipLostError and bail out.
Service-specific use
- History uses the resolver to assign shards to hosts. The
ShardController(service/history/shard/) reacts to membership change events by acquiring or releasing shards. - Matching uses the resolver to assign
(namespace, task-queue, partition)triples to hosts. - Frontend uses the resolver only for the cluster's other services (it's stateless itself).
- Worker is stateless; multiple hosts run the same system workflows.
Static membership
For unit tests and single-node deployments the static resolver in common/membership/static/ hard-codes the ring contents. Used by the in-process cluster the tests/ functional tests spin up.
Entry points for modification
- Changing the hash function: discouraged, breaks compatibility with running clusters.
- Adding a new "service" to the ring: add the role name to
common/primitives/role.go, wire the new service intotemporal/fx.go, and resolve viaMonitor.GetResolver. - Inspecting ownership:
tdbg shard list-shardsshows the owner of each History shard. The same data is exposed by the admin gRPC API.
Related pages
- History service — primary consumer.
- Matching service — partition ownership.
- Architecture overview — shows the ring in context.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.