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 Shopify
Search
Christian Joudrey
February 28, 2014
Technology
570
3
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Scaling Shopify
Talk given at ConFoo 2014 on February 28th, 2014.
Christian Joudrey
February 28, 2014
More Decks by Christian Joudrey
See All by Christian Joudrey
Writing NES games! with assembly!!
cjoudrey
1
780
Developing at Scale
cjoudrey
3
530
Scaling Rails for Black Friday / Cyber Monday at Shopify
cjoudrey
6
6.1k
Tips and Tricks from Shopify's codebase
cjoudrey
2
590
#pairwithme
cjoudrey
3
270
Two-factor authentication
cjoudrey
4
410
Automate your Infrastructure with Chef
cjoudrey
9
660
Other Decks in Technology
See All in Technology
ガバメントクラウドでのランサムウェア対策
techniczna
2
1k
20260608_Codexの可能性_ノンプログラマー向け_大城追記
doradora09
PRO
0
780
【CEDEC2026】『GRANBLUE FANTASY: Relink - Endless Ragnarok』のバトル制作事例 ~最高のキャラゲーを目指して~
cygames
PRO
0
230
今こそ聞きたいソフトウェア設計 ドメイン駆動設計再入門
masuda220
PRO
17
6.9k
【5分でわかる】セーフィー エンジニア向け会社紹介
safie_recruit
0
53k
AI駆動開発は個人技からチーム戦へ:組織でAIを使いこなすための実践設計
moongift
PRO
0
480
AIは実装を速くする。では、私たちは何を今作るべきか?-立場を越えてリリースに向き合ったチーム開発の実践 / 20260801 Hiromi Nakaya and Naoki Takahashi
shift_evolve
PRO
3
460
AIエージェントを前提としたプラットフォーム エンジニアリング:GKEで作るAgent-Ready Golden Path
legalontechnologies
PRO
2
220
【CEDEC2026】『ウマ娘 プリティーダービー』 英語版のキャラクターの方言や口調をローカライズするための創造的アプローチ
cygames
PRO
1
200
QAエンジニア起点で進める、SmartHRにおける信頼性向上について
kaomi_wombat
1
120
Software Supply Chain Attackからクラウド環境を守るためにできること
lhazy
2
230
メルカリのグローバルアプリで挑んだ AlloyDB 運用と課題解決の実践記
hatappi
0
240
Featured
See All Featured
The Spectacular Lies of Maps
axbom
PRO
1
890
Music & Morning Musume
bryan
47
7.3k
Building Adaptive Systems
keathley
44
3.2k
SEO in 2025: How to Prepare for the Future of Search
ipullrank
3
3.7k
Deep Space Network (abreviated)
tonyrice
0
250
Making the Leap to Tech Lead
cromwellryan
135
10k
Navigating the moral maze — ethical principles for Al-driven product design
skipperchong
2
470
A Tale of Four Properties
chriscoyier
163
24k
How to audit for AI Accessibility on your Front & Back End
davetheseo
0
490
My Coaching Mixtape
mlcsv
0
210
How to Ace a Technical Interview
jacobian
281
24k
Rails Girls Zürich Keynote
gr2m
96
14k
Transcript
SCALING SHOPIFY ...or ensuring happiness for online shoppers
cjoudrey @
None
None
None
None
the stack
nginx unicorn • rails 4 • mysql 5.6 (percona) ruby
2.1 •
95 app servers 3,884 unicorn workers 5 job servers 387
job workers
1 request 1 process =
scale?
over 90,000 shops
None
1.6B$ annual GMV that’s 3,600$ per min
cyber monday black friday
None
61 M$ in GMV in four days
flash sales
None
None
page caching
None
None
shopify/cacheable
generational caching
gzip • etag + 304 not modified
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
None
flash 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
flash sale
background jobs
webhooks emails • fraud detection • payment processing
None
priority queues payment • default • low realtime •
class ProductImportJob include BackgroundQueue::Realtime def perform(params) ... end end BackgroundQueue.push(ProductImportJob,
...)
throttling
the right data store for the job
ephemeral data sessions carts • inventory reservation •
now what?
catching regressions
measure it! if it moves...
statsd
None
Liquid::Template.extend StatsD::Instrument Liquid::Template.statsd_measure :parse, 'Liquid.Template.parse' Liquid::Template.statsd_measure :render, 'Liquid.Template.render'
PaymentProcessingJob.stats_count :perform, 'PaymentProcessingJob.processed'
load testing
None
simulates a flash sale
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/snormore/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)
schema migration with zero downtime
soundcloud/lhm
current schema new schema
insert/delete/update triggers
INSERT INTO ... SELECT ... insert/delete/update triggers
testing for external calls memcached • mysql • redis net/http
•
it’s not about preventing it’s about raising awareness
integration test with assert_externals(...) do .. end Unexpected external call
(mysql): !"" mysql_load("GiftCard") !"" "SELECT `gift_cards`.* FROM `gift_cards` WHERE `gift_cards`.`id` = 1063936318 LIMIT 1" #"" called from: app/services/gift_card_payment_processing.rb: 73:in `block in log_successful'
subscribe('sql.active_record') ActiveSupport::Notifications ["sql.active_record", 2014-02-26 02:38:43 +0000, 2014-02-26 02:38:43 +0000, "a119c5ac2aa6fb4a52fe",
{:sql=>"SELECT `users`.* FROM `users` LIMIT 1", :name=>"User Load", :connection_id=>69893685920420, :binds=>[]}]
monkey-patch other libs to add instrumentation
thanks! :)