Skip to Main
Home

Subscribing to RabbitMQ using Rails on Heroku

RabbitMQ with Rails

Recently, at Showoff, we were asked by one of our customers to handle publications to a RabbitMQ queue from one of their external partners. The customers production environment is deployed to Heroku. This post discusses how to daemonize potentially non-terminating processes under Rails, specifically for subscribing to, reacting to and acknowledging publications to a RabbitMQ queue, with a production environment deployed to Heroku.

One of the first approaches that we discussed was to handle this using a periodic Rake task, say every hour. While this may be a fairly standard approach to tasks such as this, and it may have simplified the process slightly, it would not have given our customer the experience that they requested: real-time handling of publications to this RabbitMQ queue.

Obviously, in order to accomplish this, we would need to step outside of our standard processes. After more discussion of the possible ways to accomplish the task at hand, we decided to have a permanently running subscription to the queue, existing in its own process, separate to any web or worker processes.

While there are several benefits to this approach, one of our main internal requirements was that this process should not impact the web processes responding to our customers users. Isolating the queue handler inside its own process also isolates its memory usage and removes any significant draw on resources associated with request handling.

Step 1: Add required Gems

In order to subscribe to our RabbitMQ queue, we will be using the bunny library, inside a process which has been daemonized using the daemons library.

While evaluating libraries to handle the RabbitMQ subscription, there appeared to be two popular gems: bunny and amqp. While both have incredibly similar interfaces, we were unable to connect to our feed using amqp, despite using the same configuration for both.
Once these libraries have been installed via bundle install, we can then begin to create our daemonized RabbitMQ process.

Step 2: Process Setup

The Gist below shows the basic setup that is required to define a daemonized Ruby process which can be run on Heroku, and which retains all the standard behavior of a Rails process, such as logging and a database connection.
Note the use of the multiple keyword which is used to prevent multiple instances of this daemon from executing at the same time. Also note, that for the purposes of this post, rabbit_mq_handler.rb is located at RAILS_ROOT/lib/rabbit_mq_handler.rb, however, if you are defining it elsewhere, please ensure to update all requires and require_relatives.

There are several bits of code in the Gist that are required to ensure that our daemonized process can log correctly and has an accessible connection to the database:
When the daemonized process is starting, we use collect_open_files to record all live files that are open before the daemonized process has been run, including the processes log file, inside the garbage collector. Once our daemonized process is run, we then use reopen_initial_files to open new streams associated with these files.

When the daemonized process is starting, we also use establish_active_record_connection to ensure that we have a connection to the database available. Note: this is not necessary if you do not plan on interacting with the database.

Step 3: Subscribe to RabbitMQ

Once we have our process setup to run correctly locally and in production, we can then handle the subscription to the RabbitMQ queue and any publications that are pushed to that queue. We have defined our subscription handling functions inside RabbitMqHelper.

In our example, both our exchange and queue are marked as durable, however this may not be correct for other RabbitMQ servers. Update the initialization of the exchange and queue variables to suit the configuration of your RabbitMQ server.

Note that we have used a blocking subscription to the queue via the block keyword, as we do not want the process handling the subscription to terminate. If you wanted to just handle any pending publications to the queue without waiting for future publications, you would not need to use a blocking subscription and could allow the process to exit, although such an approach would be better suited to a periodic Rake task.

Also note that we are expecting our payload to contain JSON, hence the call to JSON.parse. payload, when received from RabbitMQ, is a String and may not contain JSON depending on your use case.

We have enabled manual delivery acknowledgements via the manual_ack keyword, as this gives us fine grained control of whether or not we acknowledge receipt of deliver. We can then base our acknowledgements upon our processing (though in the example above, we just acknowledge all deliveries). This can be useful, for example, if we don’t want to acknowledge delivery of anything that causes our processing to fail.

Step 4: Putting it Together

Once we have defined our RabbitMQ helper, all that remains is to include our RabbitMqHelper and call handle_subscription inside our daemon.

Step 5: Starting Daemonized Process via Procfile

For the purposes of this example, I have added rabbit_mq_handler.rb to RAILS_ROOT/lib/rabbit_mq_handler.rb. In order for this to be registered as a process on Heroku, we must add it to our Procfile. As it is a daemonized process, we must also tell it to run.

The only thing that is left to enable to process from the Heroku resource settings or the Heroku CLI.

To conclude, this post has shown how to implement long running processes inside a daemon on Heroku. This has required some workarounds to allow our daemonized processes to behave like normal Rails processes, with access to logging and to the database, and has the benefits of allowing the daemonized process to exist in relative isolation from the web and other background worker processes of the Rails applications.

It has also shown how to reliably connect to a RabbitMQ instance using the current version of the bunny gem and to process new publications to a queue in real-time. If real-time processing were not required, it would be best to set up this handling in a periodic Rake task, which would have the added benefit of not requiring the workarounds to allow logging and database connections to behave correctly.

May we use cookies to track your activities? We take your privacy very seriously. Please see our privacy policy for details and any questions.Yes No