Shoryuken is probably the best option when it comes to processing background jobs using SQS with ActiveJob [1], however I couldn’t find any reliable documentation online to get it working with ElasticBeanstalk worker tiers. After some trial and error, I was able to implement a reliable setup.
Configure Shoryuken
If you haven’t already, create a shoryuken.yml
file located in the config
directory of your Rails project to specify what queues you want to consume:
# config/shoryuken.yml
queues:
- my_queue1
- my_queue2
This is a bare minimum config, so I recommend taking a look at the documentation for more options.
Setting up the ebextension
We’re going to use an ebextension to create a post-deploy script that will restart Shoryuken after deployment to ensure the latest changes are loaded.
Before we do that though, you’ll need to create a new environment variable on your worker tier. Unfortunately, ebextensions do not let you specify tier-specifc deploy scripts so this variable will be used to make sure the script only runs on your worker tiers.
The following command will create a WORKER
variable with the value set to true
:
eb setenv WORKER=true -e <worker tier environment name>
Now we’re ready to create the extension. Create a directory at the root of your project titled .ebextensions
if it doesn’t already exist. Create a file named shoryuken.config
under your .ebextensions
directory with the following:
# .ebextensions/shoryuken.config
files:
"/opt/elasticbeanstalk/hooks/appdeploy/post/50_restart_shoryuken":
mode: "000777"
content: |
# Load environment variables and your app's ruby version
EB_SCRIPT_DIR=$(/opt/elasticbeanstalk/bin/get-config container -k script_dir)
EB_SUPPORT_DIR=$(/opt/elasticbeanstalk/bin/get-config container -k support_dir)
. $EB_SUPPORT_DIR/envvars
. $EB_SCRIPT_DIR/use-app-ruby.sh
# Exit immediately if not on the worker tier
if [ $WORKER = true ]; then
exit 0
fi
cd /var/app/current
# Kill the currently running shoryuken process if the pid file exists
if [ -f /var/app/support/pids/shoryuken.pid ]
then
kill -TERM `cat /var/app/support/pids/shoryuken.pid`
rm -rf /var/app/support/pids/shoryuken.pid
fi
sleep 5
# Start shoryuken specifying the pid, log, and config paths
bundle exec shoryuken \
-R \
-P /var/app/support/pids/shoryuken.pid \
-C /var/app/current/config/shoryuken.yml \
-L /var/app/support/logs/shoryuken.log \
-d
At a high level, this script kills the currently running shoryuken process, waits five seconds for jobs to cleaned up, then starts it again.
Scheduling Jobs
Scheduling jobs with Shoryuken is a little more tricky. The easiest method I’ve found is to set up a CloudWatch Event to send a message to a specified SQS queue at a certain interval.
One Caveat…
Unfortunately you can’t use ActiveJob for CloudWatch scheduled jobs. This is because Shoryuken relies on specific SQS message attributes when working on jobs triggered with ActiveJob and CloudWatch doesn’t allow you to define these.
The workaround is to use a plain Ruby class instead of an ActiveJob subclass. The minor downside is that you will have to call Shoryuken-specific perform methods instead of ActiveJob’s perform methods (e.g.: perform_later
) to enqueue your job if you plan on enqueueing it within your app in addition to scheduling it.
Here’s an example of a “plain Ruby” Shoryuken job:
class MyJob
include Shoryuken::Worker
shoryuken_options queue: :my_queue, auto_delete: true
def perform(*args)
# job logic
end
end
Now head on over to the CloudWatch Management Console > Rules > Create Rule. Here you can specify a schedule and a target. Select your desired schedule and select “SQS Queue” from the target dropdown. Select your queue, then select “Constant (JSON text)” and put in the following:
{
"job_class": "MyJob",
"job_id": "00000000-0000-0000-0000-000000000000",
"queue_name": "my_queue",
"priority": null,
"arguments": [],
"locale": "en"
}
Change job_class
to be the same name as the job you just created, and queue_name
to the name of your SQS queue.
Now Shoryuken will be able to process scheduled jobs!
[1] Besides rolling your own SQS consumer, your two main options are either Shoryuken or active_elastic_job. Even though there’s a bit more setup, I decided to go with Shoryuken because active_elastic_job is very limited (for example, it doesn’t support multiple queues) and at the time of posting it appears to be unmaintained.