34

What is the best way to automatically clear Memcached when I deploy my rails app to Heroku?

I'm caching the home page, and when I make changes and redeploy, the page is served from the cache, and the updates aren't incorporated.

I want to have this be totally automated. I don't want to have to clear the cache in the heroku console each time I deploy.

Thanks!

Solomon
  • 6,145
  • 3
  • 25
  • 34

9 Answers9

33

I deploy my applications using a bash script that automates GitHub & Heroku push, database migration, application maintenance mode activation and cache clearing action.

In this script, the command to clear the cache is :

heroku run --app YOUR_APP_NAME rails runner -e production Rails.cache.clear

This works with Celadon Cedar with the Heroku Toolbelt package. I know this is not a Rake-based solution however it's quite efficient.

Note : be sure you set the environment / -e option of the runner command to production as it will be executed on the development one otherwise.

Edit : I have experienced issues with this command on Heroku since a few days (Rails 3.2.21). I did not have time to check the origin the issue but removing the -e production did the trick, so if the command does not succeed, please run this one instead :

heroku run --app YOUR_APP_NAME rails runner Rails.cache.clear
Fabrice Carrega
  • 659
  • 1
  • 7
  • 14
23

[On the Celadon Cedar Stack]

-- [Update 18 June 2012 -- this no longer works, will see if I can find another workaround]

The cleanest way I have found to handle these post-deploy hooks is to latch onto the assets:precompile task that is already called during slug compilation. With a nod to asset_sync Gem for the idea:

Rake::Task["assets:precompile"].enhance do
  # How to invoke a task that exists elsewhere
  # Rake::Task["assets:environment"].invoke if Rake::Task.task_defined?("assets:environment")

  # Clear cache on deploy
  print "Clearing the rails memcached cache\n"
  Rails.cache.clear
end

I just put this in a lib/tasks/heroku_deploy.rake file and it gets picked up nicely.

Hollownest
  • 1,067
  • 10
  • 10
  • 1
    Wish I could vote more than once, this is honestly the cleanest way. – Wam Mar 13 '12 at 09:12
  • Nice solution. I don't see the print message when I deploy though. Can anyone else confirm this works? According to http://stackoverflow.com/questions/9966408/why-does-rake-task-enhancement-differ-between-my-local-environment-and-when-depl it might not work. – devth Jun 14 '12 at 14:36
  • 1
    I confirmed that this does not work. Heroku runs `assets:precompile:digest`, but even if you `enahance` that task, it will just cause precompile to fail. – devth Jun 16 '12 at 18:28
  • This has stopped working since I initially implemented it. Will try to find another solution. – Hollownest Jun 19 '12 at 02:01
  • @Hollownest, the `lib/tasks/heroku_deploy.rake` is called automatically? – Luccas Nov 21 '12 at 01:36
8

What I ended up doing was creating a new rake task that deployed to heroku and then cleared the cache. I created a deploy.rake file and this is it:

namespace :deploy do

    task :production do
        puts "deploying to production"
        system "git push heroku"
        puts "clearing cache"
        system "heroku console Rails.cache.clear"
        puts "done"
    end

end

Now, instead of typing git push heroku, I just type rake deploy:production.

thewillcole
  • 2,943
  • 3
  • 30
  • 35
Solomon
  • 6,145
  • 3
  • 25
  • 34
7

25 Jan 2013: this is works for a Rails 3.2.11 app running on Ruby 1.9.3 on Cedar

In your Gemfile add the following line to force ruby 1.9.3:

ruby '1.9.3'

Create a file named lib/tasks/clear_cache.rake with this content:

if Rake::Task.task_defined?("assets:precompile:nondigest")
  Rake::Task["assets:precompile:nondigest"].enhance do
    Rails.cache.clear
  end
else
  Rake::Task["assets:precompile"].enhance do
    # rails 3.1.1 will clear out Rails.application.config if the env vars
    # RAILS_GROUP and RAILS_ENV are not defined. We need to reload the
    # assets environment in this case.
    # Rake::Task["assets:environment"].invoke if Rake::Task.task_defined?("assets:environment")
    Rails.cache.clear
  end
end

Finally, I also recommend running heroku labs:enable user-env-compile on your app so that its environment is available to you as part of the precompilation.

Mike
  • 9,692
  • 6
  • 44
  • 61
  • This works for me, but only the :nondigest task is run. Wonder why, as both assets with and without digest are generated. – Nico Mar 13 '13 at 07:56
2

Aside from anything you can do inside your application that runs on 'application start' you could use the heroku deploy hooks (http://devcenter.heroku.com/articles/deploy-hooks#http_post_hook) that would hit a URL within your application that clears the cache

John Beynon
  • 37,398
  • 8
  • 88
  • 97
  • I'm leaning towards this, but wonder how reliable heroku's deploy hooks are? Does it resend on failure? How long is the timeout (have to spin up a dyno to receive). – nruth Aug 20 '12 at 19:46
0

I've added config/initializers/expire_cache.rb with

ActionController::Base.expire_page '/'

Works sweet!

Uko
  • 13,134
  • 6
  • 58
  • 106
  • Won't this clear the cache each time a web dyno is launched? Otherwise it'd be a nice clean solution. – nruth Aug 20 '12 at 19:44
  • @nruth I have no idea. Too bad I can't look into that right now. Maybe you can find it out and tell us about it. Or I'll do it some time later. – Uko Aug 22 '12 at 06:24
  • 2
    @nruth is right. This is clearing your cache every time your app starts, which could be happening more than you think. Solomon's answer above is the best approach. – xentek Nov 12 '12 at 02:31
  • 1
    @xentek one more question. When you are running app on one worker that's always awake is the app starting also a lot of times? – Uko Nov 12 '12 at 07:27
  • Yes. In fact you are more probably more likely to have your dyno restarted with an app that only has one worker... heroku kind of puts free apps to "sleep" if there aren't active requests, which may mean that it has to start your app on the next request. You can kind of get around this by pinging the app every few minutes. – xentek Nov 14 '12 at 05:34
  • @xentek I know about ponging, that's why I was asking about that :). So for this particular setup my solution works, but this is anyway a sort of hack for some particular problem, so please excuse me for misleading. – Uko Nov 14 '12 at 08:33
0

Since the heroku gem is deprecated, an updated version of Solomons very elegant answer would be to save the following code in lib/tasks/heroku_deploy.rake:

namespace :deploy do
    task :production do
        puts "deploying to production"
        system "git push heroku"
        puts "clearing cache"
        system "heroku run rake cache:clear"
        puts "done"
    end
end

namespace :cache do
  desc "Clears Rails cache"
  task :clear => :environment do
    Rails.cache.clear
  end
end

then instead of git push heroku master you type rake deploy:production in command line. To just clear the cache you can run rake cache:clear

wnm
  • 1,349
  • 13
  • 12
0

The solution I like to use is the following:

First, I implement a deploy_hook action that looks for a parameter that I set differently for each app. Typically I just do this on the on the "home" or "public" controller, since it doesn't take that much code.

### routes.rb ###

post 'deploy_hook' => 'home#deploy'

### home_controller.rb ###

def deploy_hook
  Rails.cache.clear if params[:secret] == "a3ad3d3"
end

And, I simply tell heroku to setup a deploy hook to post to that action whenever I deploy!

heroku addons:add deployhooks:http \
   --url=http://example.com/deploy_hook?secret=a3ad3d3

Now, everytime that I deploy, heroku will do an HTTP post back to the site to let me know that the deploy worked just fine.

Works like a charm for me. Of course, the secret token not "high security" and this shouldn't be used if there were a good attack vector for taking your site down if caches were cleared. But, honestly, if the site is that critical to attack, then don't host it on Heroku! However, if you wanted to increase the security a bit, then you could use a Heroku configuration variable and not have the 'token' in the source code at all.

Hope people find this useful.

hcatlin
  • 236
  • 2
  • 6
0

I just had this problem as well but wanted to stick to the git deployment without an additional script as a wrapper.

So my approach is to write a file during slug generation with an uuid that marks the current precompilation. This is impelmented as a hook in assets:precompile.

# /lib/tasks/store_asset_cacheversion.rake
# add uuidtools to Gemfile

require "uuidtools"

def storeCacheVersion
  cacheversion = UUIDTools::UUID.random_create
  File.open(".cacheversion", "w") { |file| file.write(cacheversion) }
end

Rake::Task["assets:precompile"].enhance do
  puts "Storing git hash in file for cache invalidation (assets:precompile)\n"
  storeCacheVersion
end

Rake::Task["assets:precompile:nondigest"].enhance do
  puts "Storing git hash in file for cache invalidation (assets:precompile:nondigest)\n"
  storeCacheVersion
end

The other is an initializer that checks this id against the cached version. If they differ, there has been another precompilation and the cache will be invalidated.

So it dosen't matter how often the application spins up or down or on how many nodes the worker will be distributed, because the slug generation just happens once.

# /config/initializers/00_asset_cache_check.rb

currenthash = File.read ".cacheversion"
cachehash   = Rails.cache.read "cacheversion"

puts "Checking cache version: #{cachehash} against slug version: #{currenthash}\n"

if currenthash != cachehash
  puts "flushing cache\n"
  Rails.cache.clear
  Rails.cache.write "cacheversion", currenthash
else
  puts "cache ok\n"
end

I needed to use a random ID because there is as far as I know no way of getting the git hash or any other useful id. Perhaps the ENV[REQUEST_ID] but this is an random ID as well.

The good thing about the uuid is, that it is now independent from heroku as well.

bitcloud
  • 46
  • 6