yogameleniawan/realtime-job-batching
A Laravel package for dispatching and monitoring batches of queued jobs in real time. Track batch progress, status, completion, and failures without building custom batching infrastructure, making long-running job workflows easier to observe and manage.
What the package actually is. yogameleniawan/realtime-job-batching is a thin convenience layer over two first-class Laravel capabilities: native job batching (Bus::batch() with then/catch/finally callbacks and the Batchable trait's progress() accessor) and the broadcasting subsystem. Its value-add is wiring batch lifecycle transitions to broadcastable events so a browser subscribed via Laravel Echo/Pusher can render a live progress bar instead of polling a status endpoint. That is an integration convenience, not infrastructure — an important framing for the buy-vs-build decision.
Architecture fit. Good fit for any conventional Laravel monolith or modular monolith running a Redis or database queue with broadcasting already configured. It sits at the boundary between the queue worker and the presentation layer, adds no new persistence, no new runtime, and no new protocol beyond the existing WebSocket channel. It does not attempt to replace Horizon, and should not be positioned as an observability tool — it emits UI-facing progress, not operational telemetry. Apps without broadcasting configured, or without a queue driver that supports batching (batching requires a database-backed batch store; sync and beanstalkd are not viable), get nothing from it.
Integration feasibility. Low engineering effort in the happy path, moderate effort to make production-grade. The install is a Composer require, a config publish for Pusher credentials, private-channel authorization in routes/channels.php, and a frontend Echo subscriber. The real work is not the install — it is the hardening: throttling broadcast volume, handling terminal-event loss, and confirming version compatibility against the Laravel major you run.
Technical risk. Concentrated in sustainability rather than correctness:
| Risk | Severity | Notes |
|---|---|---|
| Bus factor / abandonment | High | 24 stars, 0 dependents, single maintainer, composite score 0.24. No community validation, no downstream pressure to keep it current. |
| Laravel major drift | High | Batching internals and broadcast event conventions have shifted across Laravel 8→11. An unmaintained adapter is a future upgrade blocker. |
| Pusher coupling | Medium | Whether it broadcasts through Laravel's BroadcastManager (swappable to Reverb/Soketi/Ably) or binds the Pusher SDK directly determines exit cost. Must verify. |
| Broadcast volume / cost | Medium | One event per job completion is chatty for large batches; Pusher message quotas and connection limits become the scaling ceiling. |
| Progress semantics | Medium | Retries, allowFailures, and partial failures can produce double-counted or non-monotonic progress; out-of-order delivery across workers can make a bar jump backwards. |
| Terminal-event loss | Medium | If finally broadcast fails or the worker is killed, the UI hangs at 97% indefinitely with no recovery path. |
| Security surface | Low | Small, but channel authorization and not leaking job class names/counts to unauthorized subscribers must be confirmed. |
The high opportunity score (5.64) against a low composite (0.24) is the tell: the problem is real and recurring, the package is not differentiated. Realtime batch progress is a genuine gap in Laravel's DX — native batching gives you progress() but nothing push-based — but the implementation gap is roughly 100–200 lines of glue.
Key questions to resolve in a one-day spike.
Bus::batch() under the hood, or does it reimplement batching state?BroadcastManager (driver-agnostic) or hard-bind Pusher?allowFailures reflected in the emitted progress?queue:prune-batches, and Laravel Octane?Stack fit. Assumes Laravel with a batching-capable queue (Redis or database driver), a configured broadcasting stack, and a JS frontend with Echo. It is additive: no schema changes, no data migration, no change to how jobs are dispatched or retried. That is its strongest property from a delivery-risk standpoint — it can be adopted on a single workload and backed out by deleting one line.
Build-vs-buy position. Given the adoption signals, the recommended path is adopt-with-a-fork, not adopt-as-dependency. Concretely: run the spike against the upstream package, then either (a) pin the version and vendor it internally behind our own interface, or (b) reimplement the small surface we need directly on Bus::batch()->then()/finally() plus a single BatchProgressUpdated broadcast event. Option (b) is likely 1–2 engineer-days and eliminates the abandonment risk entirely; option (a) is faster if the package's frontend contract saves us meaningful work. Decide after the spike, not before.
Whichever we pick, do not let package types leak into application code. Define an internal seam:
BatchProgressBroadcaster interface with started(), progress(), finished(), failed().batchId, name, totalJobs, processedJobs, failedJobs, percentage, status, sequence, emittedAt. Treat this as a public contract with the frontend, independent of the vendor.Compatibility checks before committing. Laravel version matrix (pin to our major), PHP version, queue driver, broadcasting driver, Horizon coexistence, Octane safety (long-lived workers and broadcast state), and queue:prune-batches retention versus how long the UI needs to query historical batch state. If Reverb/Soketi is our target rather than hosted Pusher, confirm the package doesn't bypass the BroadcastManager.
Sequencing.
sequence guard, terminal-event watchdog, and a GET /batches/{id} polling fallback so a dropped socket self-heals.Maintenance. The dominant operational cost is ownership, not runtime. With one maintainer, zero dependents, and a sub-0.3 composite score, assume no upstream fixes: we own every future Laravel upgrade incompatibility. Mitigation is to fork into our org, pin the version, and budget roughly 2–4 hours per Laravel major for verification. Keep the fork's diff from upstream minimal so re-syncing remains possible if the project revives.
Support. No vendor SLA, no commercial backing, thin community. Support load falls entirely on the adopting team; document the event contract internally so a future engineer can debug the UI without reading package internals. Expect the most common support ticket to be "progress bar stuck" — pre-empt it with the polling fallback and a visible "last updated" indicator.
Scaling. Two independent ceilings. First, the queue: batch progress reads scale with worker count and job completion rate, which is a solved problem with Redis and Horizon. Second, and more binding, the broadcast plane: hosted Pusher pricing scales on concurrent connections and messages delivered. A 10,000-job batch emitting one event per job will burn quota fast and may trip rate limits, causing dropped or delayed events that look like UI bugs. Enforce throttling at the adapter layer — coalesce to at most one broadcast per N jobs or per fixed time window — and treat the raw per-job event as an anti-pattern. Set a documented batch-size threshold above which the UI degrades to polling.
Failure modes.
| Failure | User-visible symptom | Mitigation |
|---|---|---|
| Terminal event lost (worker killed, broadcast error) | Bar freezes below 100% forever | Watchdog timeout + polling reconciliation endpoint |
| Out-of-order delivery across workers | Bar jumps backwards | Monotonic sequence; client discards stale updates |
| Pusher quota/rate limit exceeded | Stuttering or silently missing updates | Server-side coalescing; alert on broadcast error rate |
| Retries re-increment processed count | Progress exceeds 100% or double-counts | Derive percentage from progress() snapshot, not client-side accumulation |
| WebSocket auth misconfiguration | Silent zero updates for all users | Integration test asserting channel authorization; smoke check in staging |
| Package incompatible after Laravel upgrade | Fatal on boot in production | Pin, fork, add a CI job against the next Laravel major |
| Batch records pruned before UI reads history | "Batch not found" on reload | Set pruning window > max UI retention; persist a summary row |
Ramp-up. Low for a Laravel-fluent engineer: roughly half a day to understand batching plus broadcasting, one day to build and harden the integration, and one to two days of frontend work for the Echo subscriber and progress component. The real cost is organizational — someone must own the fork and the event contract indefinitely. If the team cannot commit a named owner, that alone argues for building the ~150-line in-house version instead, where ownership is unambiguous and the upgrade path is entirely under our control.
Bottom line. Real problem, thin package, weak durability signals. Adopt it as a reference implementation and a fast path to a working demo, but put it behind an internal interface, plan to fork or replace it, and do not let it become load-bearing infrastructure.
Primary risks: maintainer abandonment (high), Laravel major drift (high), broadcast cost/volume at scale (medium). Recommendation: conditional — spike now, wrap in an owned seam, decide fork-vs-replace at the pilot exit gate.
How can I help you explore Laravel packages today?