Skip to main content
The queue worker decouples producers from consumers: a function publishes a message to a named topic and returns right away, and any function subscribed to that topic processes the message in the background, with retries and a dead-letter queue (DLQ) for messages that keep failing.

Before adding the worker

compose::add is served by a running Compose daemon. Keep the engine and a daemon for this project running in separate terminals before using any of the commands below. If this project does not have a Compose file yet, create worker-compose.yaml containing containers: {} first.
iii compose is an intentional verbless daemon invocation, documented in the CLI reference. The -n used below is the short form of --namespace for iii call. Run the remaining commands from a third terminal in that same project directory:
This page covers common queue patterns. For the complete configuration and function API, see the queue worker docs.

Named Queues

When you need to control function execution for time consuming operations, or guarantee a certain number of retries, call the function with the enqueue action. The queue field of the action names the queue to use, so configure that queue in the queue worker configuration first.

Enqueue a function

An enqueue call is a function call with the enqueue action. The queue runs the function later, and the caller gets a receipt before the function starts:

Pub/Sub Queues

Queues can also be used in a publish/subscribe form when multiple listeners need to subscribe to the same data and it’s important that the messages be durable (ie. will succeed).

Consume messages

An observer of queue::on-message with a topic in its args runs the target function for each message published to that topic, with the message data as the input. A normal return acknowledges the message. An error retries it, and the last failed delivery moves the message to the dead-letter queue. In a worker, register the consumer function and observe queue::on-message with the topic. If you do not have a worker yet, follow Create a new worker, then edit its source:
Add the worker to start it:
Observer filters select messages by their data as input. A message that the filters reject is acknowledged without running the target.

Publish a message

With the consumer running, publish to its topic:
Open the console and go to the Traces tab to watch the message flow from the publish through to email::send running.

Subscriber args

Every observer of queue::on-message carries its subscription in args. The args fall into three classes.

Consumption

topic names the topic to consume. It is required.

Retries

max_retries counts the deliveries of one message, the first one included, before the message moves to the dead-letter queue. The default is 3. backoff_ms sets the first retry delay, and each later retry doubles it, so a message that always fails reaches the dead-letter queue about 3 seconds after it is published.
The engine bounds each delivery to a target by observers.activation_timeout_ms (30 seconds by default). A target that runs longer fails the delivery with TIMEOUT, which is retried like any other failure. For longer jobs, raise it in the engine config.

Delivery

queue_config sets the delivery settings of this subscriber, for example { type: "fifo" } to process messages one at a time. group names the consumer group: subscribers that name the same group share one consumer, which handles each message once.
A subscriber without a group has its own durable queue, so two observers of one topic each receive every published message. To spread one stream of messages across several consumers, give their observers the same group.
RabbitMQ queue names changed in 0.23.x and are not migrated automatically. See Upgrading from 0.22.x.

Inspecting Queue Topics

engine::queue::list_topics lists every topic. engine::queue::topic_stats reports the counters of one topic: depth is the messages waiting for the consumer and dlq_depth is the dead-lettered messages. A topic with no waiting messages reports depth: 0. For a named queue, consumer_count reports its active delivery slots. A topic that no observer subscribes to does not register, so there is nothing to inspect. List every topic:
Get the counters of the emails topic:
The list holds two kinds of queue. A pub/sub topic appears once an observer subscribes to it and shows broker_type: "builtin". A configured named queue shows broker_type: "function_queue".

Inspecting Dead Letter Queue Messages

A message reaches the dead-letter queue only once its target exhausts its retries, so the DLQ functions return empty until something fails.

Forcing a message into the dead-letter queue

To see the DLQ populated, make email::send fail by changing the handler to throw. Each message then fails all of its deliveries and dead-letters.
Now publish a message; after the retries run out it lands in the DLQ (exact ids, timestamps, and sizes vary per run):

Listing topics with dead-lettered messages

Browsing dead-lettered messages

Redriving dead-lettered messages

Restore the handler, then move the topic’s dead-lettered messages back to the main queue for reprocessing:
The fixed function now processes them, so the DLQ is empty again:

Redriving or discarding a single message

To handle one message instead of the whole topic, pass the id from engine::queue::dlq_messages. Redrive one message back to the main queue:
Or discard it, deleting it from the DLQ permanently: