Improving Your Heroku App Performance with Asset CDN and Unicorn

Preview:

Citation preview

Optimizing rails applications’ performance

w!"# A$$%" CDN &'( U'!)*r'

S!+*' B&,r%%v, @$"&"-$_200$b&,r%%v@,+&!..)*+

Friday, January 11, 13

Question

Does anyone know what

f5dd

is?

Friday, January 11, 13

What *is Preso is NOT

• not a coding demo or tutorial (strangely)

• not a best practices showcase

Friday, January 11, 13

What *is Preso IS

• tips and tricks on tuning rails application

• personal experience

Friday, January 11, 13

DisclaimerThere 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

*is 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# Gemfilegem 'dalli'

# config/application.rbconfig.cache_store = :dalli_store

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

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

Friday, January 11, 13

A2er Deploymentshould see entries like this in your log

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

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

Friday, January 11, 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

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# Gemfilegem "asset_sync" # will push compiled assets into CDN

# Command lineheroku 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.rbconfig.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 effect

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 Effect

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

Friday, January 11, 13

WebPageTest ResultsBefore

Friday, January 11, 13

WebPageTest Results A2er

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

classic setup nginx -> smart balancer -> pool of mongrels

Unicorn setup nginx -> unix domain socket -> unicorn workers (os handles load balancing)

Friday, January 11, 13

Unicorn for Rails App# Gemfilegem 'unicorn'

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

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

# also, add config/unicorn.rb

Friday, January 11, 13

Unicorn for Rails App# config/unicorn.rbworker_processes 3timeout 30preload_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') endend

Friday, January 11, 13

A2er Implementing Unicorn

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

Friday, January 11, 13

Better Concurrency Handlingrequire '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

Creditsdefunkt, 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 & AThis 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