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
3
520
Scaling Shopify
Talk given at ConFoo 2014 on February 28th, 2014.
Christian Joudrey
February 28, 2014
Tweet
Share
More Decks by Christian Joudrey
See All by Christian Joudrey
Writing NES games! with assembly!!
cjoudrey
1
700
Developing at Scale
cjoudrey
3
460
Scaling Rails for Black Friday / Cyber Monday at Shopify
cjoudrey
6
5.7k
Tips and Tricks from Shopify's codebase
cjoudrey
2
560
#pairwithme
cjoudrey
3
240
Two-factor authentication
cjoudrey
4
380
Automate your Infrastructure with Chef
cjoudrey
9
590
Other Decks in Technology
See All in Technology
Data-centric AI入門第6章:Data-centric AIの実践例
x_ttyszk
1
410
AndroidXR 開発ツールごとの できることできないこと
donabe3
0
130
君も受託系GISエンジニアにならないか
sudataka
2
430
リーダブルテストコード 〜メンテナンスしやすい テストコードを作成する方法を考える〜 #DevSumi #DevSumiB / Readable test code
nihonbuson
11
7.2k
AndroidデバイスにFTPサーバを建立する
e10dokup
0
250
開発スピードは上がっている…品質はどうする? スピードと品質を両立させるためのプロダクト開発の進め方とは #DevSumi #DevSumiB / Agile And Quality
nihonbuson
2
3k
アジャイル開発とスクラム
araihara
0
170
一度 Expo の採用を断念したけど、 再度 Expo の導入を検討している話
ichiki1023
1
170
全文検索+セマンティックランカー+LLMの自然文検索サ−ビスで得られた知見
segavvy
2
110
OpenID BizDay#17 KYC WG活動報告(法人) / 20250219-BizDay17-KYC-legalidentity
oidfj
0
250
利用終了したドメイン名の最強終活〜観測環境を育てて、分析・供養している件〜 / The Ultimate End-of-Life Preparation for Discontinued Domain Names
nttcom
2
200
N=1から解き明かすAWS ソリューションアーキテクトの魅力
kiiwami
0
130
Featured
See All Featured
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
507
140k
Code Review Best Practice
trishagee
67
18k
Building an army of robots
kneath
303
45k
Six Lessons from altMBA
skipperchong
27
3.6k
Into the Great Unknown - MozCon
thekraken
35
1.6k
Mobile First: as difficult as doing things right
swwweet
223
9.3k
GraphQLとの向き合い方2022年版
quramy
44
13k
Designing for humans not robots
tammielis
250
25k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.1k
Making Projects Easy
brettharned
116
6k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
100
18k
Rails Girls Zürich Keynote
gr2m
94
13k
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! :)