Failed Jobs#
Note
Not part of core. Install it separately:
composer require kinetis/queue-failed-jobs
Keeps every job the Queue worker gives up on in a MySQL or
Postgres table, recorded before the backend deletes it, so you can list
those jobs, retry them, forget them, or prune old ones. It works with
every queue backend — Redis, SQL, SQS and RabbitMQ — and with every named
queue connection, and stores the records through
kinetis/database-bridge, whatever backend the queue itself uses.
Installing the package turns recording on; there is no setting to
enable it. With the package installed, queue:work depends on the
failed-jobs database: it stops when that database refuses a write.
Create the table#
The package ships one Migrations stub per dialect:
vendor/kinetis/queue-failed-jobs/resources/migrations/create_kinetis_failed_jobs_table.mysql.php.stub
vendor/kinetis/queue-failed-jobs/resources/migrations/create_kinetis_failed_jobs_table.pgsql.php.stub
Copy the one matching your database into your migrations/ directory
with a timestamp prefix, then run vendor/bin/kinetis migrate. When the
records live on a named database connection (below), the stub goes in
that connection’s directory, migrations/<name>/.
Choose the database#
FAILED_JOBS_DB_CONNECTION_NAME=default
FAILED_JOBS_DB_CONNECTION_NAME names the database connection the store
uses: the DB_* keys for default, which an unset or blank value
selects, and DB_{NAME}_* for any other name — reports reads
DB_REPORTS_CONNECTION, DB_REPORTS_HOST and the rest (see
Database). A name outside the connection-name grammar —
lowercase letters and digits, starting with a letter — is rejected
before any connection is built.
The store opens its own link to that database the first time it is
needed and closes it when the worker or command ends. queue:work
builds it before it prints that it started, so missing or invalid DB_*
keys stop the worker at startup. A server that cannot be reached is
found at the first job the worker gives up on: the write fails, and the
worker stops with the job still on its queue.
What a record holds#
Field |
Contents |
|---|---|
id |
32 random hexadecimal characters |
connection |
The |
queue |
The queue the job was popped from |
class, args |
The job class and its constructor arguments, in full |
maxAttempts |
The cap the job was pushed with, or none when it used the worker’s default |
attempts |
The attempt that failed last |
exception |
The exception class, and its message cut to 2 KB |
failed at |
When the worker gave up on the job |
No stack trace is stored. The table holds each job’s arguments in
full, #[Sensitive] values included, because a retry rebuilds the job
from them; #[Sensitive] governs what is logged, not what is stored.
Give the table the access controls the queue’s own storage has. The
MySQL stub stores arguments as MEDIUMTEXT, up to 16 MiB; a job whose
arguments the table cannot hold stops every worker that pops it, so keep
the column at least as large as your queue backend’s largest message.
Commands#
vendor/bin/kinetis queue:failed # oldest first, 50 at most
vendor/bin/kinetis queue:failed --queue=mail --limit=200
vendor/bin/kinetis queue:retry 3f9c0b2a64d14e8f9a0c51d7e2b46a18
vendor/bin/kinetis queue:retry --all --connection=ledger
vendor/bin/kinetis queue:forget 3f9c0b2a64d14e8f9a0c51d7e2b46a18
vendor/bin/kinetis queue:prune --hours=168
queue:failed and queue:retry --all select records with the same
options: --connection=<name> for jobs a worker started with that
--connection recorded, --bound for jobs a worker on the bound queue
recorded, and --queue=<name>. Without them, every record is selected.
queue:failed prints each record’s id, time, connection, queue, class,
attempts, exception and arguments. Arguments marked #[Sensitive]
print as [redacted], and when the job class no longer exists every
argument does, since nothing says which ones were sensitive.
queue:retry pushes each job back onto the connection and queue it
failed on, with the maxAttempts it was pushed with, and then forgets
its record. A record with no connection goes to the bound queue; a named
one goes to that queue connection, built from its QUEUE_{NAME}_* keys
as queue:work --connection builds it. A record whose retry fails — the
class no longer exists or no longer accepts the stored arguments, the
connection is no longer configured, the push is refused — is reported
and kept, and the command exits 1. --all works through every selected
record oldest first, reports how many were retried and how many failed,
and leaves records of jobs that failed after it started for the next
run.
queue:forget deletes one record without retrying it. queue:prune
deletes every record older than --hours, which has no default; nothing
is pruned automatically.
Duplicate records#
A job the worker gives up on is always either still held by the queue backend or recorded, possibly both, never neither. The worker records the job before it asks the backend to delete it, and never deletes a record itself:
When the record cannot be written, the worker stops without deleting the job, and the backend delivers it again once its reservation expires.
When the worker dies between the two steps, or the delete fails, or the backend reports the delivery already lost, the record stays and the job is delivered again. Each delivery that fails at the cap again adds another record, so one job can leave several, including a job whose later delivery succeeded.
queue:retrypushes before it forgets. When it is interrupted between the two, the record stays, and retrying it again pushes the job a second time.
This is the at-least-once contract of Queue’s A job can run more than once: a handler that tolerates running twice tolerates a retried duplicate too.
Kinetis\Queue\Events\JobFailedPermanently fires once the backend has
deleted the job and carries the record’s id as failedJobId (see
Events).
What is never recorded#
A job whose process dies on every attempt never reaches the worker’s failure path, so nothing records it; the backend’s own recovery keeps redelivering it. On SQS a redrive policy covers that case — see Queue (SQS).
A stored message that cannot be decoded is deleted and logged as a warning, as without this package.
SyncQueueruns jobs inline and records nothing.A job removed by
queue:clear.
See also#
Queue — retries, failures and delivery guarantees.
Appendix: Queue Contracts — the worker’s settlement order with a recorder bound.
Migrations — running the stub, and per-connection directories.
Appendix: Configuration Reference —
FAILED_JOBS_DB_CONNECTION_NAME.