Documentation Index
Fetch the complete documentation index at: https://docs.goakt.dev/llms.txt
Use this file to discover all available pages before exploring further.
Why a fixed dispatcher pool instead of a goroutine per actor?
Earlier versions ran each active actor on its own drainer goroutine. v4.2.1 replaced that with a fixed worker pool — typicallymax(GOMAXPROCS, 2) workers — that cooperatively multiplexes the entire actor population, following the Akka / Pekko / Erlang / Orleans pattern.
Trade-offs driving the change:
- Goroutine count decoupled from actor count. Systems with 100k actors no longer allocate 100k goroutine stacks.
runtime.NumGoroutine()stabilises atworkerCount + O(1). - Fairness by construction. A per-turn throughput budget (configurable via
WithThroughputBudget, default 32) bounds how long one busy actor can hold a worker before yielding — peers aren’t starved by a hot mailbox. - Better CPU cache use. When a worker processes several messages for the same actor back-to-back, the CPU keeps that actor’s mailbox, state, and
Receivecode in its fast on-chip memory (CPU cache). Rotating to a different actor on every message would force the CPU to reload everything from main memory each time — measurably slower. - Control-plane priority preserved. Every actor carries a dedicated system mailbox drained before the user mailbox on every turn, so
PoisonPill, supervision, passivation, and termination messages cannot queue behind a user-message backlog.
PreStart / PostStop contract, reentrancy, panic recovery, and supervision all hold — the three-state Idle / Scheduled / Processing CAS on each PID enforces the single-consumer invariant the mailbox contract depends on. See Dispatcher Pool for the full design.
Why any instead of proto.Message (v4)?
v4 allows any type as actor messages. Benefits:
- Flexibility — Plain Go structs, protobuf, or custom types
- Simplicity — No mandatory .proto definitions for simple cases
- CBOR — Efficient serialization for arbitrary Go types
Why a custom TCP frame protocol?
GoAkt uses length-prefixed binary frames over TCP instead of gRPC:- Low overhead — No HTTP/2, HPACK, or stream multiplexing
- Control — Connection pools, compression, buffer pooling tuned for actor traffic
- Fewer dependencies — Leaner than gRPC
Why Olric for cluster state?
Cluster state (actor/grain placement) needs replication. Olric provides:- Embedded — No external database
- Distributed hash map — Configurable quorum for consistency
- Memberlist — Same membership layer as the cluster
Why CRDTs in addition to Olric?
Actor and grain registry entries are strongly consistent via Olric quorums. Application data that must stay available under partition and merge without a single writer uses CRDTs in thecrdt package, replicated by a dedicated Replicator actor. Deltas fan out over the existing topic bus—separate concern from registry reads and writes. See Distributed data.
Why reactive streams on top of actors?
Thestream package composes Source → Flow → Sink graphs that materialize as actors per stage, inheriting supervision, lifecycle, and demand-driven backpressure instead of unbounded queues. Streams are optional; they do not replace Tell/Ask. See Streams.
Why a tree-based actor hierarchy?
Mirrors Erlang/OTP and Akka:- Lifecycle ordering — Stopping a parent stops descendants first (depth-first)
- Scoped supervision — Parent defines failure policy for children
- Namespacing — Addresses reflect tree path; no name collisions
Why separate Actor and Grain?
- Actors — Explicit spawn/stop; caller controls lifecycle. Best for services and infrastructure.
- Grains — Identity-addressed; framework manages activation and passivation. Best for entity-per-identity patterns.