# Concept

Workerless is a small program that sits between your SQS queues and Lambda functions. You can use it to configure the prioritization of messages in each queue, monitor your queue performance and control the rate of processing.

Workerless starts several workers (via coroutines) that receive messages from a defined list of SQS queues and invoke a lambda function with their payload.

The number of workers sets the maximum concurrent invocations the lambda will receive. This is controlled via the -concurrency shell flag, or the WORKERLESS_CONCURRENCY environment variable in the official Docker image.

Each coroutine maintains an infinite loop that keeps the coroutine running. The life-cycle of each iteration consists of the following steps:

# Queue Prioritization

There are many ways you can configure the queue prioritization in Workerless:

First, you may use a strictly sorted list:

--queues=payments,submissions,default

When using a sorted list, Workerless will check for available messages in each of the queues in the order they are defined.

In the example above, the submissions queue will not be checked unless the payments queue is empty.

Another way of defining prioritization is using weights:

--queues=payments:3,submissions:2,default:1

With this definition, Workerless will check the payments queue 50% of the time, the payments queue ~33% of the time, and the default queue ~16% of the time.

Finally, there's randomization:

--queues=payments:1,submissions:1,default:1

With this definition, there's a ~33% chance any of the queues will be checked first.

For each worker, on each iteration, a new list of queues is generated. The order of the queues in this list depends on their weights and/or priority.

# Receiving Messages

Using the generated sorted list, the worker will start checking the queues one by one. Once a message is found, the worker will send it to the lambda and start a new loop with a newly generated queues list.

If no messages were found in any of the queues, the worker will pick a random queue and start dequeuing messages using long polling (opens new window). It will wait for a defined number of seconds for any messages to arrive.

If no messages were found still, the worker can be configured to sleep for a defined number of seconds before starting a new loop. Or start a new loop right away if sleeping is not configured.

workerless --function=my-function \
           --queues=payments:3,submissions:2,default:1 \
           --poll-for=10 \
           --sleep=0

The example above polls on one of the queues for 10 seconds if all queues are empty, then starts a new loop. It won't sleep.

# Building The Payload

When a worker finds messages in a queue, it uses their payload to build a lambda-compatible payload (opens new window). It looks like this:

{
    "Records": [
        {
            "messageId": "059f36b4-87a3-44ab-83d2-661975830a7d",
            "receiptHandle": "AQEBwJnKyrHigUMZj6rYigCgxlaS3SLy0a...",
            "body": "Test message.",
            "attributes": {
                "ApproximateReceiveCount": "1"
            },
            "messageAttributes": {},
            "md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3",
            "eventSource": "aws:sqs",
            "eventSourceARN": "arn:aws:sqs:us-east-2:123456789012:payments",
            "awsRegion": "us-east-2"
        }
    ]
}

# Invoking The Function

Once the payload is ready, the lambda function will be invoked and any errors will be logged to STDOUT.

# Collecting Metrics

Workerless can be configured to measure the count and duration of messages received. It sends these metrics to CloudWatch on a custom namespace named after the --name you provide to workerless.

workerless --name=my-app-staging

This will instruct Worker to send the metrics to a CloudWatch namespace called "Workerless-my-app-staging".

You can check any of the job metrics in your CloudWatch dashboard and generate alarms & reports.

By default, Workerless sends one PutMetricData request to CloudWatch every 60 seconds. You can configure the interval using the --metrics-interval flag.

# Shutting Down Gracefully

Workerless handles the SIGTERM signal and broadcasts it to all workers. Each of them listens for this signal before checking for messages in any queue. If the signal was detected, the worker shuts down immediately. That way, no worker will shut shutdown between receiving messages and invoking the function.

# Why Coroutines

Coroutines are very lightweight compared to processes and threads. Given that the nature of what Workerless does is mainly IO operations, using coroutines means that the Workerless daemon process will require very little resources—in terms of memory and CPU—to run efficiently.