A Scalable Rails app
  Deployed in 60
      seconds




                       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
5
6
7
7
7
7
7
7
8
8
9
10
11
x 1,000?
           11
12
x 10,000?
            12
writes


reads




         13
writes


reads




         x 1,000,000?
                        13
14
On-Demand
        15
# Users by time of day
400



300



200



100



 0
 12 AM 3AM 6AM 9AM 12PM 3PM 6PM 9PM 12AM



                                           16
# Users by time of day
400



300



200



100



 0
 12 AM 3AM 6AM 9AM 12PM 3PM 6PM 9PM 12AM



                                           16
# 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
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
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
SCALE




        18
19
Awesome Shredder Art by Dan Barret

                                20
gem install heroku
             Awesome Shredder Art by Dan Barret

                                             20
• Platform as a service for Rack-based apps
• Scale easily
• Great workflow
• Free to start / Friendly Pricing
• Complete command-line API
                                              21
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
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
Using git for deployment is f***ing
              GENIUS




                                      23
Technology




             • Amazon Web Services
             • Caching w/ Varnish
             • Debian Linux
             • Erlang Routing Mesh
             • Nginx
             • PostgreSQL
                                     24
25
How Heroku Scales




                    26
How Heroku Scales
  or WTF is a Dyno?




                      26
How Heroku Scales
  or WTF is a Dyno?




                      26
How Heroku Scales
  or WTF is a Dyno?




                      26
How Heroku Scales




                    27
How Heroku Scales




                    27
How Heroku Scales




                    27
When to add dynos




• Increasing response times
• “Backlog Too Deep” Error
                                           28
Always Measure




• New Relic RPM
• Apache Bench



                                   29
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
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_ton
      flash[:notice] => "Ok we sent them"

end




                                                           41
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
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 Jobs
Pedro's Fork / auto-scale branch




                               44
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
• 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
contact = {

! :blog => http://flux88.com,
!
! :twitter => @subdigital,
!
! :email => ben@scheirman.com

}

                                46
47

A Scalable Rails App Deployed in 60 Seconds

  • 1.
    A Scalable Railsapp Deployed in 60 seconds 1
  • 2.
    Ben Scheirman • Directorof Development at ChaiONE • Microsoft MVP, ASP Insider • Certified ScrumMaster • Former .NET Ninja, now Rails/iPhone aficionado 2
  • 3.
    Ben Scheirman • ...notaffiliated with Heroku 3
  • 4.
    So you havea fantastic app? 4
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
    writes reads x 1,000,000? 13
  • 23.
  • 24.
  • 25.
    # Users bytime of day 400 300 200 100 0 12 AM 3AM 6AM 9AM 12PM 3PM 6PM 9PM 12AM 16
  • 26.
    # Users bytime of day 400 300 200 100 0 12 AM 3AM 6AM 9AM 12PM 3PM 6PM 9PM 12AM 16
  • 27.
    # Users bytime 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 ForPR 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 ForPR Campaign 900 675 450 225 0 Mar 1 Mar 15 Apr 1 Apr 15 May 1 May 15 Jun 1 Jun 2 17
  • 30.
  • 31.
  • 32.
    Awesome Shredder Artby Dan Barret 20
  • 33.
    gem install heroku Awesome Shredder Art by Dan Barret 20
  • 34.
    • Platform asa service for Rack-based apps • Scale easily • Great workflow • Free to start / Friendly Pricing • Complete command-line API 21
  • 35.
    Deploy a railsapp 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 railsapp 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
  • 37.
    Using git fordeployment is f***ing GENIUS 23
  • 38.
    Technology • Amazon Web Services • Caching w/ Varnish • Debian Linux • Erlang Routing Mesh • Nginx • PostgreSQL 24
  • 39.
  • 40.
  • 41.
    How Heroku Scales or WTF is a Dyno? 26
  • 42.
    How Heroku Scales or WTF is a Dyno? 26
  • 43.
    How Heroku Scales or WTF is a Dyno? 26
  • 44.
  • 45.
  • 46.
  • 47.
    When to adddynos • Increasing response times • “Backlog Too Deep” Error 28
  • 48.
    Always Measure • NewRelic RPM • Apache Bench 29
  • 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
  • 50.
  • 51.
    Coping with theFree 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 theFree 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 theFree 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 theFree 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 theFree 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 theFree 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 theFree 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 theFree 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 :deploydo 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
  • 60.
  • 61.
    Delayed Jobs def send_bulk_emails BulkEmailSender.send_a_ton flash[:notice] => "Ok we sent them" end 41
  • 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
  • 63.
  • 64.
    Delayed Jobs > geminstall delayed_job > rails g delayed_job > rake db:migrate 42
  • 65.
    Delayed Jobs > geminstall 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
  • 66.
  • 67.
    Delayed Jobs $0.05 perhour / worker (even when it's not processing Jobs) 43
  • 68.
    Delayed Jobs $0.05 perhour / worker (even when it's not processing Jobs) http://github.com/pedro/delayed_job/commit/ 09d7657e1fc7d25072e6c5e73ede20d6e1185eac http://bit.ly/9Qokff 43
  • 69.
    Delayed Jobs Pedro's Fork/ auto-scale branch 44
  • 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 filesystem • 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
  • 73.