ݺߣ

ݺߣShare a Scribd company logo
Optimizing rails applications
                                 performance
                               with Asset CDN and Unicorn

                                            Simon Bagreev, @status_200
                                                    sbagreev@gmail.com




Friday, January 11, 13
Question
                         Does anyone know what

                                 f5dd
                                  is?




Friday, January 11, 13
What This Preso is NOT

                         ? not a coding demo or tutorial (strangely)
                         ? not a best practices showcase




Friday, January 11, 13
What This Preso IS

                         ? tips and tricks on tuning rails application
                         ? personal experience




Friday, January 11, 13
Disclaimer
                         There are many other ways to improve apps performance:

                           ?database performance (indexes, N+1, slow queries)
                           ?caching
                           ?background processing
                           ?changing interpreter, GC
                           ?conditional asset loading, etc
                           ?removing cruft!
                         Try them first!



Friday, January 11, 13
This Presentation - Two Parts

                         ? CDN Asset Host using aws*
                         ? unicorn web server*
                 * Both approaches were tested on heroku, but must work with other hosting solutions




Friday, January 11, 13
Sample App




Friday, January 11, 13
NewRelic Monitoring




                         average load time for mobile traffic only, includes iframed ads




Friday, January 11, 13
Heroku Dyno Operation




Friday, January 11, 13
Step 1 - Rack::Cache




                  Serving assets from rack::cache is faster, and frees up app instance to serve more requests




Friday, January 11, 13
Static Asset Caching
                         # Gemfile
                         gem 'dalli'

                         # config/application.rb
                         config.cache_store = :dalli_store

                         # config/environments/production.rb
                         config.action_dispatch.rack_cache = {
                           :metastore    => Dalli::Client.new,
                           :entitystore => 'file:tmp/cache/rack/body',
                           :allow_reload => false
                         }
                         config.serve_static_assets = true
                         config.assets.digest = true

                         config.action_controller.perform_caching = true
                         # provision Memcache addon on Heroku




Friday, January 11, 13
After Deployment
                                   should see entries like this in your log

          cache: [GET /assets/application-c0747cab950350f59304a3815f980622.css] miss, store
          cache: [GET /assets/application-032691d4988a7003c42a10995819a0ce.js] miss, store
          cache: [GET /assets/s_code.js] miss, store

          cache: [GET /assets/application-c0747cab950350f59304a3815f980622.css] fresh
          cache: [GET /assets/application-032691d4988a7003c42a10995819a0ce.js] fresh
          cache: [GET /assets/s_code.js] fresh




Friday, January 11, 13
Using Rack::Cache E?ect




                         down from 4.91 / 201 / 2.29 before the change -- not bad for 8 lines of code!




Friday, January 11, 13
Step 2 - S3 bucket for assets




                  heroku instance has more time to serve application code because all assets are served from aws s3




Friday, January 11, 13
S3 Bucket for Assets
             # Gemfile
             gem "asset_sync" # will push compiled assets into CDN

             # Command line
             heroku config:add FOG_PROVIDER=AWS 
                    AWS_ACCESS_KEY_ID=xxx 
                    AWS_SECRET_ACCESS_KEY=yyy

             heroku config:add FOG_DIRECTORY=yourappname-assets

             # config/environments/production.rb
             config.action_controller.asset_host =
             "//#{ENV['FOG_DIRECTORY']}.s3.amazonaws.com"

             # make sure to use AssetTagHelper methods (like image_tag)
             # to ensure assets are properly referenced




Friday, January 11, 13
Now, on git push heroku




                         assets are automatically synced to s3 anytime ON rake assets:precompile




Friday, January 11, 13
S3 Bucket e?ect




                         down from 4.45 / 179 / 2.43 before the change -- even better!




Friday, January 11, 13
Step 3 - AWS CloudFront




Friday, January 11, 13
CloudFront E?ect




                         down from 3.85 / 179 / 2.19 before the change -- Awesome!




Friday, January 11, 13
Loading Single File
                 $ time curl http://careersingear.mobi/assets/application-
                 bdb77a926724ccc3c20b923ab168d89d.js

                 real    0m0.896s
                 user    0m0.008s
                 sys     0m0.016s

                 ----------------

                 $ time curl http://d3kd72psxbec02.cloudfront.net/assets/
                 application-bdb77a926724ccc3c20b923ab168d89d.js

                 real    0m0.293s
                 user    0m0.006s
                 sys     0m0.010s

                         getting a single application.js file from cloud front is 3x faster




Friday, January 11, 13
WebPageTest ResultsBefore




Friday, January 11, 13
WebPageTest Results After




Friday, January 11, 13
Meet Unicorn
                         ? HTTp server for Ruby
                         ? Starts one master process
                         ? forks worker processes
                         ? workers handle requests
                         ? master returns
                         ? one port, several
                            concurrent requests



Friday, January 11, 13
Server Setup




                                                                                    Unicorn setup
                                        classic setup
                                                                       nginx -> unix domain socket -> unicorn
                         nginx -> smart balancer -> pool of mongrels
                                                                       workers (os handles load balancing)




Friday, January 11, 13
Unicorn for Rails App
                         # Gemfile
                         gem 'unicorn'

                         # Procfile
                         web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb

                         # config/application.rb
                         config.logger = Logger.new(STDOUT)

                         # also, add config/unicorn.rb




Friday, January 11, 13
Unicorn for Rails App
              # config/unicorn.rb
              worker_processes 3
              timeout 30
              preload_app true

              before_fork do |server, worker|
                if defined?(ActiveRecord::Base)
                  ActiveRecord::Base.connection.disconnect!
                  Rails.logger.info('Disconnected from ActiveRecord')
                end

                  if defined?(Resque)
                    Resque.redis.quit
                    Rails.logger.info('Disconnected from Redis')
                  end

              end

              after_fork do |server, worker|
                if defined?(ActiveRecord::Base)
                  ActiveRecord::Base.establish_connection
                  Rails.logger.info('Connected to ActiveRecord')
                end

                if defined?(Resque)
                  Resque.redis = ENV["REDISTOGO_URL"]
                  Rails.logger.info('Connected to Redis')
                end
              end


Friday, January 11, 13
After Implementing Unicorn




                         down from 3.64 / 46.7 / 1.17 before the change -- good, but also...




Friday, January 11, 13
Better Concurrency Handling
              require 'typhoeus'
              require "benchmark"

              URL = "http://careersingear.mobi"
              HYDRA = Typhoeus::Hydra.new(max_concurrency: 20)

              1000.times do
                request = Typhoeus::Request.new(URL, method: :get, timeout: 10000)
                request.on_complete do |response|
                  puts response.code
                end
                HYDRA.queue(request)
              end

              Benchmark.bm(7) do |x|
                x.report("first:")   { HYDRA.run }
              end

              # using thin
              # user          system      total       real
              # 1.030000   0.380000    1.410000 ( 16.713791)

              # using unicorn
              # user        system      total           real
              # 1.050000   0.390000    1.440000 (    7.843766)




Friday, January 11, 13
And ...




                         my app can process six concurrent requests on two heroku dynos




Friday, January 11, 13
Riding Unicorn




Friday, January 11, 13
To Conclude

                         ? implemented asset cdn
                         ? configured unicorn
                         ? brought down average end user load time from almost
                            5 sec to 3.5 sec

                         ? app can serve more requests faster and for less $$$


Friday, January 11, 13
Friday, January 11, 13
Credits
         defunkt, unicorn! https://github.com/blog/517-unicorn
         heroku dev center, using rack::cache with memcached in rails 3.1+ https://devcenter.heroku.com/articles/
         rack-cache-memcached-rails31
         Rice, david, using a cdn asset host with rails 3.1 https://devcenter.heroku.com/articles/cdn-asset-
         host-rails31
         Sikkes, Michael, Complete Guide to serving your Rails assets over S3 with asset_sync http://blog.firmhouse.com/
         complete-guide-to-serving-your-rails-assets-over-s3-with-asset_sync
         van roijen, michael, more concurrency on a single heroku dyno with the new celadon cedar stack http://
         michaelvanrooijen.com/articles/2011/06/01-more-concurrency-on-a-single-heroku-dyno-
         with-the-new-celadon-cedar-stack/




Friday, January 11, 13
Q&A
         This presentation can be found on github github.com/semmin/asset-cdn-and-unicorn-preso
         twitter: @status_200
         Email: sbagreev@gmail.com



                                              Questions?




Friday, January 11, 13

More Related Content

Improving Your Heroku App Performance with Asset CDN and Unicorn

  • 1. Optimizing rails applications performance with Asset CDN and Unicorn Simon Bagreev, @status_200 sbagreev@gmail.com Friday, January 11, 13
  • 2. Question Does anyone know what f5dd is? Friday, January 11, 13
  • 3. What This Preso is NOT ? not a coding demo or tutorial (strangely) ? not a best practices showcase Friday, January 11, 13
  • 4. What This Preso IS ? tips and tricks on tuning rails application ? personal experience Friday, January 11, 13
  • 5. Disclaimer There are many other ways to improve apps performance: ?database performance (indexes, N+1, slow queries) ?caching ?background processing ?changing interpreter, GC ?conditional asset loading, etc ?removing cruft! Try them first! Friday, January 11, 13
  • 6. This Presentation - Two Parts ? CDN Asset Host using aws* ? unicorn web server* * Both approaches were tested on heroku, but must work with other hosting solutions Friday, January 11, 13
  • 8. NewRelic Monitoring average load time for mobile traffic only, includes iframed ads Friday, January 11, 13
  • 10. Step 1 - Rack::Cache Serving assets from rack::cache is faster, and frees up app instance to serve more requests Friday, January 11, 13
  • 11. Static Asset Caching # Gemfile gem 'dalli' # config/application.rb config.cache_store = :dalli_store # config/environments/production.rb config.action_dispatch.rack_cache = { :metastore => Dalli::Client.new, :entitystore => 'file:tmp/cache/rack/body', :allow_reload => false } config.serve_static_assets = true config.assets.digest = true config.action_controller.perform_caching = true # provision Memcache addon on Heroku Friday, January 11, 13
  • 12. After Deployment should see entries like this in your log cache: [GET /assets/application-c0747cab950350f59304a3815f980622.css] miss, store cache: [GET /assets/application-032691d4988a7003c42a10995819a0ce.js] miss, store cache: [GET /assets/s_code.js] miss, store cache: [GET /assets/application-c0747cab950350f59304a3815f980622.css] fresh cache: [GET /assets/application-032691d4988a7003c42a10995819a0ce.js] fresh cache: [GET /assets/s_code.js] fresh Friday, January 11, 13
  • 13. Using Rack::Cache E?ect down from 4.91 / 201 / 2.29 before the change -- not bad for 8 lines of code! Friday, January 11, 13
  • 14. Step 2 - S3 bucket for assets heroku instance has more time to serve application code because all assets are served from aws s3 Friday, January 11, 13
  • 15. S3 Bucket for Assets # Gemfile gem "asset_sync" # will push compiled assets into CDN # Command line heroku config:add FOG_PROVIDER=AWS AWS_ACCESS_KEY_ID=xxx AWS_SECRET_ACCESS_KEY=yyy heroku config:add FOG_DIRECTORY=yourappname-assets # config/environments/production.rb config.action_controller.asset_host = "//#{ENV['FOG_DIRECTORY']}.s3.amazonaws.com" # make sure to use AssetTagHelper methods (like image_tag) # to ensure assets are properly referenced Friday, January 11, 13
  • 16. Now, on git push heroku assets are automatically synced to s3 anytime ON rake assets:precompile Friday, January 11, 13
  • 17. S3 Bucket e?ect down from 4.45 / 179 / 2.43 before the change -- even better! Friday, January 11, 13
  • 18. Step 3 - AWS CloudFront Friday, January 11, 13
  • 19. CloudFront E?ect down from 3.85 / 179 / 2.19 before the change -- Awesome! Friday, January 11, 13
  • 20. Loading Single File $ time curl http://careersingear.mobi/assets/application- bdb77a926724ccc3c20b923ab168d89d.js real 0m0.896s user 0m0.008s sys 0m0.016s ---------------- $ time curl http://d3kd72psxbec02.cloudfront.net/assets/ application-bdb77a926724ccc3c20b923ab168d89d.js real 0m0.293s user 0m0.006s sys 0m0.010s getting a single application.js file from cloud front is 3x faster Friday, January 11, 13
  • 23. Meet Unicorn ? HTTp server for Ruby ? Starts one master process ? forks worker processes ? workers handle requests ? master returns ? one port, several concurrent requests Friday, January 11, 13
  • 24. Server Setup Unicorn setup classic setup nginx -> unix domain socket -> unicorn nginx -> smart balancer -> pool of mongrels workers (os handles load balancing) Friday, January 11, 13
  • 25. Unicorn for Rails App # Gemfile gem 'unicorn' # Procfile web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb # config/application.rb config.logger = Logger.new(STDOUT) # also, add config/unicorn.rb Friday, January 11, 13
  • 26. Unicorn for Rails App # config/unicorn.rb worker_processes 3 timeout 30 preload_app true before_fork do |server, worker| if defined?(ActiveRecord::Base) ActiveRecord::Base.connection.disconnect! Rails.logger.info('Disconnected from ActiveRecord') end if defined?(Resque) Resque.redis.quit Rails.logger.info('Disconnected from Redis') end end after_fork do |server, worker| if defined?(ActiveRecord::Base) ActiveRecord::Base.establish_connection Rails.logger.info('Connected to ActiveRecord') end if defined?(Resque) Resque.redis = ENV["REDISTOGO_URL"] Rails.logger.info('Connected to Redis') end end Friday, January 11, 13
  • 27. After Implementing Unicorn down from 3.64 / 46.7 / 1.17 before the change -- good, but also... Friday, January 11, 13
  • 28. Better Concurrency Handling require 'typhoeus' require "benchmark" URL = "http://careersingear.mobi" HYDRA = Typhoeus::Hydra.new(max_concurrency: 20) 1000.times do request = Typhoeus::Request.new(URL, method: :get, timeout: 10000) request.on_complete do |response| puts response.code end HYDRA.queue(request) end Benchmark.bm(7) do |x| x.report("first:") { HYDRA.run } end # using thin # user system total real # 1.030000 0.380000 1.410000 ( 16.713791) # using unicorn # user system total real # 1.050000 0.390000 1.440000 ( 7.843766) Friday, January 11, 13
  • 29. And ... my app can process six concurrent requests on two heroku dynos Friday, January 11, 13
  • 31. To Conclude ? implemented asset cdn ? configured unicorn ? brought down average end user load time from almost 5 sec to 3.5 sec ? app can serve more requests faster and for less $$$ Friday, January 11, 13
  • 33. Credits defunkt, unicorn! https://github.com/blog/517-unicorn heroku dev center, using rack::cache with memcached in rails 3.1+ https://devcenter.heroku.com/articles/ rack-cache-memcached-rails31 Rice, david, using a cdn asset host with rails 3.1 https://devcenter.heroku.com/articles/cdn-asset- host-rails31 Sikkes, Michael, Complete Guide to serving your Rails assets over S3 with asset_sync http://blog.firmhouse.com/ complete-guide-to-serving-your-rails-assets-over-s3-with-asset_sync van roijen, michael, more concurrency on a single heroku dyno with the new celadon cedar stack http:// michaelvanrooijen.com/articles/2011/06/01-more-concurrency-on-a-single-heroku-dyno- with-the-new-celadon-cedar-stack/ Friday, January 11, 13
  • 34. Q&A This presentation can be found on github github.com/semmin/asset-cdn-and-unicorn-preso twitter: @status_200 Email: sbagreev@gmail.com Questions? Friday, January 11, 13