Open-Source Wikis

/

Temporal

/

Temporal Server

/

Architecture

temporalio/temporal

Architecture

Active contributors: wxing1292, alex, yichaoyang

This page describes how the four Temporal services fit together, what data they exchange, and how a single workflow execution moves through the system. It is the wiki's starting point for engineers who need to reason about the server end to end.

The four services

graph LR
    subgraph user[User-hosted]
      App[App / SDK Client]
      Worker[Worker process]
    end

    subgraph cluster[Temporal Cluster]
      FE[Frontend]
      H[History<br/>sharded]
      M[Matching<br/>sharded by task queue]
      W[Internal Worker]
    end

    subgraph store[Storage]
      DB[(Persistence)]
      Vis[(Visibility)]
    end

    App -->|StartWorkflow / Signal /<br/>Update / Query| FE
    Worker -->|PollWorkflowTask /<br/>PollActivityTask| FE
    Worker -->|RespondWorkflowTaskCompleted /<br/>RespondActivityTaskCompleted| FE

    FE -->|StartWorkflowExecution<br/>(by workflow id)| H
    FE -->|PollWorkflowTaskQueue| M
    M <-->|AddWorkflowTask /<br/>AddActivityTask| H
    H --> DB
    H --> Vis
    M --> DB
    W -->|internal SDK<br/>via Frontend| FE

Each service is wired up by a top-level fx module in temporal/fx.go and started from cmd/server/main.go. Service membership and host-shard ownership are tracked via a Ringpop gossip ring; see common/membership/.

Service Sharded by Stateful? Default port
Frontend none (stateless) no 7233 / 7243
History shard ID yes 7234
Matching task-queue name partial 7235
Worker none no 7239

Stateful here means "owns leases on durable shards"; see Membership.

Workflow lifecycle in one diagram

A normal workflow that runs an activity and a timer:

sequenceDiagram
    participant App
    participant Worker
    participant FE as Frontend
    participant H as History (shard)
    participant M as Matching
    participant DB as Persistence

    App->>FE: StartWorkflowExecution
    FE->>H: StartWorkflowExecution
    H->>DB: append history events,<br/>create transfer task
    H-->>FE: ok
    FE-->>App: ok

    Note over H: Transfer-queue executor<br/>processes the task
    H->>M: AddWorkflowTask
    Worker->>FE: PollWorkflowTaskQueue
    FE->>M: PollWorkflowTaskQueue
    M-->>Worker: WorkflowTask

    Worker->>FE: RespondWorkflowTaskCompleted<br/>(commands: ScheduleActivityTask, StartTimer)
    FE->>H: RespondWorkflowTaskCompleted
    H->>DB: append events,<br/>create transfer + timer tasks

    H->>M: AddActivityTask (transfer task)
    Worker->>FE: PollActivityTaskQueue → ActivityTask
    Worker->>FE: RespondActivityTaskCompleted
    FE->>H: RespondActivityTaskCompleted
    H->>DB: append events

Two queue families inside History drive the asynchronous half:

  • Transfer queue — handles the transactional-outbox pattern between History and Matching. Its tasks dispatch Workflow / Activity tasks via gRPC to Matching, update visibility, etc.
  • Timer queue — handles deferred work (workflow sleeps, schedule-to-start timeouts, retry backoffs).

Both are ordered queues inside the History shard, materialised in the persistence backend's executions table family, processed by goroutines started in service/history/queue_factory_base.go and the executors in service/history/transfer_queue_active_task_executor.go and service/history/timer_queue_active_task_executor.go.

Shards and ownership

The unit of horizontal scaling is the History shard. The number of shards is fixed at cluster creation. Every workflow ID is hashed to a shard, and a shard is owned by exactly one History host at a time; ownership transfers via a generation number called RangeID (a RangeID mismatch fences out stale writes).

flowchart LR
    NS[NamespaceID + WorkflowID] -->|hash mod NumShards| Shard
    Shard -->|Ringpop ring| HistoryHost
    HistoryHost -->|owns lease| Persistence

Matching shards are simpler: each (namespace, task-queue, partition) triple is owned by one Matching host at a time, again via Ringpop.

See common/membership/ and service/history/shard/ for the implementation.

State sources for a workflow execution

For every workflow execution, three kinds of state are persisted, all atomically updated through History's persistence interface:

State kind Where it lives What it contains
Workflow History history_node / history_tree tables — see schema/cassandra/temporal/schema.cql Append-only event log; what the workflow has done.
Mutable State executions row, loaded into MutableStateImpl A summarised snapshot used to make decisions without replaying all events.
History Tasks Per-shard internal task queues (transfer, timer, visibility, archival, replication, outbound) The "what to do next" inbox for the shard's queue processors.

Mutable State could in principle be reconstructed by replaying History Events, but the cost would be prohibitive — the summary is itself persisted and lives in an LRU cache workflow.Cache. Consistency between History Events, Mutable State, and tasks is provided by writing them in the same persistence transaction; see Consistency guarantees in History service.

Cross-cutting frameworks

A few internal frameworks deserve their own pages because they cross service boundaries:

  • CHASM (chasm/) — generalised, registry-based state-machine runtime. Workflow, Scheduler, and NexusOperation are CHASM Libraries. See systems/chasm.md.
  • HSM (service/history/hsm/) — the hierarchical state-machine machinery used by callbacks and Nexus operations inside History.
  • Persistence (common/persistence/) — pluggable storage interface with Cassandra, MySQL, PostgreSQL, and SQLite implementations behind the same Go interfaces.
  • Replication (service/history/replication/) — multi-cluster (XDC) replication of namespaces and history events.

Lifecycle of a server process

graph TD
    Start[cmd/server/main.go: buildCLI]
    Load[temporal/fx.go: load config + dynamicconfig]
    Auth[authorization.GetAuthorizer]
    NewSrv[temporal.NewServer]
    StartFx[fx graph starts each service]
    Ringpop[membership ring joins]
    Shards[History acquires shards]
    Ready[Frontend accepts traffic]

    Start --> Load --> Auth --> NewSrv --> StartFx --> Ringpop --> Shards --> Ready

The dependency graph is constructed with Uber fx; the top-level provider list is in temporal/fx.go. Each service has its own fx.go (e.g. service/history/fx.go, service/matching/fx.go) that depends on shared providers from common/.

Further reading inside this wiki

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Architecture – Temporal wiki | Factory