🚀 Oveview
This release brings multi-datacenter support, a high-performance protobuf-over-TCP remoting stack, and important fixes for channel pooling, timeouts, and data races. It also adds an option to disable grain relocation and several allocation and lock optimizations across the hot paths.
✨ What's New
🌐 Multi-datacenter support
- DC-transparent messaging — actors and grains can communicate across datacenters without changing your code.
- Pluggable control plane — use NATS JetStream or Etcd for coordination.
- DC-aware placement — spawn actors in a specific datacenter with
SpawnOnand theWithDataCenteroption. - See the
datacenterpackage andWithDataCenterfor details.
🛡️ Grain relocation control
WithGrainDisableRelocation— disable actor/grain relocation when you don’t need it (e.g. stateless actors or short-lived grains).
🐛 Bug Fixes
| Area | Fix |
|---|---|
Shutdown |
preShutdown no longer builds or persists peer state when relocation is disabled (WithoutRelocation), so shutdown proceeds correctly and avoids unnecessary cluster work. |
Channel pool |
Fixed channel-pool poisoning in GrainContext.NoErr(): sending on both response and error channels for sync calls could corrupt the pool under scheduling races. |
Timeouts |
Fixed localSend timeout/cancel paths returning channels to the pool while the grain goroutine could still write, preventing stale values in later requests. |
Data race |
Fixed race in PID.recordProcessedMessage() by replacing the runtime type assertion on passivationStrategy with an atomic.Bool set at init. |
⚡ Performance & Remoting
🌐 New remoting stack (protobuf over TCP)
ConnectRPC/HTTP-based remoting is replaced with a protobuf-over-TCP server and connection-pooling client:
- Multi-loop TCP accept with
SO_REUSEPORT,TCP_FASTOPEN, andTCP_DEFER_ACCEPTfor lower connection-setup latency and kernel-level load balancing. - Sharded
WorkerPoolfor connection dispatch, reducing contention under high concurrency. - Length-prefixed wire protocol with dynamic protobuf type dispatch via the global registry (no per-service stubs or HTTP path routing).
FramePoolwith power-of-two bucketedsync.Pool(256 B – 4 MiB) for read buffers and frame slices to cut heap allocations and GC pressure.- LIFO connection pool in the TCP client with lazy stale-connection eviction and no background goroutines.
- Pluggable
ConnWrappercompression (Zstandard, Brotli, Gzip) on both client and server.
♻️ Pooling & allocation
- Bounded channel-based pools instead of
sync.PoolforReceiveContext,GrainContext, and response/error channels on theTell,Ask,SendAsync, andSendSynchot paths to avoid cross-P thrashing and madvise overhead. PID.latestReceiveTimeNanoswitched fromatomic.Timetoatomic.Int64(Unix nanoseconds) to remove ~24-byte interface allocations per message.- Cached
Address.String()andGrainIdentity.String()to avoid repeatedfmt.Sprintfon every message. - Mailbox nodes in
UnboundedMailboxandgrainMailboxuse plain pointers instead ofatomic.Pointeron enqueue/dequeue. - Inlined
deferclosures in Ask-path functions (PID.Ask,actor.Ask,handleRemoteAsk, grainlocalSend) to remove per-call closure allocations.
🔓 Locking & contention
ActorOfdoes local actor lookup before taking the system-wideRWMutex;SendAsync/SendSyncbypass thePID.ActorSystem()getter lock, reducing read-lock acquisitions from three to one on the local path.passivationManager.Touchcoalesced via an atomic timestamp guard (lastPassivationTouch), so mutex contention drops from once-per-message to at most once per 100 ms.
📦 Upgrade notes
- Remoting now uses the new TCP-based stack by default. No API changes are required for typical usage.
- Use
WithGrainDisableRelocationwhen you want to skip relocation for certain grains. - For multi-DC setups, use the
datacenterpackage andWithDataCenterwithSpawnOn.
🔗 Pull requests
Features & refactors
- ✨ Multi-datacenter capabilities — @Tochemey in #1085
- 🔄 Datacenter implementation refactor — @Tochemey in #1092
- ⚡ Performance optimisations — @Tochemey in #1094
- 📚 Comprehensive documentation — @Tochemey in #1096
Dependencies & tooling
- chore(deps):
github.com/zeebo/xxh3→ v1.1.0 — @renovate[bot] in #1088 - chore(deps):
actions/checkout→ v6.0.2 — @renovate[bot] in #1086 - chore(deps):
golangci/golangci-lint→ v2.8.0 — @renovate[bot] in #1087 - chore(deps):
github.com/redis/go-redis/v9→ v9.17.3 — @renovate[bot] in #1089 - chore(deps):
github.com/bufbuild/buf→ v1.64.0 — @renovate[bot] in #1091 - chore(deps): Go → v1.25.6 — @renovate[bot] in #1090
- chore(deps):
github.com/klauspost/compress→ v1.18.4 — @renovate[bot] in #1093 - chore(deps):
github.com/bufbuild/buf→ v1.65.0 — @renovate[bot] in #1098
Full Changelog: v3.13.0...v3.14.0