A Scalable Rails App Deployed in 60 Seconds

Post on 13-Jan-2015

4.087 views 0 download

Tags:

description

I gave this talk at Lone Star Ruby Conference in Austin on 8/28/2010.

Transcript of A Scalable Rails App Deployed in 60 Seconds

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/ | git@heroku.com: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/ | git@heroku.com: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 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 => ben@scheirman.com

}

46

47