ADR 0008: Worker Pool / Cluster Separation
Status
Accepted
Context
The original nexus-cluster (see ADR 0005) used OS-process isolation via Swoole\Process\Pool, Unix socket IPC, and php_serialize for all cross-worker messages. This introduced ~50 µs of serialization overhead per message hop.
Swoole 6.0 introduced SWOOLE_THREAD mode with Thread\Queue and Thread\Map primitives that allow PHP objects to be shared between threads without explicit serialization. The internal copy is handled by Swoole's thread engine.
These are two fundamentally different scaling models:
- Local thread-based: Multiple OS threads in the same process. Objects pass via
Thread\Queuecopies. No network or serialization overhead. - Remote node-based (future): Multiple machines connected via TCP. Requires a serialization wire format for cross-network message delivery.
The original nexus-cluster tried to serve both models in one package, creating unnecessary coupling and preventing optimal implementations for either.
Decision
Split into three packages with clear boundaries:
nexus-worker-pool — Pure PHP, local worker pool contracts and implementation.
WorkerNode: per-worker coordinator, routes via consistent hash ring.WorkerTransport/WorkerDirectory: envelope-based interfaces (no serializer).WorkerActorRef: cross-worker reference that sendsEnvelopeobjects directly.WorkerAsk*protocol: request/reply/cancel/ack control flow (see ADR 0007).InMemoryWorkerTransport/InMemoryWorkerDirectory: test doubles.- Depends only on
nexus-coreandnexus-runtime.
nexus-worker-pool-swoole — Swoole thread primitives. Requires ZTS PHP 8.5+ and Swoole 6.0+ compiled with --enable-swoole-thread.
ThreadQueueTransport:Thread\Queue-backed transport with adaptive-poll coroutine.ThreadMapDirectory:Thread\Map-backed shared actor directory.WorkerRunnable+WorkerPoolBootstrap+WorkerPoolApp: thread pool bootstrap.- Depends on
nexus-worker-pool,nexus-core, andnexus-runtime-swoole.
nexus-cluster — Stripped to remote contracts only. No implementation.
NodeAddress: multi-machine node identity (cluster/datacenter/application/node).ClusterTransport: TCP-level send/listen interface.NodeDirectory: actor path →NodeAddressmapping.NodeHashRing: consistent hash ring overNodeAddresslist.- Depends only on
nexus-core.
nexus-cluster-swoole (the Swoole process-based implementation) is deleted entirely. A future TCP cluster implementation will live in a new nexus-cluster-swoole package that implements the ClusterTransport and NodeDirectory contracts.
Consequences
Positive
- No serialization overhead for same-machine workers. Objects cross thread boundaries via
Thread\Queue's internal copy mechanism. - Clean architectural boundary: local scaling (worker pool) vs remote scaling (future cluster) have independent packages with independent interfaces.
nexus-worker-poolhas no Swoole dependency — usable with any PHP runtime that supports threads.nexus-clusteris a thin contracts package — easy to depend on without pulling in Swoole.
Trade-offs
- The
WorkerAsk*protocol mirrors the oldRemoteAsk*naming; they are equivalent protocols. When the TCP cluster is built, it will use a similar protocol underClusterAsk*or equivalent naming. WorkerActorRefdoes not enforce#[MessageType]for actual serialization — the Psalm ruleNonSerializableRemoteMessageRuleis kept as a convention check for forward compatibility when messages may eventually cross a serialization boundary.