Queue (SQS)¶
Note
Not part of core. Install it separately:
composer require kinetis/queue-sqs
Adds Amazon SQS as a backend for Queue. Switching to it changes configuration, not application code.
QUEUE_CONNECTION=sqs
QUEUE_SQS_REGION=us-east-1
vendor/bin/kinetis queue:work --queue=high,default
Every SQS request, including a worker waiting for its next job, suspends the calling Fiber instead of blocking the process. Credential files are the exception — see Credentials.
Configuring¶
QUEUE_SQS_REGION is required. Five settings are optional:
QUEUE_SQS_QUEUE_PREFIX=myapp-
QUEUE_SQS_ENDPOINT=http://localstack:4566
QUEUE_SQS_PLAINTEXT=true
QUEUE_SQS_TIMEOUT=30
QUEUE_VISIBILITY_TIMEOUT_SECONDS=300
QUEUE_SQS_QUEUE_PREFIX is prepended to every queue name, so staging and
production can share one AWS account without both using default.
QUEUE_SQS_ENDPOINT points at an SQS-compatible service such as
LocalStack. It is one origin — a scheme, a host and an optional port,
with no userinfo, path, query or fragment — and anything else is refused
when the client is built. Unset, the destination is AsyncAws’s regional
endpoint table, and an AWS_ENDPOINT_URL set in the environment for
another tool is refused rather than redirecting this application’s
signed requests.
QUEUE_SQS_PLAINTEXT=true allows an http:// endpoint. Plain HTTP
between containers on one Compose network is ordinary; a public
plain-HTTP endpoint would carry credentials and job payloads unencrypted,
and nothing in a hostname tells the two apart, so the decision is
explicit.
QUEUE_SQS_TIMEOUT (seconds, default 30, any positive value) bounds
each SQS request and each credential lookup on its own — not one deadline
across a pop() that makes several requests. Keep it above five seconds:
a worker’s long poll holds a request open for up to that long, and a
shorter budget aborts an idle poll as a failure. A request is one
attempt, with no retry and no redirect followed.
QUEUE_VISIBILITY_TIMEOUT_SECONDS (default 300, 1 to 43200) is
the window every ReceiveMessage asks for and every renewal restores.
It is sent explicitly on each receive, so it overrides the queue’s own
VisibilityTimeout attribute for the messages this application takes;
the remote attribute still governs anything else receiving from that
queue. A value outside the range is refused when the backend is built,
before any AWS client exists.
Visibility timeout¶
queue:work renews a message’s invisibility automatically while the job
runs, at half QUEUE_VISIBILITY_TIMEOUT_SECONDS, so that setting sizes
how long a crashed worker’s message waits to be redelivered rather
than how long a job may take. A handler that never yields to the event
loop cannot be renewed, and neither can one whose worker has died.
AWS counts a message’s own 12-hour maximum from the receive, not from
the last renewal. A job still running at that point is redelivered
whatever the worker sends, and the ChangeMessageVisibility that would
have extended it past the limit is refused by SQS — an error that
propagates the same way every other SQS failure here does, reported
after the worker has attempted the job’s settlement (see Queue).
Credentials¶
Credentials resolve through AsyncAws’s standard chain, in its standard
order: environment variables (including the STS assume-role that
AWS_ROLE_ARN selects), web identity, the shared credentials and config
files, ECS or EKS pod identity, then IMDS. Nothing is configured in
Kinetis.
Every provider that calls AWS uses the same non-blocking transport as the client, so an assume-role or IMDS lookup suspends the calling Fiber. The shared credentials file, the shared config file and any web-identity or pod-identity token file are read with blocking calls, on first resolution and on each refresh.
Resolved credentials are reused until they expire. An expired answer is skipped for the next provider, and a lookup that resolved nothing is not remembered, so a role, container credential endpoint or token file that appears after a worker starts is picked up by the next queue operation.
Create your queues ahead of time¶
Each queue name you push to — default, high — must already exist as
a standard SQS queue of that name, with QUEUE_SQS_QUEUE_PREFIX
prepended when set. This package never creates a queue. Create each
one with Terraform, CloudFormation, the AWS CLI or the console before
pushing or popping. FIFO queues (names ending in .fifo) are not
supported.
The queue’s own VisibilityTimeout attribute governs anything else
receiving from it; this application’s workers send
QUEUE_VISIBILITY_TIMEOUT_SECONDS on every receive instead (see
Visibility timeout).
Delivery caveats¶
SQS can deliver a message more than once on its own, even to a second worker while the first still holds it within the visibility timeout, so handlers must be idempotent.
A job’s attempt count is SQS’s
ApproximateReceiveCount, which AWS documents as approximate.A settlement that arrives after the visibility timeout is not reported as a lost delivery: if SQS rejects it, its error stops the worker, and if SQS accepts it, nothing reports it.
A renewal is fenced by the receipt handle SQS itself scopes to the receive, and reports nothing back: a refused renewal is logged once after the settlement attempt, never raised as a lost delivery.
A worker watching several queues checks each once, then long-polls only the highest-priority queue for up to five seconds, so a job arriving on a lower-priority queue can wait that long.
SQS mechanisms maps each queue operation to its SQS call.
Emptying a queue¶
SqsQueue does not declare ClearableQueueInterface, so it has no
clear(), and kinetis queue:clear names the backend and exits 1.
SQS’s PurgeQueue also deletes messages workers hold in flight and
messages sent during the up-to-60-second purge, and reports no count, so
this backend never calls it. Empty a queue the way you created it —
aws sqs purge-queue, or recreating the queue — accepting those
semantics explicitly.
Delays and retries¶
SQS delays a message by at most 900 seconds (15 minutes). A longer
delaySeconds makes push() throw before anything is sent. Retries
follow Queue: maxAttempts, QUEUE_MAX_ATTEMPTS and
QUEUE_RETRY_BASE_DELAY_SECONDS. A delayed retry is the message’s own
visibility timeout: ChangeMessageVisibility’s request field accepts up
to 43200 seconds (12 hours), wider than the send-time delay, and a
larger value throws before anything is sent.
Being within that field’s range is not the same as being accepted. SQS refuses a timeout longer than the time left in that received message’s own 12-hour maximum and does not recalculate down to it, so a long backoff late in a message’s life can be rejected. That is service state this package cannot see, so it makes no local guess: SQS’s error propagates and the delivery stays unsettled, the same as any other settlement failure here.
Named connections¶
QUEUE_CONNECTION_NAME=reports
QUEUE_REPORTS_CONNECTION=sqs
QUEUE_REPORTS_SQS_REGION=eu-west-1
QUEUE_REPORTS_SQS_QUEUE_PREFIX=myapp-reports-
A connection’s name scopes its selector and every QUEUE_SQS_* key;
default reads the plain keys. QUEUE_CONNECTION_NAME makes reports
the bound QueueInterface, and queue:work --connection=reports runs
it either way. See Queue’s “Named connections” and Configuration.
See also¶
Queue — jobs, workers, retries and delivery guarantees.
Appendix: Queue Contracts — the SQS mechanisms and delivery contracts.
Configuration — named connections and every
QUEUE_SQS_*key.