Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Cluster Laravel Package

amphp/cluster

amphp/cluster lets PHP applications scale across multiple processes and machines, providing cluster nodes, workers, message passing, job distribution, and synchronization primitives on top of Amp for building resilient distributed systems.

Deep Wiki
Context7

Getting Started

composer require amphp/cluster — it is a standalone Amp v3 / Revolt library, not a Laravel package. You need PHP 8.1+, amphp/amp ^3, amphp/socket, amphp/process, and a Unix-like host (the IPC and socket-sharing pieces lean on Unix domain sockets; Windows is not a realistic target).

Read in this order: the README example, src/ClusterConfig.php (the knobs you actually tune), src/Cluster.php (the facade you call), then examples/. The API surface is small enough to read end-to-end in under an hour, which is the right investment given the low star count.

Minimal setup — a config file and a single entrypoint that is both master and worker:

// cluster.php
use Amp\Cluster\ClusterConfig;

return (new ClusterConfig)
    ->withWorkerCount(4)
    ->withSocketPath(sys_get_temp_dir() . '/myapp-cluster.sock');
// server.php
use Amp\Cluster\Cluster;
use function Amp\async;

require __DIR__ . '/vendor/autoload.php';

$cluster = new Cluster(__DIR__ . '/cluster.php');
$cluster->start(); // master spawns workers; workers fall through

$server = Cluster::listen('0.0.0.0:8080');

while ($socket = $server->accept()) {
    async(function () use ($socket) {
        $socket->write('worker ' . Cluster::getWorkerId() . "\n");
        $socket->end();
    });
}

Run php server.php, hit port 8080 repeatedly with nc, and confirm different worker IDs answer. That is the first use case worth validating: a single listening port whose connections are distributed across cores by the OS, with no sticky-session or load-balancer work on your side.

Implementation Patterns

Shared listener, per-connection coroutine. Cluster::listen() is called identically in every worker; the cluster arranges for the socket to be shared so the kernel distributes accepts. Keep the accept loop thin and push all per-client work into async(). Anything blocking (sync file I/O, sleep, heavy CPU) stalls that worker's entire event loop, so it must go to a separate process or be chunked.

Treat workers as disposable, stateless processes. There is no shared memory. In-memory caches, counters, and rate limiters are per-worker and silently inconsistent. Put shared state in Redis, the database, or another external store, and accept that a worker can die at any time.

Use the message bus for coordination, not data transfer. Register Cluster::onMessage(...) in every worker, then use targeted sends or broadcast. Messages are serialized, so closures, resources, and PDO handles cannot cross. Keep payloads small (IDs, commands, small JSON blobs) and reconstruct state on the receiving side. Use it for cache invalidation, "reload config", leader election, and broadcast fan-out — not for shipping large buffers between workers.

Integrate with Laravel as a sidecar process, not inside it. Run this under Supervisor/systemd as its own service, next to php artisan queue:work and Octane. Bridge through Redis pub/sub, the database, or a small HTTP call. If you must touch Eloquent, bootstrap the container once at worker start, then call DB::purge() / DB::reconnect() after forks or long idle periods — long-lived processes and Laravel's connection handling are a classic source of "MySQL server has gone away". Do not bootstrap a fresh Laravel app per message; that is exactly the cost you are trying to avoid.

Combine with amphp/http-server for a multi-core HTTP service. Cluster gives you the process model; amphp/http-server gives you routing and middleware. This is the natural pairing for an internal API, an SSE hub, or a WebSocket fan-out service that reads from Redis.

Handle shutdown deliberately. Trap SIGTERM/SIGINT (e.g. Amp\trapSignal([SIGTERM, SIGINT])) in each worker, stop accepting, drain in-flight coroutines with a timeout, then exit. Supervisor's stopwaitsecs must exceed your drain timeout, otherwise you will cut connections on every deploy.

Use the file watcher in development only. Cluster::watch(__DIR__ . '/src') (guarded by an env check) gives you restart-on-change; never enable it in production.

Test with a worker count of one. Factor the per-connection logic into a plain function and unit-test it directly. Reserve multi-worker tests for integration runs, because failures are non-deterministic and hard to reproduce.

Gotchas and Tips

It is not a job queue. There is no acknowledgement, retry, persistence, or backpressure protocol built into the message bus. If a worker dies mid-message, that message is gone. Use a real queue (Redis streams, SQS, Laravel queues) for anything that must not be lost, and keep the cluster bus for ephemeral coordination.

Worker code is re-executed from the same entrypoint. Anything with side effects at file scope (opening sockets, registering signal handlers, writing files) runs once in the master and again in every worker. Guard master-only setup or move it into the config file. Relatedly, a config change requires a full restart; hot-reloading config in a running cluster is not a thing.

Version and API churn is the real risk. The package is at 61 stars with a single recent release, and Amp moved from v2 to v3 with breaking changes across the ecosystem. Pin the exact version, avoid mixing Amp v2 and v3 packages in one project, and be prepared to read src/ or fork — the API is small enough that forking is a weekend, not a quarter. Treat the opportunity score as a signal: this is infrastructure you adopt because you already run Amp, not because you want to adopt Amp.

Serialization errors surface late. A closure or a DateTime with a resource inside will fail at send time, often only under load in one worker. Write a small assertSerializable helper or restrict messages to arrays of scalars, and log the source worker ID on both send and receive.

Logging from N processes interleaves unpredictably. Prefix every line with Cluster::getWorkerId() and the PID, and prefer per-worker log files or structured lines to stderr for Supervisor to capture. Without the prefix, cross-process debugging is miserable.

Memory leaks are amplified. Every worker is a long-running PHP process holding every object it has ever touched. Watch memory_get_usage() per worker, avoid accumulating listeners or unbounded arrays, and consider a supervisor-enforced restart cadence (e.g. recycle workers nightly) as a safety net. Opcache and preloading apply per process — verify the worker code matches the master after a deploy, since stale opcache across a restart is a subtle failure mode.

Unix socket path issues bite early. ClusterConfig::withSocketPath() needs a directory the user can write, a path shorter than the ~104-character sun_path limit, and cleanup of stale socket files after a hard kill. If workers fail to connect to the master, check that path and its permissions before anything else.

Config path resolution is relative to the current working directory. Always pass an absolute path (__DIR__ . '/cluster.php') to new Cluster(...), because Supervisor and cron invoke your script from arbitrary directories. Environment variables are inherited by workers, so .env loading in the master propagates naturally — but only if the master loads it before start().

Debug with workerCount = 1 first. Half of all cluster bugs are logic bugs that happen to be masked by concurrency. Once a single-worker run is clean, scale up and use getWorkerId() in every log line to reconstruct ordering.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource