PHPackages                             kamranata/queue-sql - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. kamranata/queue-sql

ActiveLibrary[Database &amp; ORM](/categories/database)

kamranata/queue-sql
===================

Queue any Laravel write query (delete/update/insert) across parallel batched jobs.

v1.4.0(1mo ago)2822[1 issues](https://github.com/kamranata/queue-sql/issues)MITPHPPHP ^8.1CI passing

Since Jul 8Pushed 1mo agoCompare

[ Source](https://github.com/kamranata/queue-sql)[ Packagist](https://packagist.org/packages/kamranata/queue-sql)[ Docs](https://github.com/kamranata/queue-sql)[ RSS](/packages/kamranata-queue-sql/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (4)Dependencies (10)Versions (6)Used By (0)

[![queue-sql](art/logo.png)](art/logo.png)

[![Build Status](https://github.com/kamranata/queue-sql/actions/workflows/tests.yml/badge.svg)](https://github.com/kamranata/queue-sql/actions)[![Total Downloads](https://camo.githubusercontent.com/0d8890024f5646edb677c733de309efd3d6d60a7772c448e081ebc8c5895bc6c/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6b616d72616e6174612f71756575652d73716c)](https://packagist.org/packages/kamranata/queue-sql)[![Latest Stable Version](https://camo.githubusercontent.com/670e05ad4a92b67c62dd228c6773a99609cd58ecc8975173e16f1703871bc3a2/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6b616d72616e6174612f71756575652d73716c)](https://packagist.org/packages/kamranata/queue-sql)[![License](https://camo.githubusercontent.com/0d3b0ac8211381e7677637f4645497986eff8a39b99ea5a6d5c9b6e9f6cbf00c/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6b616d72616e6174612f71756575652d73716c)](https://packagist.org/packages/kamranata/queue-sql)

Queue any Laravel write query — `delete`, `update`, `insert` — and run it across parallel batched jobs. Built for large-scale mutations without long locks.

Why
---

[](#why)

A single `User::where('is_blocked', true)->delete()` over millions of rows runs one giant statement: it holds a long lock, blocks the web request, strains replication, and often hits the request or worker timeout.

`queue-sql` turns that one statement into many small, parallel jobs. It reads the min/max primary key under your constraints, splits the key space into `chunk`-sized ranges, and dispatches one batched job per range. Each job re-applies your original `WHERE` and mutates only its own slice — so every statement is small and bounded, locks stay short, and the work runs on your queue workers instead of the request.

You keep the fluent Eloquent API you already know, and get batch progress, retries, throttling, `then`/`catch`/`finally` callbacks, Artisan monitoring, and Horizon tags for free.

Features
--------

[](#features)

- **Any write, any builder** — `delete` / `update` / `insert` / `upsert` on the Query Builder or Eloquent.
- **Parallel PK-range fan-out** — one batched job per key range; short locks instead of one long one.
- **Every `where` survives the queue boundary** — nested closures, `whereHas`, `whereExists`, sub-selects — captured as a parameterized SQL fragment (injection-safe, no serialized closures).
- **Size by rows or by jobs** — fixed `chunk`, or `maxJobs` to cap the total job count.
- **Config-driven defaults** — set `chunk` / `tries` / `backoff` / `throttle` / `delay` / queue routing once in `config/queue-sql.php`.
- **Operate from the CLI** — `queue-sql:status` and `queue-sql:cancel`, plus Horizon tags.
- **Preview first** — `dryRun()` reports the plan without dispatching.
- **Tested on SQLite, MySQL, and Postgres** across Laravel 10–13 / PHP 8.1+.

How it works
------------

[](#how-it-works)

```
queue() macro  →  capture WHERE as a portable SQL fragment  →  plan PK ranges
              →  one batched job per range  →  Bus::batch (parallel workers)

```

- Constraints are captured as a compiled SQL fragment plus bindings, so **every** `where` type — flat, nested closures, `whereHas`, `whereExists`, sub-selects — survives the queue boundary and stays fully parameterized (injection-safe).
- Delete/update by primary-key range are **idempotent**, so a retried job is safe.
- No single incrementing integer key? The operation falls back to one job (still queued).

Install
-------

[](#install)

```
composer require kamranata/queue-sql
php artisan queue:batches-table   # required: job_batches table
php artisan migrate
php artisan vendor:publish --tag=queue-sql-config   # optional
```

Usage
-----

[](#usage)

```
use App\Models\User;

User::where('is_blocked', true)
    ->queue(chunk: 5000, tries: 3, backoff: 30, onQueue: 'cleanup', throttle: 10)
    ->delete()
    ->then(fn () => Log::info('done'))
    ->catch(fn (Throwable $e) => Log::error($e))
    ->dispatch();

// Preview without dispatching:
User::where('is_blocked', true)->queue(chunk: 5000)->delete()->dryRun();
// => ['operation' => 'delete', 'table' => 'users', 'jobs' => 40, 'ranges' => 40, 'estimatedRows' => 190234]

// Update:
User::where('last_login', 'queue(chunk: 5000)->delete()->dispatch();
$batch->id; // persist this to check progress / cancel later
```

Every job also carries Horizon tags — `queue-sql`, `queue-sql:{operation}`, and `queue-sql:{operation}:{table}` — so runs group and filter in the Horizon dashboard.

Monitoring (Artisan)
--------------------

[](#monitoring-artisan)

```
php artisan queue-sql:status            # list every queue-sql batch (id, name, progress)
php artisan queue-sql:status {batch}    # one batch: total/pending/failed, progress %, state
php artisan queue-sql:cancel {batch}    # cancel a running batch

php artisan queue-sql:status --watch    # live-refreshing view (--interval=N, default 2s)
php artisan queue-sql:status --json      # machine-readable JSON for external dashboards
```

Progress comes straight from Laravel's batch record — no extra tracking is stored. Grab a batch id from `->dispatch()` (`$batch->id`) or from the `queue-sql:status` list. `--json` works for both the list and a single batch; `--watch` refreshes only on a real terminal (piped or scheduled runs render once).

Parameters (`queue(...)`)
-------------------------

[](#parameters-queue)

ParamMeaningDefault`chunk`rows per job`1000``maxJobs`cap total jobs (auto-sizes `chunk`) — mutually exclusive with `chunk`none`tries`per-job retries`1``backoff`seconds between retries`0``onConnection`queue connectiondefault`onQueue`queue namedefault`throttle`max jobs/secondnone`delay`seconds before jobs startnoneEvery param resolves in this order: **explicit `queue(...)` argument → `config/queue-sql.php`default → built-in fallback** (the *Default* column above). Publish the config to set your own defaults once, globally:

```
// config/queue-sql.php
return [
    'chunk' => 1000,
    'tries' => 1,
    'backoff' => 0,
    'connection' => null,   // null = Laravel's default connection
    'queue' => null,        // null = default queue name
    'throttle' => null,     // null = no throttle
    'delay' => null,        // null = no delay
];
```

Note: because a `null` argument means "fall back to config", you cannot pass `throttle: null`at the call site to *disable* a throttle configured globally — set `throttle: 0`-style opt-outs are not supported for it. `delay: 0` does work as an explicit per-call override.

`maxJobs` is opt-in and has **no** config default — it never competes with the configured `chunk`.

### `maxJobs` — size by job count instead of chunk

[](#maxjobs--size-by-job-count-instead-of-chunk)

When you care about "how many jobs" rather than "how many rows per job", pass `maxJobs`. It auto-sizes the chunk so the fan-out produces at most that many jobs:

```
// At most 50 jobs, whatever the table size:
User::where('is_blocked', true)->queue(maxJobs: 50)->delete()->dispatch();

// insert: at most 20 jobs across the row array
DB::table('imports')->queue(maxJobs: 20)->insert($millionRows)->dispatch();
```

`chunk` and `maxJobs` are mutually exclusive — passing both throws `InvalidArgumentException`. For range fan-out (`delete`/`update`) the cap is derived from the **primary-key span**(`ceil(span / maxJobs)`), so with sparse keys individual jobs may cover uneven row counts; the job *count* is still bounded. `insert` derives it from the row count directly.

Benchmark
---------

[](#benchmark)

A plain mass delete runs one statement that holds a lock for its entire duration; queue-sql splits it into many small statements, so the longest single lock is a fraction of that. Measure it on your own database:

```
php benchmarks/lock_duration.php 200000 5000
```

In a quick local run (SQLite, 20k rows, chunk 2k) the baseline's longest lock was ~3.6× the longest lock under queue-sql. On MySQL or Postgres with millions of rows and real lock contention the gap is far larger — point the script at your database with `DB_CONNECTION=mysql …` to see your own numbers.

Limitations
-----------

[](#limitations)

- **Reads are not supported** — write operations only.
- **Fan-out needs an incrementing integer primary key.** Other keys fall back to a single job (still queued). All `where` types (including nested closures, `whereHas`, `whereExists`, sub-selects) are supported.
- **`insert` is not idempotent** — a retry can duplicate rows. Guard with unique indexes / `insertOrIgnore` at the DB level, or use `upsert` (which is retry-safe).
- **Throttle/delay staggering relies on the queue driver honoring per-job delay** — the database and redis drivers do; SQS caps delay at 15 minutes.
- **Fan-out plans the primary-key range at dispatch time** — rows inserted afterward with a key above the captured max are not processed by that run.

Requirements
------------

[](#requirements)

Laravel 10–13, PHP 8.1+. The suite runs against SQLite, MySQL, and Postgres in CI.

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance90

Actively maintained with recent releases

Popularity14

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity46

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~2 days

Total

5

Last Release

42d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/4980999?v=4)[Kamran Ata](/maintainers/kamranata)[@kamranata](https://github.com/kamranata)

---

Top Contributors

[![kamranata](https://avatars.githubusercontent.com/u/4980999?v=4)](https://github.com/kamranata "kamranata (35 commits)")

---

Tags

batchbulk-operationsdatabaseeloquentjobslaravellaravel-packagephpqueuephplaraveldatabaseeloquentqueuebatchjobsbulkbulk insertMass Updatemass-delete

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/kamranata-queue-sql/health.svg)

```
[![Health](https://phpackages.com/badges/kamranata-queue-sql/health.svg)](https://phpackages.com/packages/kamranata-queue-sql)
```

###  Alternatives

[spatie/laravel-medialibrary

Associate files with Eloquent models

6.2k45.4M718](/packages/spatie-laravel-medialibrary)[mongodb/laravel-mongodb

A MongoDB based Eloquent model and Query builder for Laravel

7.1k8.9M113](/packages/mongodb-laravel-mongodb)[laravel/scout

Laravel Scout provides a driver based solution to searching your Eloquent models.

1.7k57.2M688](/packages/laravel-scout)[kirschbaum-development/eloquent-power-joins

The Laravel magic applied to joins.

1.6k35.7M52](/packages/kirschbaum-development-eloquent-power-joins)[psalm/plugin-laravel

Psalm plugin for Laravel

3345.4M354](/packages/psalm-plugin-laravel)[spiritix/lada-cache

A Redis based, automated and scalable database caching layer for Laravel

591459.5k2](/packages/spiritix-lada-cache)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
