Improving Your Heroku App Performance with Asset CDN and Unicorn

4,686 views
4,493 views

Published on

Improving Your Heroku App Performance with Asset CDN and Unicorn

  1. 1. Optimizing rails applications’ performance with Asset CDN and Unicorn Simon Bagreev, @status_200 sbagreev@gmail.comFriday, January 11, 13
  2. 2. Question Does anyone know what f5dd is?Friday, January 11, 13
  3. 3. What This Preso is NOT • not a coding demo or tutorial (strangely) • not a best practices showcaseFriday, January 11, 13
  4. 4. What This Preso IS • tips and tricks on tuning rails application • personal experienceFriday, January 11, 13
  5. 5. Disclaimer There are many other ways to improve app’s 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. 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 solutionsFriday, January 11, 13
  7. 7. Sample AppFriday, January 11, 13
  8. 8. NewRelic Monitoring average load time for mobile traffic only, includes iframed adsFriday, January 11, 13
  9. 9. Heroku Dyno OperationFriday, January 11, 13
  10. 10. Step 1 - Rack::Cache Serving assets from rack::cache is faster, and frees up app instance to serve more requestsFriday, January 11, 13
  11. 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 HerokuFriday, January 11, 13
  12. 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] freshFriday, January 11, 13
  13. 13. Using Rack::Cache Effect down from 4.91 / 201 / 2.29 before the change -- not bad for 8 lines of code!Friday, January 11, 13
  14. 14. Step 2 - S3 bucket for assets heroku instance has more time to serve application code because all assets are served from aws s3Friday, January 11, 13
  15. 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 referencedFriday, January 11, 13
  16. 16. Now, on git push heroku assets are automatically synced to s3 anytime ON rake assets:precompileFriday, January 11, 13
  17. 17. S3 Bucket effect down from 4.45 / 179 / 2.43 before the change -- even better!Friday, January 11, 13
  18. 18. Step 3 - AWS CloudFrontFriday, January 11, 13
  19. 19. CloudFront Effect down from 3.85 / 179 / 2.19 before the change -- Awesome!Friday, January 11, 13
  20. 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 fasterFriday, January 11, 13
  21. 21. WebPageTest ResultsBeforeFriday, January 11, 13
  22. 22. WebPageTest Results AfterFriday, January 11, 13
  23. 23. Meet Unicorn • HTTp server for Ruby • Starts one master process • forks worker processes • workers handle requests • master returns • one port, several concurrent requestsFriday, January 11, 13
  24. 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. 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.rbFriday, January 11, 13
  26. 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 endFriday, January 11, 13
  27. 27. After Implementing Unicorn down from 3.64 / 46.7 / 1.17 before the change -- good, but also...Friday, January 11, 13
  28. 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. 29. And ... my app can process six concurrent requests on two heroku dynosFriday, January 11, 13
  30. 30. Riding UnicornFriday, January 11, 13
  31. 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
  32. 32. Friday, January 11, 13
  33. 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. 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

×