Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Scaling Shopify
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Christian Joudrey
February 28, 2014
Technology
580
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
800
Developing at Scale
cjoudrey
3
540
Scaling Rails for Black Friday / Cyber Monday at Shopify
cjoudrey
6
6.2k
Tips and Tricks from Shopify's codebase
cjoudrey
2
600
#pairwithme
cjoudrey
3
280
Two-factor authentication
cjoudrey
4
420
Automate your Infrastructure with Chef
cjoudrey
9
680
Other Decks in Technology
See All in Technology
GitHub、使う?使わない?
happysamurai294
0
140
Jev の中身は50行で再現できる。でも本物との差はどこにあるのか
hisuzuya
0
280
デザインレビューは自動化できるのか?─ Flutter × Figma で UI 品質保証を考える
techiro
2
720
複雑な業務システムを高速でつくるため、チーム全員をつくれる人へ
ayakurabuchi
0
120
"計画的無茶振り"を通してAIネイティブ世代の新人を育てる
subroh0508
1
140
[General] 개발자로 해외 기업에서 일하기
skydoves
1
250
AI時代の開発はウォーターフォールに回帰する? / development-with-ai-back-to-wf
okamototakuyasr2
0
170
Lint の層を組み直した話
bengo4com
2
290
eBPF 簡介 2026 版
shunghsiyu
0
130
WireGuard for Apache NuttX - Bringing Secure Remote Access to embedded Devices -
aki310ooooo
0
120
リアクションエンジニア ― 小さなリアクションから、話しかけやすい組織をつくる ―
ryuguyana
1
100
Flutter Webを「SDK」として配る — 50以上のゲームを支える埋め込みアーキテクチャ
masayukisuda
0
170
Featured
See All Featured
Avoiding the “Bad Training, Faster” Trap in the Age of AI
tmiket
0
290
A Soul's Torment
seathinner
8
3.9k
職位にかかわらず全員がリーダーシップを発揮するチーム作り / Building a team where everyone can demonstrate leadership regardless of position
madoxten
70
68k
HDC tutorial
michielstock
2
990
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
31
10k
Agile Leadership in an Agile Organization
kimpetersen
PRO
0
310
How to Talk to Developers About Accessibility
jct
2
600
Why Your Marketing Sucks and What You Can Do About It - Sophie Logan
marketingsoph
0
450
Building Better People: How to give real-time feedback that sticks.
wjessup
371
20k
[RailsConf 2023] Rails as a piece of cake
palkan
59
7.1k
Accessibility Awareness
sabderemane
1
280
Imperfection Machines: The Place of Print at Facebook
scottboms
270
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! :)