63
shopify How Shopify Scales Rails John Du

How Shopify Scales Rails

  • Upload
    jduff

  • View
    6.688

  • Download
    4

Embed Size (px)

Citation preview

Page 1: How Shopify Scales Rails

shopify

How Shopify Scales RailsJohn Duff

Page 2: How Shopify Scales Rails

• The Shopify stack

• Knowing what to scale

• How we cache

• Scaling beyond caching

• Splitting things up

Overview

Page 3: How Shopify Scales Rails

What is Shopify?

Page 4: How Shopify Scales Rails
Page 5: How Shopify Scales Rails
Page 6: How Shopify Scales Rails
Page 7: How Shopify Scales Rails

The Stack

Page 8: How Shopify Scales Rails

• Ruby 1.9.3-p385

• Rails 3.2

• Percona MySQL 5.5

• Unicorn 4.5

• Memcached 1.4.14

• Redis 2.6

The Stack

Page 9: How Shopify Scales Rails

The Stack• 53 App Servers

• 1590 Unicorn Workers

• 5 Job Servers

• 370 Job Workers

Nginx

Unicorn

Rails 3.2

Ruby 1.9.3-p385

Page 10: How Shopify Scales Rails

The Stack

Firewall

Load Balancer

App Servers

Redis

Job Servers

Database

Memcached Search

Page 11: How Shopify Scales Rails

• 55,873 Lines of application code

• 15,968 Lines of CoffeeScript application code

• 81,892 Lines of test code

• 211 Controllers

• 468 Models

The Stack

Page 12: How Shopify Scales Rails

Current Scale

Page 13: How Shopify Scales Rails

9.9 M OrdersAn order every 3.2 seconds

Page 14: How Shopify Scales Rails

2,008 Sales per MinuteCyber Monday

Page 15: How Shopify Scales Rails

50,000 RPM45 ms response time

Page 16: How Shopify Scales Rails

13.3 billion requests

Page 17: How Shopify Scales Rails

Looking Back, to Look Ahead

Page 18: How Shopify Scales Rails

• First line of code written in 2004

• Shopify released June, 2006

• Same codebase

• Over 9 years of Rails upgrades, improvements and changes

Looking Back, to Look Ahead

Page 19: How Shopify Scales Rails

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)

Page 20: How Shopify Scales Rails

Looking Back, to Look Ahead

• Ruby 1.8.2

• Rails 0.13.1

• MySQL 4.1

• Lighttpd

• Memcached

Page 21: How Shopify Scales Rails

Know The System

Page 22: How Shopify Scales Rails

One Request, One Process

Page 23: How Shopify Scales Rails

RPM = W * 1/R

Page 24: How Shopify Scales Rails

RPM = 1590 * 60 / 0.072

Page 25: How Shopify Scales Rails

1,325,000 = 1172 * 60 / 0.072

Page 26: How Shopify Scales Rails

↑ Workers

↓ Response Time

Page 27: How Shopify Scales Rails

Know The System

• Avoid network calls during requests

• Speed up unavoidable network calls

• The Storefront and Checkout

• The Chive

Page 28: How Shopify Scales Rails

Chive Flash Sale

Page 29: How Shopify Scales Rails

Measure ALL THE THINGS

Page 30: How Shopify Scales Rails

Measure ALL THE THINGS

• New Relic

• Splunk

• StatsD

• Cacti

• Conan

Page 31: How Shopify Scales Rails

New Relic

Page 32: How Shopify Scales Rails

Splunk

Page 33: How Shopify Scales Rails
Page 34: How Shopify Scales Rails
Page 35: How Shopify Scales Rails

Caching

Page 36: How Shopify Scales Rails

cacheable

Page 37: How Shopify Scales Rails

cacheable

• https://github.com/Shopify/cacheable

• serve gzip’d content

• ETag and 304 Not Modified

• generational caching

• no explicit expiry

Page 38: How Shopify Scales Rails

cacheableclass 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 } endend

Page 39: How Shopify Scales Rails

requests

Caching Dynamic 404s

Page 40: How Shopify Scales Rails

Identity Cache

Page 41: How Shopify Scales Rails

Identity Cache

• https://github.com/Shopify/identity_cache

• cache full model objects in memcached

• can include associated objects in cache

• must opt in to the cache

• explicit, but automatic expiry

Page 42: How Shopify Scales Rails

Identity Cacheclass Product < ActiveRecord::Base include IdentityCache has_many :images cache_index [:shop_id, :id] cache_has_many :images, :embed => trueend

@product = Product.fetch_by_shop_id_and_id(shop_id, id)@images = @product.fetch_images

Page 43: How Shopify Scales Rails

Identity Cache

Page 44: How Shopify Scales Rails

Get Out of My Process

Page 45: How Shopify Scales Rails

Delayed Job

• Jobs stored in the db

• Workers run in their own process

• Workers poll for jobs periodically

• https://github.com/collectiveidea/delayed_job

Page 46: How Shopify Scales Rails

Resque

• Redis backed

• O(1) operation to pop jobs

• Faster (300 jobs/sec vs 120 jobs/sec)

• Extensible

• https://github.com/defunkt/resque

Page 47: How Shopify Scales Rails

Resque

• Sending Email

• Processing Payments

• Geolocation

• Import / Export

• Indexing for Search

• 86 Other things...

Page 48: How Shopify Scales Rails

Background Payment Processing

ms

Page 49: How Shopify Scales Rails

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! endend

Resque.enqueue(AddressGeolocationJob, :id => 1, :model => 'Address')

Page 50: How Shopify Scales Rails

Redis

• Inventory reservation system

• Sessions

• Theme uploads

• Throttling

• Carts

Page 51: How Shopify Scales Rails

All Roads Lead To MySQL

Page 52: How Shopify Scales Rails

MySQL Hardware

• 4 x 8 Core Processor

• SSD

• 256 GB Ram

• Full working set in memory

Page 53: How Shopify Scales Rails

MySQL Query Optimization

• pt-query-digest

• Avoid queries that generate temp tables

• Adding the right indexes

• Forcing / Ignoring Indexes

Page 54: How Shopify Scales Rails

MySQL Tuning

• disable innodb_stats_on_metadata

• increase table_open_cache

• replace glibc memory allocator with tcmalloc

• innodb_autoinc_lock_mode=‘interleaved’

Page 55: How Shopify Scales Rails

after_commitdb transactions best friend

Page 56: How Shopify Scales Rails

after_commit

• After transaction has been committed

• Webhooks

• Cache expiry

• Update associated objects

Page 57: How Shopify Scales Rails

after_commitclass 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 endend

Page 58: How Shopify Scales Rails

Services

Page 59: How Shopify Scales Rails

Services

• Split out standalone services as needed

• Independently scaled

• Segmented metrics

• Overall system is more complex

• Limit to what is necessary

Page 60: How Shopify Scales Rails

Imagery

Page 61: How Shopify Scales Rails

Adapt and Evolve as NeededUsing data and knowledge of the system to drive decisions

Page 62: How Shopify Scales Rails

Summary

• Know your application and infrastructure.

• Keep slow IO or CPU tasks out of the main process.

• Measure your optimizations. You can make it worse.