Skip to content

Changelog ​

v1.2.0 ​

Released 2026-11-01. Run unfinished backfills after a deploy, and test code that starts them.

  • backfill:pending lists, runs or queues every backfill that has not finished, the way migrate handles migrations. Add backfill:pending --queue --force to a deploy script and new backfills run themselves. Paused runs resume from their cursor, finished ones are skipped. See running after a deploy.
  • $manual keeps a backfill out of backfill:pending. Operator-panel backfills and ones with a required parameter are left out automatically.
  • Backfills::fake() records runs without touching the data, for testing code that starts backfills. Assert with assertRan, assertRanTimes, assertNotRan and assertNothingRan. See testing.
  • Assertion helpers on InteractsWithBackfills: assertBackfillCompleted, assertBackfillProcessed and assertBackfillHasNoFailures, with failure messages that name the stop reason or the first failed row.

v1.1.0 ​

Released 2026-11-01. Fixes for queue mode under real workers, a tighter row-ceiling guard, and static analysis in CI.

Queue mode

  • Queued slices are time-boxed. A slice now ends after batches_per_job batches or max_seconds_per_job seconds, whichever comes first, so a slow process() can no longer outlive the worker timeout. The new time_limit stop code chains like max_batches.
  • Jobs carry their own timeout. RunBackfillJob sets $timeout from backfill.queue.timeout and turns on failOnTimeout.
  • A killed job is reported, not lost. Its failed() hook marks the run interrupted, releases the lock, and fires BackfillFailed, so notifications go out. Before, the run sat at running until its heartbeat went stale and nobody was told.
  • The worker's own shutdown handling is preserved. The runner used to replace queue:work's SIGTERM handler and then reset it to the default. The worker never learned it should quit, and after the job the next SIGTERM killed it mid-job. The previous handler is now called alongside the runner's and restored afterwards.

Guards and retries

  • --no-count no longer bypasses the row ceiling. While max_rows_without_confirmation is set, skipping the count is refused without --force.
  • backfill:retry-failed is tenant-aware. It takes the same per-tenant lock as the runner, switches into the run's tenant, and re-applies the run's parameters.

Dashboard

  • Throughput leaves out paused time. Runs record active seconds per session, so a run paused overnight is no longer shown at a fraction of its real speed.

Housekeeping

  • Laravel 11 is no longer supported. It stopped receiving security fixes in March 2026, and Composer now refuses to install its releases by default because of the open advisories. Laravel 12 and 13 are supported.
  • Requires laravel/framework rather than three illuminate/* components. The package always used parts only the full framework ships.
  • PHPStan (Larastan, level 5) and Pint run in CI.

v1.0.0 ​

The first tagged release. It bundles four milestones, listed below in the order they were built.

v0.4 ​

The operator panel, ledger mode, per-tenant cursors, and the Pulse card.

  • Operator panel — mark a backfill $operatorRunnable, declare its inputs, and support staff can run it from a browser. Own route, own gate, validated inputs, progress in plain words.
  • Parameters — ids, text, textarea, number, boolean and select, with required, min, max, default, help and placeholder. Recorded on the run and re-applied on resume; resuming with different parameters is refused.
  • Ledger mode — claim before, confirm after, for work the database cannot roll back. Unconfirmed claims are surfaced, never retried automatically. Declaring side effects without a ledger logs a loud warning.
  • Multi-tenancy — a cursor, run row and lock per tenant, so one tenant crashing never rewinds another and tenants can run side by side.
  • Pulse card — in-flight and troubled runs, failed first, deliberately outside Pulse's period filter.
  • --param and --tenant on backfill:run.

v0.3 ​

The dashboard, queue mode, events and notifications.

  • Livewire dashboard — live progress, throughput, cursor, batch-duration sparkline, failed rows with retry. Actions are queued, not run in the request. Closed by default outside local.
  • Queue mode — --queue dispatches a job that runs a slice and chains the next. Short jobs mean a deploy costs one batch. The chain stops on a pause nobody asked for.
  • Eight lifecycle events with a machine-readable StopReason on every pause.
  • Notifications on completion, failure and automatic pause. An operator pausing on purpose is never notified.
  • Finished runs prunable via model:prune.

v0.2 ​

Dry run, throttling, retries, the circuit breaker and production guards.

  • Dry run — scope, index check, duration estimate, and real before/after diffs from rows processed inside a rolled-back transaction. Mail, notifications, jobs and HTTP intercepted; events recorded but not suppressed.
  • Adaptive throttling on replication lag, with a rolling-median batch-duration fallback.
  • Transient-failure retries with exponential backoff; everything else fails immediately.
  • Circuit breaker that auto-pauses on a sustained failure rate, counted per session so a fixed run can finish.
  • Statement and lock timeouts per engine.
  • Production guards — row ceiling, deploy freeze windows.
  • backfill:retry-failed, and an optional per-batch audit trail.

v0.1 ​

The MVP.

  • Backfill class, make:backfill, and discovery.
  • Keyset runner with cursor persistence — never OFFSET.
  • Per-row error isolation with savepoints.
  • run, status, list, pause, resume, cancel.
  • The run lock, graceful SIGTERM/SIGINT shutdown, and the migration guard.
  • The chaos test: a real SIGKILL mid-batch, asserting the resume matches an uninterrupted run.

Released under the MIT Licence.