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
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
770
Developing at Scale
cjoudrey
3
520
Tips and Tricks from Shopify's codebase
cjoudrey
2
590
Scaling Shopify
cjoudrey
3
570
#pairwithme
cjoudrey
3
270
Two-factor authentication
cjoudrey
4
410
Automate your Infrastructure with Chef
cjoudrey
9
650
Other Decks in Technology
See All in Technology
なぜ Platform Engineering の土台に Kubernetes を選ぶのか
r4ynode
1
570
日本 Fintech 未来予測レポート 2027〜2028年(手動編集版)
8maki
0
1.7k
非定型業務をAI slackbotで自動化する ~ 社内要望を自動壁打ちするbotを作った ~/automating-ad-hoc-work-with-ai-slackbot
shibayu36
0
600
チームで進めるAI駆動アジャイル×ウォーターフォール
kumaiu
0
150
ACE-Step-1.5で見る 音楽生成AIのしくみと“破綻だけ直す”Retake機能の開発【zennfes spring 2026 登壇資料】
personabb
1
110
機械学習を「社会実装」するということ 2026年夏版 / Social Implementation of Machine Learning June 2026 Version
moepy_stats
4
1.4k
LLMと共に進化するプロセスを目指して
ymatsuwitter
12
4k
非エンジニアがClaudeと挑んだ「1ヶ月間プロダクト30本ノック」
askokc
0
290
AIソロプレナー時代に2ヶ月で20人増員した事業創造会社の開発組織の話
miyatakoji
0
580
脆弱性対応、どこで線を引くか
rymiyamoto
0
360
Android の公式 Skill / Android skills
yanzm
0
120
2026TECHFRESH畢業分享會 - AI 時代的人生存檔點
line_developers_tw
PRO
0
770
Featured
See All Featured
Accessibility Awareness
sabderemane
1
140
Fashionably flexible responsive web design (full day workshop)
malarkey
408
66k
The Illustrated Children's Guide to Kubernetes
chrisshort
51
52k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
162
16k
Odyssey Design
rkendrick25
PRO
2
690
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
560
Digital Projects Gone Horribly Wrong (And the UX Pros Who Still Save the Day) - Dean Schuster
uxyall
0
1.7k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
The Hidden Cost of Media on the Web [PixelPalooza 2025]
tammyeverts
2
330
Amusing Abliteration
ianozsvald
1
200
HDC tutorial
michielstock
2
700
ラッコキーワード サービス紹介資料
rakko
1
3.6M
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! :)