{"id":744,"date":"2011-10-21T09:36:05","date_gmt":"2011-10-21T07:36:05","guid":{"rendered":"http:\/\/blog.railsware.com\/?p=744"},"modified":"2021-08-16T16:04:30","modified_gmt":"2021-08-16T13:04:30","slug":"the-resque-way","status":"publish","type":"post","link":"https:\/\/railsware.com\/blog\/the-resque-way\/","title":{"rendered":"The Resque Way"},"content":{"rendered":"\n<h3 class=\"wp-block-heading\">Introduction<\/h3>\n\n\n\n<p>When we started using Resque two years ago we were impressed by two things: its power out-of-the-box and its opportunities for scalability. Over the past two years, we\u2019ve explored Resque internals and plugins. We\u2019d like to share what we\u2019ve learned from our practical experience using Resque during different phases of the web application life cycle.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Working in development and running tests<\/h3>\n\n\n\n<p>The first challenge we encountered was deciding how to organize development for non-Ruby developers and test environments. Our aims were to keep most of the team free from knowledge about Resque and Workers and to avoid stubs in tests. This idea resulted in the&nbsp;inline mode for Resque&nbsp;that solved the problem perfectly.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">First step in production: ActiveRecord and Resque<\/h3>\n\n\n\n<p>Since there are many articles covering how to deploy Resque, we\u2019ll focus on issues that haven\u2019t been thoroughly described before.<\/p>\n\n\n\n<p>After two weeks in production it was clear to us that there was an issue with Resque and ActiveRecord. In some cases you may enqueue a Resque job while inside a database transaction, but Redis commands are independent from database transactions. Sometimes a worker starts processing a job before the transaction that creates the specific job commits. After a few ugly solutions that forced us to restructure the code, we discovered what we needed in the\u00a0<a href=\"https:\/\/github.com\/grosser\/ar_after_transaction\" target=\"_blank\" rel=\"noreferrer noopener\">ar_after_transaction<\/a>\u00a0gem. This\u00a0<a href=\"https:\/\/github.com\/defunkt\/resque\/wiki\/FAQ\" target=\"_blank\" rel=\"noreferrer noopener\">Resque FAQ<\/a>\u00a0details how to make a Resque job wait for an ActiveRecord transaction commit, so that it can see all the changes made by that transaction. Of course, if you ensure database transactions are committed prior to enqueuing jobs, you can structure your application in any manner you desire.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Second step in production: Outer HTTP APIs with Resque<\/h3>\n\n\n\n<p>External HTTP calls are a common bottleneck for web requests and need to be moved to the background because of unpredictable response time and downtime for these APIs. You may find the\u00a0<a href=\"https:\/\/github.com\/lantins\/resque-retry\" target=\"_blank\" rel=\"noreferrer noopener\">resque-retry<\/a>\u00a0plugin (and\u00a0<a href=\"https:\/\/github.com\/bvandenbos\/resque-scheduler\" target=\"_blank\" rel=\"noreferrer noopener\">resque-scheduler<\/a>\u00a0plugin as a dependency) useful, allowing you to retry exceptions in workers with a customizable delay.<\/p>\n\n\n\n<p>Here are some common HTTP errors in the \u201cjust try again to fix\u201d category:<\/p>\n\n\n\n<div>\n<div>\n<pre lang=\"ruby\">@retry_exceptions = [\n  Timeout::Error,\n  Errno::ECONNREFUSED,\n  Errno::ECONNRESET,\n  # errors from your favorite\n  # Net::HTTP wrapping library goes here\n]<\/pre>\n<\/div>\n<\/div>\n\n\n\n<p><strong>N.B.&nbsp;<\/strong>Errno codes are platform-specific, make sure you understand how portable your code needs to be.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Third step in production: Email sending<\/h3>\n\n\n\n<p>If you are using an external SMTP server to send email, you will need to move the email delivery to the background \u2014 with Resque\u2019s help, of course. There are number of solutions available, such as\u00a0<a href=\"https:\/\/github.com\/seattlerb\/ar_mailer\" target=\"_blank\" rel=\"noreferrer noopener\">ar_mailer<\/a>. We decided to use\u00a0<a href=\"https:\/\/github.com\/zapnap\/resque_mailer\" target=\"_blank\" rel=\"noreferrer noopener\">resque_mailer<\/a>. We encountered an initial problem with\u00a0<code>Net::SMTPServerBusy<\/code>\u00a0and\u00a0<code>Timeout::Error<\/code>\u00a0exceptions that appeared randomly while sending email. We found\u00a0<a href=\"https:\/\/github.com\/lantins\/resque-retry\" target=\"_blank\" rel=\"noreferrer noopener\">resque-retry<\/a>\u00a0is also useful here. In the case of resque_mailer we wanted to have shared configuration for resque-retry for every Mailer class. We found that this was not easy because historically Resque is configured through instance variables in a class that are not inherited. We needed a base class that could share all instance variables across any child class:<\/p>\n\n\n\n<div>\n<div>\n<pre lang=\"ruby\">class AsyncApplicationMailer &lt; ActionMailer::Base\n\n  include Resque::Mailer\n\n  extend Resque::Plugins::Retry\n\n  # All Notifiers inherited from this class\n  # require same resque-retry options.\n  # Resque workers are classes but not instances of classes.\n  # That is why resque retry require class variables that is not inherited\n  # In order to setup same resque-retry class variables\n  # for every inherited class we need this hack.\n  def self.inherited(host)\n    super(host)\n    host.class_eval do\n      @retry_exceptions = [Net::SMTPServerBusy, Timeout::Error, Resque::DirtyExit]\n      @retry_limit = 3\n      @retry_delay = 60 #seconds\n    end\n  end\n\nend<\/pre>\n<\/div>\n<\/div>\n\n\n\n<p>Use this class as the base class for all your mailers and retry configuration will be shared among them.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Play Minesweeper: Bug fixing along the way<\/h3>\n\n\n\n<p>As the number of users and load grew, we decided it was a good idea to include other plugins like\u00a0<a href=\"https:\/\/github.com\/jayniz\/resque-loner\" target=\"_blank\" rel=\"noreferrer noopener\">resque-loner<\/a>\u00a0(to track job uniqueness) and\u00a0<a href=\"https:\/\/github.com\/ono\/resque-cleaner\" target=\"_blank\" rel=\"noreferrer noopener\">resque-cleaner<\/a>\u00a0(to cleanup failed jobs). This required fixing and improving these libraries:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"https:\/\/github.com\/bvandenbos\/resque-scheduler\/commit\/7f65bba3bf9ec066b114c95a42b3a8b58736ff16\" target=\"_blank\" rel=\"noreferrer noopener\">Fix resque-scheduler process death after pushing invalid resque job class<\/a><\/li><li><a href=\"https:\/\/github.com\/grosser\/ar_after_transaction\/commit\/a59386e31939d64a5c0f78b873\" target=\"_blank\" rel=\"noreferrer noopener\">Fix infinite recursion in edge case usage of ar_after_transaction<\/a><\/li><li><a href=\"https:\/\/github.com\/zapnap\/resque_mailer\/issues\/3\" target=\"_blank\" rel=\"noreferrer noopener\">Make resque-mailer respect the\u00a0<code>ActionMailer::Base.perform_deliveries<\/code>\u00a0configuration option<\/a><\/li><li><a href=\"https:\/\/github.com\/lantins\/resque-retry\/pull\/22\" target=\"_blank\" rel=\"noreferrer noopener\">Fix resque-retry suppression in resque failures for jobs with custom identifier<\/a><\/li><li><a href=\"https:\/\/github.com\/jayniz\/resque-loner\/commit\/2ada35a8a080cc2f2540ecf83a8461e4b0b2df0b\" target=\"_blank\" rel=\"noreferrer noopener\">Modulize resque-loner to be compatible with other plugins<\/a><\/li><\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Business requirements increase: Returning results from jobs<\/h3>\n\n\n\n<p>The original Resque design does not allow you to receive something back after the worker completes. This may be beneficial for most use cases. However, for our use case (payment checkout through an outer Authorization gateway) it was important to know whether the worker was in progress or not, and if not \u2013 whether it was successful or not.<\/p>\n\n\n\n<p>Many Resque plugins introduce a job identifier based on arguments passed to this job, but there is no standardization regarding how it should be done. Here are two examples:<\/p>\n\n\n\n<p>resque-retry:<\/p>\n\n\n\n<div>\n<div>\n<pre lang=\"ruby\"># @abstract You may override to implement a custom identifier,\n#           you should consider doing this if your job arguments\n#           are many\/long or may not cleanly cleanly to strings.\n#\n# Builds an identifier using the job arguments. This identifier\n# is used as part of the redis key.\ndef identifier(*args)<\/pre>\n<\/div>\n<\/div>\n\n\n\n<p>resque-loner:<\/p>\n\n\n\n<div>\n<div>\n<pre lang=\"ruby\">#\n#  Payload is what Resque stored for this job along with the job's class name.\n#  On a Resque with no plugins installed, this is a hash containing :class and :args\n#\ndef redis_key(payload)<\/pre>\n<\/div>\n<\/div>\n\n\n\n<p>In order to synchronize a job identifier across plugins, we implemented our own\u00a0<a href=\"https:\/\/gist.github.com\/1222167\" target=\"_blank\" rel=\"noreferrer noopener\">interface for jobs with completion status<\/a>. This can be helpful for people that need something like this. However, don\u2019t confuse this with execution status in\u00a0<a href=\"https:\/\/github.com\/quirkey\/resque-status\" target=\"_blank\" rel=\"noreferrer noopener\">resque-status<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Contribution to open source<\/h3>\n\n\n\n<p>Last but not least, thank you to these people responsible for supporting our patches:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>@bvandenbos\/resque-scheduler: Merged. Thanks. Will ship in 1.9.8.<\/li><li>@defunkt\/resque: Love it. \u2026 This is a great patch \u2013 docs, tests, and code! Thanks.<\/li><li>@zapnap\/resque_mailer: Good point. I just pushed a change to the repository that should take care of this and released a new gem (1.0.1)<\/li><li>@jayniz\/resque-loner: Awesome, thanks! Will pull ASAP :)<\/li><\/ul>\n\n\n\n<p>Note: \u00a0This article was originally published in\u00a0<a href=\"http:\/\/www.engineyard.com\/blog\/2011\/the-resque-way\/#more-10714\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">EngineYard blog<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction When we started using Resque two years ago we were impressed by two things: its power out-of-the-box and its opportunities for scalability. Over the past two years, we\u2019ve explored Resque internals and plugins. We\u2019d like to share what we\u2019ve learned from our practical experience using Resque during different phases of the web application life&#8230;<\/p>\n","protected":false},"author":15,"featured_media":3766,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[3],"tags":[],"coauthors":["Bogdan Gusiev"],"class_list":["post-744","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development"],"acf":[],"aioseo_notices":[],"categories_data":[{"name":"Engineering","link":"https:\/\/railsware.com\/blog?category=development"}],"post_thumbnails":"https:\/\/railsware.com\/blog\/wp-content\/themes\/railsware\/vendors\/images\/article-thumbnail-default.jpg","amp_enabled":true,"_links":{"self":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts\/744","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/users\/15"}],"replies":[{"embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/comments?post=744"}],"version-history":[{"count":18,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts\/744\/revisions"}],"predecessor-version":[{"id":14169,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts\/744\/revisions\/14169"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/media\/3766"}],"wp:attachment":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/media?parent=744"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/categories?post=744"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/tags?post=744"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/coauthors?post=744"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}