Upload
ben-scheirman
View
4.087
Download
0
Tags:
Embed Size (px)
DESCRIPTION
I gave this talk at Lone Star Ruby Conference in Austin on 8/28/2010.
Citation preview
A Scalable Rails app Deployed in 60
secondsA love affair with Rails & Heroku.
1
Ben Scheirman
• Director of Development at ChaiONE
• Microsoft MVP, ASP Insider
• Certified ScrumMaster
• Former .NET Ninja, now Rails/iPhone aficionado
2
Ben Scheirman
• ...not affiliated with Heroku
3
So you have a fantastic app?
4
Now deploy it...
5
Shared HostingVirtual Dedicated
Dedicated6
Shared Hosting
7
Shared Hosting
7
Shared Hosting
7
Shared Hosting
7
Shared Hosting
7
Shared Hosting
7
Virtual Dedicated
8
Virtual Dedicated
8
Dedicated
9
Scale
10
11
x 1,000?11
12
x 10,000?12
writes
reads
13
x 1,000,000?
writes
reads
13
14
ScaleOn-Demand
15
0
100
200
300
400
12 AM 3AM 6AM 9AM 12PM 3PM 6PM 9PM 12AM
# Users by time of day
16
0
100
200
300
400
12 AM 3AM 6AM 9AM 12PM 3PM 6PM 9PM 12AM
# Users by time of day
16
0
100
200
300
400
12 AM 3AM 6AM 9AM 12PM 3PM 6PM 9PM 12AM
# Users by time of day
Only need massive scale from 9am - 9pm
16
0
225
450
675
900
Mar 1 Mar 15 Apr 1 Apr 15 May 1 May 15 Jun 1 Jun 2
Expected Users For PR Campaign
17
0
225
450
675
900
Mar 1 Mar 15 Apr 1 Apr 15 May 1 May 15 Jun 1 Jun 2
Expected Users For PR Campaign
17
Wouldn’t this be magical?
SCALE
18
Forget about servers19
Awesome Shredder Art by Dan Barret
20
Awesome Shredder Art by Dan Barret
gem install heroku
20
• Platform as a service for Rack-based apps
• Scale easily
•Great workflow
• Free to start / Friendly Pricing
• Complete command-line API
21
git initgit add .git commit -m “My First Commit”heroku creategit push heroku master
Deploy a rails app in 5 steps
Creating morning-summer-18..... doneCreated http://morning-summer-18.heroku.com/ | [email protected]:morning-summer-18.git
22
git initgit add .git commit -m “My First Commit”heroku creategit push heroku master
Deploy a rails app in 5 steps
LET THAT SOAK IN...
Creating morning-summer-18..... doneCreated http://morning-summer-18.heroku.com/ | [email protected]:morning-summer-18.git
22
Using git for deployment is f***ing GENIUS
23
• Amazon Web Services
• Caching w/ Varnish
• Debian Linux
• Erlang Routing Mesh
• Nginx
• PostgreSQL
Technology
24
25
How Heroku Scales
26
How Heroku Scalesor WTF is a Dyno?
26
How Heroku Scalesor WTF is a Dyno?
26
How Heroku Scalesor WTF is a Dyno?
26
How Heroku Scales
27
How Heroku Scales
27
How Heroku Scales
27
• Increasing response times
• “Backlog Too Deep” Error
When to add dynos
28
• New Relic RPM
• Apache Bench
Always Measure
29
• Multiple Heroku apps for Test, Staging, Production
• Learn the awesome-ness of Taps
• Heroku logs
• Heroku Console
• Useful addons: New Relic, Exceptional
Heroku Tips
30
Backups
31
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
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
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
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
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
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
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
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
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
Delayed Jobs
41
Delayed Jobs
def send_bulk_emails
BulkEmailSender.send_a_tonflash[:notice] => "Ok we sent them"
end
41
Delayed Jobs
def send_bulk_emails
BulkEmailSender.send_a_tonflash[:notice] => "Ok we sent them"
end
def send_bulk_emails
BulkEmailSender.send_later(:send_a_ton)flash[:notice] => "Ok we sent them"
end
41
Delayed Jobs
42
Delayed Jobs
> gem install delayed_job> rails g delayed_job > rake db:migrate
42
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
Delayed Jobs
43
Delayed Jobs
$0.05 per hour / worker(even when it's not processing Jobs)
43
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
Delayed JobsPedro's Fork / auto-scale branch
44
Delayed JobsPedro's Fork / auto-scale branch
//lib/delayed/job.rbdef after_createManager.scale_up if self.class.auto_scale && Manager.qty == 0
end
//lib/delayed/worker.rbManager.scale_down if count.zero? && Job.auto_scale && Job.count == 0
44
• Read-only file system
• some gems don’t work
• File uploads?
• Temp Directory
• SSL, memcached, Full-text-search expensive
• Bound-by Amazon’s SLA
Gotchas
45
contact = {
! :blog => http://flux88.com,!! :twitter => @subdigital,!! :email => [email protected]
}
46
47