Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How Shopify Scales Rails

How Shopify Scales Rails

This talk goes into detail about how Shopify has been using and scaling rails up to now.

Avatar for John Duff

John Duff

April 29, 2013

More Decks by John Duff

Other Decks in Technology

Transcript

  1. • Ruby 1.9.3-p327 • Rails 3.2 • Percona MySQL 5.5

    • Unicorn 4.5 • Memcached 1.4.14 • Redis 2.6 The Stack
  2. The Stack • 33 App Servers • 1172 Unicorn Workers

    • 5 Job Servers • 370 Job Workers Nginx Unicorn Rails 3.2 Ruby 1.9.3- p327
  3. • 55,873 Lines of application code • 81,892 Lines of

    test code • 211 Controllers • 468 Models The Stack
  4. • First line of code written in 2004 • Shopify

    released July, 2005 • Same codebase • Over 9 years of Rails upgrades, improvements and changes Looking Back, to Look Ahead
  5. Looking Back, to Look Ahead • 6,702 Lines of application

    code (55,873) • 4,386 Lines of test code (81,892) • 38 Controllers (211) • 77 Models (468)
  6. Looking Back, to Look Ahead • Ruby 1.8.2 • Rails

    0.13.1 • MySQL 4.1 • Lighttpd • Memcached
  7. Know The System • Avoid network calls during requests •

    Speed up unavoidable network calls • The Storefront and Checkout • The Chive
  8. cacheable class PostsController < ApplicationController def show response_cache do @post

    = @shop.posts.find(params[:id]) respond_with(@post) end end def cache_key_data { :action => action_name, :format => request.format, :params => params.slice(:id), :shop_version => @shop.version } end end
  9. Identity Cache • cache full model objects in memcached •

    can include associated objects in cache • must opt in to the cache • explicit, but automatic expiry
  10. Identity Cache class Product < ActiveRecord::Base include IdentityCache has_many :images

    cache_index [:shop_id, :id] cache_has_many :images, :embed => true end @product = Product.fetch_by_shop_id_and_id(shop_id, id) @images = @product.fetch_images
  11. Delayed Job • Jobs stored in the db • Workers

    run in their own process • Workers poll for jobs periodically • https://github.com/collectiveidea/delayed_job
  12. Resque • Redis backed • No more db contention •

    Faster (300 jobs/sec vs 120 jobs/sec) • Extensible • https://github.com/defunkt/resque
  13. Resque • Sending Email • Processing Payments • Geolocation •

    Import / Export • Indexing for Search • 86 Other things...
  14. Resque class AddressGeolocationJob max_retries 3 def self.perform(params) object = params[:model].constantize.find(params[:id])

    object.latitude, object.longitude = Geocoder.geocode(object) object.save! end end Resque.enqueue(AddressGeolocationJob, :id => 1, :model => 'Address')
  15. MySQL Hardware • 4 x 8 Core Processor • SSD

    • 256 GB Ram • Full working set in memory
  16. MySQL Query Optimization • pt-query-digest • Avoid queries that generate

    temp tables • Adding the right indexes • Forcing / Ignoring Indexes
  17. MySQL Tuning • disable innodb_stats_on_metadata • increase table_open_cache • replace

    glibc memory allocator with tcmalloc • innodb_autoinc_lock_mode=‘interleaved’
  18. after_commit class OrderObserver < ActiveRecord::Observer observe :order def after_save(order) if

    order.changes.keys.include?(:financial_status) order.flag_for_after_commit(:update_customer) end end def after_commit(order) if order.flagged_for_after_commit?(:update_customer) Resque.enqueue(UpdateCustomerJob, :id => order.id) end end end
  19. Services • Split out standalone services as needed • Independently

    scaled • Segmented metrics • Overall system is more complex • Limit to what is necessary