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
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Christian Joudrey
February 28, 2014
Technology
3
560
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
760
Developing at Scale
cjoudrey
3
510
Scaling Rails for Black Friday / Cyber Monday at Shopify
cjoudrey
6
6k
Tips and Tricks from Shopify's codebase
cjoudrey
2
580
#pairwithme
cjoudrey
3
260
Two-factor authentication
cjoudrey
4
400
Automate your Infrastructure with Chef
cjoudrey
9
640
Other Decks in Technology
See All in Technology
「通るまでRe-run」から卒業!落ちないテストを書く勘所
asumikam
2
470
生成AIで速度と品質を両立する、QAエンジニア・開発者連携のAI協調型テストプロセス
shota_kusaba
0
500
Copilot 宇宙へ 〜生成AIで「専門データの壁」を壊す方法〜
nakasho
0
160
GitHub Copilot CLI で Azure Portal to Bicep
tsubakimoto_s
0
180
Kiroで見直す開発プロセスとAI-DLC
k_adachi_01
0
130
FastMCP OAuth Proxy with Cognito
hironobuiga
3
170
スピンアウト講座05_実践活用事例
overflowinc
0
1.1k
Phase07_実務適用
overflowinc
0
1.7k
形式手法特論:SMT ソルバで解く認可ポリシの静的解析 #kernelvm / Kernel VM Study Tsukuba No3
ytaka23
1
760
DDD×仕様駆動で回す高品質開発のプロセス設計
littlehands
5
2.3k
AIエージェント×GitHubで実現するQAナレッジの資産化と業務活用 / QA Knowledge as Assets with AI Agents & GitHub
tknw_hitsuji
0
200
脳が溶けた話 / Melted Brain
keisuke69
1
880
Featured
See All Featured
Jamie Indigo - Trashchat’s Guide to Black Boxes: Technical SEO Tactics for LLMs
techseoconnect
PRO
0
89
Highjacked: Video Game Concept Design
rkendrick25
PRO
1
320
Practical Orchestrator
shlominoach
191
11k
[RailsConf 2023] Rails as a piece of cake
palkan
59
6.4k
The Invisible Side of Design
smashingmag
302
51k
Writing Fast Ruby
sferik
630
63k
Joys of Absence: A Defence of Solitary Play
codingconduct
1
320
We Have a Design System, Now What?
morganepeng
55
8k
職位にかかわらず全員がリーダーシップを発揮するチーム作り / Building a team where everyone can demonstrate leadership regardless of position
madoxten
62
52k
Six Lessons from altMBA
skipperchong
29
4.2k
From π to Pie charts
rasagy
0
160
sira's awesome portfolio website redesign presentation
elsirapls
0
200
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! :)