More Related Content Similar to A Scalable Rails App Deployed in 60 Seconds (20) More from Ben Scheirman (6) A Scalable Rails App Deployed in 60 Seconds2. Ben Scheirman
• Director of Development at ChaiONE
• Microsoft MVP, ASP Insider
• Certified ScrumMaster
• Former .NET Ninja, now Rails/iPhone
aficionado
2
25. # Users by time of day
400
300
200
100
0
12 AM 3AM 6AM 9AM 12PM 3PM 6PM 9PM 12AM
16
26. # Users by time of day
400
300
200
100
0
12 AM 3AM 6AM 9AM 12PM 3PM 6PM 9PM 12AM
16
27. # Users by time of day
400
300
200
100
0
12 AM 3AM 6AM 9AM 12PM 3PM 6PM 9PM 12AM
Only need massive scale from 9am - 9pm
16
28. Expected Users For PR Campaign
900
675
450
225
0
Mar 1 Mar 15 Apr 1 Apr 15 May 1 May 15 Jun 1 Jun 2
17
29. Expected Users For PR Campaign
900
675
450
225
0
Mar 1 Mar 15 Apr 1 Apr 15 May 1 May 15 Jun 1 Jun 2
17
34. • Platform as a service for Rack-based apps
• Scale easily
• Great workflow
• Free to start / Friendly Pricing
• Complete command-line API
21
35. Deploy a rails app in 5 steps
git init
git add .
git commit -m “My First Commit”
heroku create
git push heroku master
Creating morning-summer-18..... done
Created http://morning-summer-18.heroku.com/ | git@heroku.com:morning-summer-18.git
22
36. Deploy a rails app in 5 steps
git init
git add .
git commit -m “My First Commit”
heroku create
git push heroku master
Creating morning-summer-18..... done
Created http://morning-summer-18.heroku.com/ | git@heroku.com:morning-summer-18.git
LET THAT SOAK IN...
22
38. Technology
• Amazon Web Services
• Caching w/ Varnish
• Debian Linux
• Erlang Routing Mesh
• Nginx
• PostgreSQL
24
47. When to add dynos
• Increasing response times
• “Backlog Too Deep” Error
28
49. Heroku Tips
• Multiple Heroku apps for Test, Staging,
Production
• Learn the awesome-ness of Taps
• Heroku logs
• Heroku Console
• Useful addons: New Relic, Exceptional
30
51. Coping with the Free Single Bundle
task :backup do
bundle_name = "my-backup"
heroku_app = ENV['app']
def wait_for_response_text(text)
heroku_app = ENV['app'] #lame, I know
begin
puts "."
bundles = `heroku bundles --app #{heroku_app}`
end while bundles.match(text).nil?
end
#check for existing bundle
bundles = `heroku bundles --app #{heroku_app}`
unless bundles.match /has no bundles/
`heroku bundles:destroy #{bundle_name} --app #{heroku_app}`
wait_for_response_text 'has no bundles'
end
puts "Capture bundle #{bundle_name}..."
`heroku bundles:capture #{bundle_name} --app #{heroku_app}`
wait_for_response_text "complete"
puts "Downloading bundle..."
`heroku bundles:download #{bundle_name} --app #{heroku_app}`
timestamp = `date -u '+%Y-%m-%d-%H-%M'`.chomp
new_path = "backups/#{heroku_app}-#{timestamp}.tar.gz"
mkdir "backups" unless File.exists? "backups"
`mv #{heroku_app}.tar.gz #{new_path}`
puts "Bundle saved to #{new_path}" end
end
32
52. Coping with the Free Single Bundle
task :backup do
bundle_name = "my-backup"
heroku_app = ENV['app']
def wait_for_response_text(text)
heroku_app = ENV['app'] #lame, I know
begin
puts "."
bundles = `heroku bundles --app #{heroku_app}`
end while bundles.match(text).nil?
end
#check for existing bundle
bundles = `heroku bundles --app #{heroku_app}`
unless bundles.match /has no bundles/
`heroku bundles:destroy #{bundle_name} --app #{heroku_app}`
wait_for_response_text 'has no bundles'
end
puts "Capture bundle #{bundle_name}..."
`heroku bundles:capture #{bundle_name} --app #{heroku_app}`
wait_for_response_text "complete"
puts "Downloading bundle..."
`heroku bundles:download #{bundle_name} --app #{heroku_app}`
timestamp = `date -u '+%Y-%m-%d-%H-%M'`.chomp
new_path = "backups/#{heroku_app}-#{timestamp}.tar.gz"
mkdir "backups" unless File.exists? "backups"
`mv #{heroku_app}.tar.gz #{new_path}`
puts "Bundle saved to #{new_path}" end
end
33
53. Coping with the Free Single Bundle
task :backup do
bundle_name = "my-backup"
heroku_app = ENV['app']
def wait_for_response_text(text)
heroku_app = ENV['app'] #lame, I know
begin
puts "."
bundles = `heroku bundles --app #{heroku_app}`
end while bundles.match(text).nil?
end
#check for existing bundle
bundles = `heroku bundles --app #{heroku_app}`
unless bundles.match /has no bundles/
`heroku bundles:destroy #{bundle_name} --app #{heroku_app}`
wait_for_response_text 'has no bundles'
end
puts "Capture bundle #{bundle_name}..."
`heroku bundles:capture #{bundle_name} --app #{heroku_app}`
wait_for_response_text "complete"
puts "Downloading bundle..."
`heroku bundles:download #{bundle_name} --app #{heroku_app}`
timestamp = `date -u '+%Y-%m-%d-%H-%M'`.chomp
new_path = "backups/#{heroku_app}-#{timestamp}.tar.gz"
mkdir "backups" unless File.exists? "backups"
`mv #{heroku_app}.tar.gz #{new_path}`
puts "Bundle saved to #{new_path}" end
end
34
54. Coping with the Free Single Bundle
task :backup do
bundle_name = "my-backup"
heroku_app = ENV['app']
def wait_for_response_text(text)
heroku_app = ENV['app'] #lame, I know
begin
puts "."
bundles = `heroku bundles --app #{heroku_app}`
end while bundles.match(text).nil?
end
#check for existing bundle
bundles = `heroku bundles --app #{heroku_app}`
unless bundles.match /has no bundles/
`heroku bundles:destroy #{bundle_name} --app #{heroku_app}`
wait_for_response_text 'has no bundles'
end
puts "Capture bundle #{bundle_name}..."
`heroku bundles:capture #{bundle_name} --app #{heroku_app}`
wait_for_response_text "complete"
puts "Downloading bundle..."
`heroku bundles:download #{bundle_name} --app #{heroku_app}`
timestamp = `date -u '+%Y-%m-%d-%H-%M'`.chomp
new_path = "backups/#{heroku_app}-#{timestamp}.tar.gz"
mkdir "backups" unless File.exists? "backups"
`mv #{heroku_app}.tar.gz #{new_path}`
puts "Bundle saved to #{new_path}" end
end
35
55. Coping with the Free Single Bundle
task :backup do
bundle_name = "my-backup"
heroku_app = ENV['app']
def wait_for_response_text(text)
heroku_app = ENV['app'] #lame, I know
begin
puts "."
bundles = `heroku bundles --app #{heroku_app}`
end while bundles.match(text).nil?
end
#check for existing bundle
bundles = `heroku bundles --app #{heroku_app}`
unless bundles.match /has no bundles/
`heroku bundles:destroy #{bundle_name} --app #{heroku_app}`
wait_for_response_text 'has no bundles'
end
puts "Capture bundle #{bundle_name}..."
`heroku bundles:capture #{bundle_name} --app #{heroku_app}`
wait_for_response_text "complete"
puts "Downloading bundle..."
`heroku bundles:download #{bundle_name} --app #{heroku_app}`
timestamp = `date -u '+%Y-%m-%d-%H-%M'`.chomp
new_path = "backups/#{heroku_app}-#{timestamp}.tar.gz"
mkdir "backups" unless File.exists? "backups"
`mv #{heroku_app}.tar.gz #{new_path}`
puts "Bundle saved to #{new_path}" end
end
36
56. Coping with the Free Single Bundle
task :backup do
bundle_name = "my-backup"
heroku_app = ENV['app']
def wait_for_response_text(text)
heroku_app = ENV['app'] #lame, I know
begin
puts "."
bundles = `heroku bundles --app #{heroku_app}`
end while bundles.match(text).nil?
end
#check for existing bundle
bundles = `heroku bundles --app #{heroku_app}`
unless bundles.match /has no bundles/
`heroku bundles:destroy #{bundle_name} --app #{heroku_app}`
wait_for_response_text 'has no bundles'
end
puts "Capture bundle #{bundle_name}..."
`heroku bundles:capture #{bundle_name} --app #{heroku_app}`
wait_for_response_text "complete"
puts "Downloading bundle..."
`heroku bundles:download #{bundle_name} --app #{heroku_app}`
timestamp = `date -u '+%Y-%m-%d-%H-%M'`.chomp
new_path = "backups/#{heroku_app}-#{timestamp}.tar.gz"
mkdir "backups" unless File.exists? "backups"
`mv #{heroku_app}.tar.gz #{new_path}`
puts "Bundle saved to #{new_path}" end
end
37
57. Coping with the Free Single Bundle
task :backup do
bundle_name = "my-backup"
heroku_app = ENV['app']
def wait_for_response_text(text)
heroku_app = ENV['app'] #lame, I know
begin
puts "."
bundles = `heroku bundles --app #{heroku_app}`
end while bundles.match(text).nil?
end
#check for existing bundle
bundles = `heroku bundles --app #{heroku_app}`
unless bundles.match /has no bundles/
`heroku bundles:destroy #{bundle_name} --app #{heroku_app}`
wait_for_response_text 'has no bundles'
end
puts "Capture bundle #{bundle_name}..."
`heroku bundles:capture #{bundle_name} --app #{heroku_app}`
wait_for_response_text "complete"
puts "Downloading bundle..."
`heroku bundles:download #{bundle_name} --app #{heroku_app}`
timestamp = `date -u '+%Y-%m-%d-%H-%M'`.chomp
new_path = "backups/#{heroku_app}-#{timestamp}.tar.gz"
mkdir "backups" unless File.exists? "backups"
`mv #{heroku_app}.tar.gz #{new_path}`
puts "Bundle saved to #{new_path}" end
end
38
58. Coping with the Free Single Bundle
task :backup do
bundle_name = "my-backup"
heroku_app = ENV['app']
def wait_for_response_text(text)
heroku_app = ENV['app'] #lame, I know
begin
puts "."
bundles = `heroku bundles --app #{heroku_app}`
end while bundles.match(text).nil?
end
#check for existing bundle
bundles = `heroku bundles --app #{heroku_app}`
unless bundles.match /has no bundles/
`heroku bundles:destroy #{bundle_name} --app #{heroku_app}`
wait_for_response_text 'has no bundles'
end
puts "Capture bundle #{bundle_name}..."
`heroku bundles:capture #{bundle_name} --app #{heroku_app}`
wait_for_response_text "complete"
puts "Downloading bundle..."
`heroku bundles:download #{bundle_name} --app #{heroku_app}`
timestamp = `date -u '+%Y-%m-%d-%H-%M'`.chomp
new_path = "backups/#{heroku_app}-#{timestamp}.tar.gz"
mkdir "backups" unless File.exists? "backups"
`mv #{heroku_app}.tar.gz #{new_path}`
puts "Bundle saved to #{new_path}" end
end
39
59. One-button Deployments
task :deploy do
heroku_app = ENV['app']
heroku_config = Hash.new
Rake::Task["heroku:backup"].invoke
puts "Turning maintenance on..."
puts "** " + `heroku maintenance:on --app #{heroku_app}`
sleep 15 #maintenance mode causes your slug to be recompiled
puts "Pushing changes to heroku..."
puts "** " + `git push #{git_remotes[heroku_app]} master`
puts "Migrating heroku databases..."
puts "** " + `heroku rake db:migrate --app #{heroku_app}`
puts "Setting up config variables..."
heroku_config.each_pair do |k, v|
puts "** " + `heroku config:add #{k.to_s.upcase}=#{v}`
end
puts "Turning maintenance mode off"
puts "** " + `heroku maintenance:off --app #{heroku_app}`
puts "DEPLOYMENT DONE!"
end
40
62. Delayed Jobs
def send_bulk_emails
BulkEmailSender.send_a_ton
flash[:notice] => "Ok we sent them"
end
def send_bulk_emails
BulkEmailSender.send_later(:send_a_ton)
flash[:notice] => "Ok we sent them"
end
41
65. Delayed Jobs
> gem install delayed_job
> rails g delayed_job
> rake db:migrate
> heroku workers 1
simple-snow-68 now running 1 worker
> heroku workers +1
simple-snow-68 now running 2 workers
42
68. Delayed Jobs
$0.05 per hour / worker
(even when it's not processing Jobs)
http://github.com/pedro/delayed_job/commit/
09d7657e1fc7d25072e6c5e73ede20d6e1185eac
http://bit.ly/9Qokff
43
70. Delayed Jobs
Pedro's Fork / auto-scale branch
//lib/delayed/job.rb
def after_create
Manager.scale_up if self.class.auto_scale &&
Manager.qty == 0
end
//lib/delayed/worker.rb
Manager.scale_down if count.zero? &&
Job.auto_scale &&
Job.count == 0
44
71. • Read-only file system
• some gems don’t work
• File uploads?
• Temp Directory
• SSL, memcached, Full-text-search expensive
• Bound-by Amazon’s SLA
45
72. contact = {
! :blog => http://flux88.com,
!
! :twitter => @subdigital,
!
! :email => ben@scheirman.com
}
46