Clear Memcached on Heroku Deploy - ruby-on-rails

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!

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

[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.

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.

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.

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

I've added config/initializers/expire_cache.rb with
ActionController::Base.expire_page '/'
Works sweet!

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

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.

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.

Related

Why does Travis fail to connect use Redis cache_store when deploying to Heroku?

I use Redis caching in my Rails app:
config.cache_store = :redis_store, redis_url
When I push my Rails app straight to Heroku, it is deployed successfully. When using Travis, the Heroku deploy step fails because the asset precompilation attempts to connect to Redis.
Running: rake assets:precompile
rake aborted!
ArgumentError: invalid uri scheme ''
/tmp/build_7c5f167bf750cb2986dbb9c3510ea11e/vendor/bundle/ruby/2.1.0/gems/redis-3.2.0/lib/redis/client.rb:390:in `_parse_options'
I have tried various things: overriding RedisStore methods using rake tasks, moving the cache_store instantiation to the initialization phase, using Docker instead of sudo, changing Heroku build strategy and other travis.yml configurations etc.
I don't want to precompile locally, and I'd rather not change caching solution. Many other apps running on the cedar-14 stack use a very similar setup so the issue seems a bit peculiar.
Any suggestions how to resolve this Travis+Heroku deploy issue?
In my case, I solved this by changing the redis init to:
REDIS = Redis.new(:url => redis_url_string)
where previously I was parsing the URI and passing in the arguments as:
uri = URI.parse(redis_url)
REDIS = Redis.new(:host => uri.host, :port => uri.port, :password => uri.password, :scheme => uri.scheme)
I wonder if the cache store init has a similar init implementation for redis (I haven't checked the source there).
We solved the problem by overriding the Redis::Store initialization. When using Travis to trigger the Heroku deploy, Redis Store tries to connect to Redis. This is probably due to the current version (Nov 2013) of the Redis Store gem not being compatible with the current asset pipeline implementation. The reason why this works when pushing straight to Heroku is unclear. It could be related to the order that assets are compiled, when using the Heroku build strategies as specified in the travis.yml file. Perhaps these issues will be resolved in future Redis Store versions.
This is the rake task used to avoid loading Redis when using Redis Store as cache store (lib/assets/tasks/assets.rake):
pt = Rake::Task['assets:environment']
Rake.application.send(:eval, "#tasks.delete('assets:environment')")
namespace :assets do
task :environment do
class Redis
class Store
def initialize(options = { })
puts ”Do nothing"
end
end
def initialize(options = { })
puts ”Do nothing"
end
end
pt.execute
end
end
This is not a very elegant solution, but it does the trick for now. Consider changing the caching solution instead.

debugging admin_data on heroku rails

I keep getting the error "not authorized" whenever i try to visit:
appname.herokuapp.com/admin_data
I've created an admin.rb file with
AdminData.config do |config|
#comment
config.is_allowed_to_view = lambda {|controller| return true if (Rails.env.development? || Rails.env.production?) }
end
(I know the above following has a safety issue but I'm just trying to get it work before I check for administration.)
I've tried
bundle exec rake assets:precompile
and also
RAILS_ENV=production rake assets:precompile
prior to pushing to heroku but i can't seem to get past that error.
I've followed the instructions from
https://github.com/bigbinary/admin_data
but can't seem to get it to work on production.
On my development machine it works.
http://localhost:3000/admin_data shows everything belonging to my dev environment.
Are you certain that your heroku instance is in production mode? Sometimes instances on Heroku are configured as staging.
Try this:
heroku config --app your_app_name
This will return the config variables of your instance. You will see a variety of config variables. Look for these:
RACK_ENV => production
RAILS_ENV => production
Are they set to production or staging?
Have you read the Production Configuration section of https://github.com/bigbinary/admin_data/wiki/admin_data-security-configuration-for-a-Rails3-application - seems like there are extra steps you need to take when your application is running in production mode.
ah it was a dumb mistake. i was not pushing from my master branch and my app was never being updated

How do I keep log messages out of STDOUT when running Rake tasks on Heroku?

I have some Rake tasks that produce CSV output which I'd like to redirect to a file and open with other tools, but when I run heroku rake foo > foo.csv I get log messages (SQL queries, etc.) in my output.
I've tried Rails.logger = Logger.new('/dev/null') and Rails.logger = Logger.new(STDERR) at the top of the Rake task and while those function as expected locally, they don't have any noticeable effect when I run the task on Heroku.
I'm not too shocked that Heroku would squash STDOUT and STDERR together but it's a mystery to me why sending to /dev/null would not kill the output.
Any help greatly appreciated.
Rails v3.0.0, Heroku bamboo-ree-1.8.7 stack, rake 0.9.2.
I was having the same problem, though I didn't run into it until I changed config/environments/production.rb to have this:
config.logger = Logger.new(STDOUT)
(I did this so that my app would log to the heroku log.)
My fix was this:
config.logger = Logger.new(STDOUT) unless 'rake' == File.basename($0)
From Heroku | Dev Center | Logging:
When a Rails app is pushed, we will automatically install the rails_log_stdout plugin into the application which will redirect logs to stdout.
I think Heroku includes (in the output sent through your git push command) a notification about this (and one other addition: for serving static/public content, if I remember correctly). You may only see the notifications for certain kinds of pushes though (complete slug rebuilds?). I remember seeing it when I recently pushed a new application to a Bamboo/MRI-1.9.2 stack, but I do not think I got the message every time I pushed changes to just the application’s code (maybe adding a new gem to the Gemfile is enough to trigger it?).
Several Rails subsystems keep their own logger binding (independent bindings whose values are often initialized from Rails.logger; reassigning the latter does not change the former):
ActiveRecord::Base.logger
ActionController::Base.logger
ActionMailer::Base.logger
Heroku’s changes probably set a new value for Rails.logger before ActiveRecord is initialized. When ActiveRecord is eventually loaded, it sets its own logger to be the same as Rails.logger (the Heroku/stdout one). When your task runs, it reassigns Rails.logger, but it is too late for this to have any effect on ActiveRecord::Base.logger (the only most likely to be handling the SQL logs).
You probably need to reassign some of these other logger bindings to squelch the logging going to STDOUT. Some other likely locations are listed in rails_log_stdout’s init.rb in the Rails 2 section.
I faced the same problem and found the following to be a more convenient workaround:
Add the following to config/environments/production.rb
config.logger.level = Logger.const_get(ENV['LOG_LEVEL'] ? ENV['LOG_LEVEL'].upcase : 'INFO')
Push to Heroku, then when you run your rake tasks add LOG_LEVEL="fatal" to the end of the command (replace foo and foo.csv with your things):
heroku run rake foo LOG_LEVEL="fatal" > foo.csv
I have log_level set to fatal in the above example, but it can be any of the following: debug|info|warn|error|fatal. In our case, using the highest would mean nothing but the most fatal errors are outputted into the csv file.
Just to help anyone with a "fresh" Rails project pushing to Heroku:
You need a combination of #Matt Burke and #Hengjie's answer:
Add these two lines to config/environments/production.rb:
config.logger = Logger.new(STDOUT)
config.logger.level = Logger.const_get(ENV['LOG_LEVEL'] ? ENV['LOG_LEVEL'].upcase : 'INFO')
This will setup a new STDOUT logger and allow you to easily control the log resolution with the LOG_LEVEL environment variable.
I solved this problem with the following change to production.rb:
if 'rake' == File.basename($0)
ActiveRecord::Base.logger = Logger.new('rake.log', 'daily')
end
I suppose we could ignore the output as well
if 'rake' == File.basename($0)
ActiveRecord::Base.logger = Logger.new('/dev/null')
end

Heroku rake task not loading ActionMailer class

I have a rake task that delivers daily emails via ActionMailer. My plan is to use Heroku's cron job to run this task nightly.
The problem is that my ActionMailer class (Notifier) is not recognized with Heroku. The specific error is "Unitialized constant Notifier." However, the Notifier class properly sends out emails in Heroku everywhere else in the application (controllers and models) but not from the rake task. Running the rake task also does work locally.
If I do heroku rake cron to run the task manually, it throws that error.
heroku rake cron
This doesn't work. However,
rake RAILS_ENV=production cron
works fine (Heroku vs. local).
I've even tried adding my mailer path to the autoload directory.
I think in this case seeing your code would be helpful. I implemented the same exact thing this past weekend on heroku and had no problems. For me my rake task calls a method on my model like so...
#note this has to be in lib/tasks and be named cron.rake
desc "Send mailing"
task :cron => :environment do
Lease.updates
end
My model has the following method...
def self.updates
//setting up params
UserMailer.deliver_report_due(user, #leases)
end
This will call deliver on your method in your UserMailer class.
def report_due(user, leases)
recipients user.email
from "email#email.com"
subject "Confirmation"
body :leases=>leases,
:user=>user
end
Also note that this is rails 2.3 in which you call UserMailer.deliver_method, In rails 3 I think it is UserMailer.method_deliver.
If all else fails you could try restarting your heroku app on the command line with 'heroku restart'
Heroku finally figured out what was going on. I originally created my Notifier class file named as "Notifier" (capitalized first letter) instead of "notifier". I realized the mistake and manually changed the filename, however I had already did a git commit. Well since Max OS X is case insensitive for filenames, changing the capitalization to lowercase didn't commit to git. So everything worked locally on my Macbook, and showed that the filename was correct, but deploying to Heroku pushed a improperly named file. Even worse is that it only was affected under the rake tasks.
I don't think I would have ever figured this out without Heroku.
Try to write
require 'notifier'
in your rake task file

Rails + Heroku + Jammit

I'm working to install Jammit on my Rails 3 app and then to deploy to Heroku.
I installed the Jammit Gem, and configured assets.yml just fine, it works on dev. But when I pushed to heroku, the files were 404'ing.
Jammit's Usage instructions say: "You can easily use Jammit within your Rakefile, and other scripts:
require 'jammit'
Jammit.package!
I'm not following where/how that works. Running Jammit in my sites command like on the Mac yields a command not found.
Any Jammit users able to help me understand how to move to production with Jammit?
Thanks
I'm using jammit on a Rails 3.0.7 app, on Heroku
gem "jammit", :git => "git://github.com/documentcloud/jammit.git"
I have this in a rake file, to package up the assets before I commit/deploy
desc 'jammit'
task :jam => :environment do
require 'jammit'
Jammit.package!
end
And this in .git/hooks/pre-commit so it is done automatically
echo "jamming it"
rake jam
git add public/assets/*
git add public/javascripts/*
By default, the expires time on Heroku was only 12hrs, to increase it (because I have a cache-busting scheme that I am confident in) I put this in config/initializers/heroku.rb
module Heroku
class StaticAssetsMiddleware
def cache_static_asset(reply)
return reply unless can_cache?(reply)
status, headers, response = reply
headers["Expires"] = CGI.rfc1123_date(11.months.from_now)
build_new_reply(status, headers, response)
end
end
end
To decrease the load on my Heroku Rails server, I am also using a free account at CloudFlare which provides a lightweight, reverse-proxy/cdn, with some decent analytics and security features.
When I get around to caching common content, this thing is really gonna scream!
You could, as I do, use jammit force to pack your assets, upload everything to s3 and define an asset host(s) in rails. This has the added advantage of keeping your slug smaller and more responsive as you can add your public directory to .slugignore .
Alternatively you'll need to work out how to make the heroku version work due to the read only file system.
You can also use a git pre-commit hook to ensure your assets are packaged prior to pushing to heroku (or any server). See https://gist.github.com/862102 for an example. You can copy that file to .git/hooks/pre-commit in your project directory.
this one is the solution
https://github.com/kylejginavan/heroku_jammit
Heroku has a read-only file system, so Jammit is unable to actually store compressed and minified CSS/JS files.
Here is a very good article on the challenge of asset packaging on heroku: http://jimmycuadra.com/posts/the-challenge-of-asset-packaging-on-heroku

Resources