Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Scaling Rails for Black Friday / Cyber Monday a...
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Christian Joudrey
February 18, 2015
Technology
6.1k
6
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Scaling Rails for Black Friday / Cyber Monday at Shopify
Talk given at ConFoo 2015 on February 18th, 2015 and RailsConf 2015.
Christian Joudrey
February 18, 2015
More Decks by Christian Joudrey
See All by Christian Joudrey
Writing NES games! with assembly!!
cjoudrey
1
790
Developing at Scale
cjoudrey
3
530
Tips and Tricks from Shopify's codebase
cjoudrey
2
590
Scaling Shopify
cjoudrey
3
570
#pairwithme
cjoudrey
3
270
Two-factor authentication
cjoudrey
4
420
Automate your Infrastructure with Chef
cjoudrey
9
670
Other Decks in Technology
See All in Technology
Bet AI Day 2026丨AIを「使う」から、AIが「働く」へ ― LayerXが進める「組織AI」の社会実装
layerx
PRO
2
700
名刺メーカーDevグループ 紹介資料
sansan33
PRO
0
1.3k
会計事務所と顧問先の契約関係をOIDC・OAuthで表現する
terara
0
560
OAuth SPIFFE Client Authentication(OAuth/OIDC Numa (Immersion) Workshop 2026)
oidfj
PRO
0
410
Eight Engineering Unit 紹介資料
sansan33
PRO
3
8.3k
研究開発部の紹介 / Sansan R&D Profile
sansan33
PRO
4
25k
AI × Datadog MCP でコスト削減まで実現した Metric Streams の導入
three_mouth_ago
0
130
【5分でわかる】セーフィー エンジニア向け会社紹介
safie_recruit
0
54k
いま、生成AIにKaggleをどこまで 任せられるか — ROGIIコンペでの進め方とTips
k951286
0
400
AIエージェントを雇う前に決める5つのこと
knishioka
1
120
AI駆動開発をチームに根付かせる - 「1行も書かない」チームがHarnessを育てた1年 -
kenichirokimura
6
3.6k
KAEN Company Deck
kaen
PRO
0
170
Featured
See All Featured
Public Speaking Without Barfing On Your Shoes - THAT 2023
reverentgeek
1
550
brightonSEO & MeasureFest 2025 - Christian Goodrich - Winning strategies for Black Friday CRO & PPC
cargoodrich
3
800
SEO Brein meetup: CTRL+C is not how to scale international SEO
lindahogenes
1
2.8k
SEO for Brand Visibility & Recognition
aleyda
0
4.7k
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
194
17k
Rails Girls Zürich Keynote
gr2m
96
14k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
17k
KATA
mclloyd
PRO
35
15k
Breaking role norms: Why Content Design is so much more than writing copy - Taylor Woolridge
uxyall
1
390
Exploring anti-patterns in Rails
aemeredith
3
480
Agile Leadership in an Agile Organization
kimpetersen
PRO
0
220
Why Our Code Smells
bkeepers
PRO
340
58k
Transcript
scaling rails for Black Friday Cyber Monday
cjoudrey @
None
None
None
None
None
the stack
nginx unicorn • rails 4 • mysql 5.6 (percona) ruby
2.1 •
95 app servers 1,800 unicorn workers 18 job servers 1,400
job workers
scale?
None
400,000 reqs/min
3.7B$ annual GMV that’s 7,000$ per min
Black Friday Cyber Monday
Black Friday Cyber FUNday
None
~ 600,000 reqs/min
1 request 1 process =
scale++ ↓ resp. time ↑ workers
page caching
None
None
shopify/cacheable
generational caching
gzip • etag + 304 not modified
class PostsController < ApplicationController def index response_cache do @posts =
@shop.posts.paginate(params[:page]) respond_with(@posts) end end def cache_key_data { shop_id: @shop.id, path: request.path, format: request.format, params: params.slice(:page), shop_version: @shop.version } end end
md5( { shop_id: 1, path: '/posts', format: 'text/html', params: {
page: 2 }, shop_version: 123 }.to_s ) GET /posts?page=2
None
sale
query caching
shopify/identity_cache
full model caching
opt-in by design
after_commit expiry
class Product < ActiveRecord::Base include IdentityCache has_many :images cache_has_many :images,
:embed => true end product = Product.fetch(id) images = product.fetch_images
class Product < ActiveRecord::Base include IdentityCache cache_index :shop_id, :handle, :unique
=> true end Product.fetch_by_shop_id_and_handle(shop_id, handle)
None
sale
background jobs
webhooks emails • fraud detection • payment processing
None
priority queues payment • default • low realtime •
throttling
now what?
measure it! if it moves...
statsd
shopify/statsd-instrument
Liquid::Template.extend StatsD::Instrument Liquid::Template.statsd_measure :render, 'Liquid.Template.render'
PaymentProcessingJob.statsd_count :perform, 'PaymentProcessingJob.processed'
None
None
load testing
genghis khan
simulate Black Friday Cyber Monday before it happens
several times per week
slow queries
# User@Host: shopify[shopify] @ [127.0.0.1] # Thread_id: 264419969 Schema: shopify
Last_errno: 0 Killed: 0 # Query_time: 0.150491 Lock_time: 0.000057 Rows_sent: 1 Rows_examined: 147841 Rows_affected: 0 Rows_read: 147841 # Bytes_sent: 1214 Tmp_tables: 0 Tmp_disk_tables: 0 Tmp_table_sizes: 0 # InnoDB_trx_id: FF7021AAA # QC_Hit: No Full_scan: No Full_join: No Tmp_table: No Tmp_table_on_disk: No # Filesort: Yes Filesort_on_disk: No Merge_passes: 0 # InnoDB_IO_r_ops: 0 InnoDB_IO_r_bytes: 0 InnoDB_IO_r_wait: 0.000000 # InnoDB_rec_lock_wait: 0.000000 InnoDB_queue_wait: 0.000000 # InnoDB_pages_distinct: 475 SET timestamp=1393385020; SELECT `discounts`.* FROM `discounts` WHERE `discounts`.`shop_id` = 1745470 AND `discounts`.`status` = 'enabled' ORDER BY ISNULL(ends_at) DESC, ends_at DESC LIMIT 1
determining root cause
https://github.com/newobj/nginx-x-rid-header nginx request_id header proxy_set_header X-Request-ID "$request_id"; log_format main '...
$request_id' step 1
https://gist.github.com/mnutt/566725 Complete 200 OK in 100ms (Views: 60ms | ActiveRecord:
40ms | request_id=bc12813bce...) log_process_action ActionController::Instrumentation step 2
https://github.com/basecamp/marginalia User Load (0.3ms) SELECT `users`.* FROM `users` WHERE `users`.`id`
= 1 LIMIT 1 /*application:Shopify, controller:users,action:show, request_id:bc12813bce...*/ basecamp/marginalia step 3
# User@Host: shopify[shopify] @ [127.0.0.1] # Thread_id: 264419969 Schema: shopify
Last_errno: 0 Killed: 0 # Query_time: 0.150491 Lock_time: 0.000057 Rows_sent: 1 Rows_examined: 147841 Rows_affected: 0 Rows_read: 147841 # Bytes_sent: 1214 Tmp_tables: 0 Tmp_disk_tables: 0 Tmp_table_sizes: 0 # InnoDB_trx_id: FF7021AAA # QC_Hit: No Full_scan: No Full_join: No Tmp_table: No Tmp_table_on_disk: No # Filesort: Yes Filesort_on_disk: No Merge_passes: 0 # InnoDB_IO_r_ops: 0 InnoDB_IO_r_bytes: 0 InnoDB_IO_r_wait: 0.000000 # InnoDB_rec_lock_wait: 0.000000 InnoDB_queue_wait: 0.000000 # InnoDB_pages_distinct: 475 SET timestamp=1393385020; SELECT `discounts`.* FROM `discounts` WHERE `discounts`.`shop_id` = 1745470 AND `discounts`.`status` = 'enabled' ORDER BY ISNULL(ends_at) DESC, ends_at DESC LIMIT 1 /*application:Shopify,controller:orders,action:pay, request_id:bc12813bce...*/ profit!
access.log rails.log slow_query.log profit! (2)
bonus! background jobs
schema migration with zero downtime
soundcloud/lhm
class NewIndexOnOrders < ActiveRecord::Migration def self.up Lhm.change_table :orders do |m|
m.add_index [:shop_id, :customer_id] end end def self.down # end end
orders 20140520_orders
insert/delete/update triggers
INSERT INTO ... SELECT ... insert/delete/update triggers
async caveat
resiliency
A resilient system is one that functions with one or
more components being unavailable or unacceptably slow. -
don’t let a take you down minor dependencies
shopify/toxiproxy ☢ redis memcached sessions your app toxiproxy
def load_customer if customer_id = session[:customer_id] @customer = Customer.find_by_id(customer_id) end
end
def load_customer if customer_id = session[:customer_id] @customer = Customer.find_by_id(customer_id) end
rescue Sessions::DataStoreUnavailable @customer = nil end
def test_storefront_resilient_to_sessions_down Toxiproxy[:sessions_data_store].down do get '/' assert_response :success end end
rinse & repeat http://www.shopify.com/technology/16906928-building-and-testing-resilient-ruby-on-rails-applications
what about resources? slow
shard 2 shard 3 shard 1 shop 4, 5, 6
shop 7, 8, 9 shop 1, 2, 3
rails request shard 2 shard 3 shard 1 shop 4,
5, 6 shop 7, 8, 9 shop 1, 2, 3
rails request shard 2 shard 3 shard 1 shop 4,
5, 6 shop 7, 8, 9 shop 1, 2, 3
how can we fail fast?
shopify/semian smart circuit-breaker
shopify/semian Semian.register(:mysql_shard_1, tickets: 5, timeout: 0.5, error_threshold: 100, error_timeout: 10,
success_threshold: 2)
shopify/semian Semian[:mysql_shard_1].acquire do # Query the resource end
rails request shard 2 shard 3 shard 1 shop 4,
5, 6 shop 7, 8, 9 shop 1, 2, 3
what else can go wrong?
Shopify Shipping rate providers Payment gateways Fulfillment services (FedEX, UPS,
USPS, etc..) (Stripe, PayPal, etc..) (Shipwire, etc…) Internal services (MySQL, Memcached, etc..)
manual circuit breakers around external dependencies
thanks! :)