webpatser/torque
Torque is a lightweight Laravel package by webpatser that offers a clean, fluent API for common application tasks. It integrates smoothly into existing Laravel projects and is easy to install and configure, making it a practical choice for small projects and rapid prototyping.
Torque is a Redis Streams queue driver for Laravel with Fiber-based concurrency. The shortest path from zero to a confident decision:
composer require <vendor>/torque
php artisan vendor:publish --tag=torque-config
config/queue.php alongside your existing redis connection (don't overwrite it — you'll want it for comparison):
'torque' => [
'driver' => 'torque',
'connection' => 'default',
'queue' => 'default',
'serializer' => env('TORQUE_SERIALIZER', 'json'),
// concurrency / group / block settings per config/torque.php
],
BENCHMARKS.md including its "what this benchmark does NOT measure" section. These are written to let you self-select out, which is the single most valuable thing in the docs.php artisan torque:start # prints "Serializer: igbinary" if enabled, or an install hint if the ext is missing
php artisan torque:status
php artisan torque:bench --use-running-master --workload=async-io
Your first real use case should be async I/O fan-out: handlers that spend their time waiting on HTTP, Redis, or a socket — exactly the async-io and fanout profiles where the release claims up to 15x throughput and 95% lower memory versus queue:work. If your jobs are CPU-bound or dominated by blocking SQL, benchmarking first will save you a migration.
Keep handlers Fiber-friendly — this is the whole ballgame. The README's new "Working with databases (avoid the Eloquent trap)" section is required reading. Eloquent "works," but a blocking PDO call parks the Fiber and collapses your fan-out back to serial behaviour. Two patterns:
// Pre-fetch at dispatch time — keeps the handler pure and Fiber-safe
ProcessOrder::dispatch($order->only(['id', 'total', 'customer_id']));
// Or route reads through the pool inside the handler
public function handle(MysqlPool $pool): void
{
$row = $pool->selectOne('select * from orders where id = ?', [$this->orderId]);
// ...
}
Pick the serializer per workload, not globally. JSON stays the default and should stay your default in dev — redis-cli XRANGE remains readable, which is worth more than throughput on small payloads where igbinary shows "no measurable difference." Flip TORQUE_SERIALIZER=igbinary only for queues carrying large payloads, where encode/decode roughly halves. Because the decode path sniffs the leading byte, you can flip it on a running cluster and let in-flight JSON messages drain organically — treat that as your deployment story rather than a coordinated restart.
Use torque:bench as a CI gate, not a one-off. The --json=path (or --json=- for stdout) schema is designed for diffing:
php artisan torque:bench --use-running-master --workload=mixed --json=storage/bench/$(date +%F).json
Commit the JSON alongside the change that caused it. Throughput regressions from a concurrency tweak are otherwise invisible until production.
Mirror the Horizon kit when you need to justify the choice. bench/horizon-comparison/ gives you HorizonBenchJob, a standalone run-horizon-bench.php that auto-detects the host app from cwd, and a README. Run both sides on your hardware instead of quoting BENCHMARKS.md in a design review — the numbers are host-specific and reviewers know it.
Tune concurrency to the workload profile, not to a single global number. The seven profiles (cpu, io, async-io, fanout, mixed, payload-small, payload-large) exist precisely because the optimum diverges; a Fibre count that wins on async-io will oversubscribe on cpu.
Surface live job progress as a first-class UX feature. It's positioned in this release as a differentiator versus Horizon; if you're migrating, wire progress broadcasting into your handlers early rather than bolting it on later.
StreamJob::__construct with a TypeError on the readonly array payload property when the body is binary — jobs pile up and the tail times out with no obvious cause. The bug was incidentally hidden because the JSON path was the only one exercised.decodePayload/isValidPayload sniff both formats, so workers won't drop binary blobs as "corrupt" (the old json_validate check in WorkerProcess::processMessage did exactly that). But a queue with mixed formats is unreadable in redis-cli; when debugging, check the first byte ({, [, or \x00\x00\x00\x02) before assuming corruption.serializer must reach the connection config, not just config/torque.php. StreamConnector::connect() reads 'serializer' off the queue connection array. If you set the env var but your queue.php entry doesn't reference it, you'll silently stay on JSON.--use-running-master. Self-spawning workers land in v1.1; expect a hard failure (not a fallback) if you forget to start a master first.BenchProbe is wired but the serialize / xadd / xreadgroup / unserialize / handle / xack breakdown is explicitly v1.1. Today you get throughput, p50/p95/p99 end-to-end latency, and median handle duration — useful for spotting a regression, not for attributing it.composer.json only suggests ext-igbinary. Nothing enforces it; a deploy to a host without the extension leaves you on JSON (with a torque:start hint) rather than failing — check the startup line in your process supervisor logs to confirm which serializer is actually live.StreamQueue::encodePayload / transcodeIncomingPayload (private) and decodePayload / isValidPayload (public static) are the seam if you ever need a third format; the Bench* console classes under src/Console/Bench/ are live and aggregator-driven, so custom profiles are a plausible fork point.How can I help you explore Laravel packages today?