03 — Shopify Storefront API
한 줄 답: Shopify는 Storefront API(공개·고트래픽·buyer)와 Admin API(인증·계산비용·merchant)를 분리하고 leaky bucket cost system을 두어 헤드리스 커머스를 지속 가능하게 만들었다. GitHub이 시간당 점수라면, Shopify는 초당 점수 + 버킷이다 — 트래픽 패턴이 다르기 때문.
Why — 왜 Shopify가 흥미로운가
Shopify는 두 종류의 클라이언트를 동시에 만족시켜야 한다.
| 클라이언트 | 특성 | 요구 |
|---|---|---|
| Buyer (구매자) | 익명, 트래픽 폭증(블랙프라이데이), 읽기 위주 | 빠르고 제한 없는 듯한 API |
| Merchant/App (판매자/앱) | 인증, 안정적 백오피스 작업, 쓰기 포함 | 예측 가능한 cost 예산 |
이 둘을 하나의 API로 묶으면 — 권한 모델이 폭발하고, DDoS 방어가 어렵고, cost 모델이 둘을 다 만족시키지 못한다.
Shopify의 해법은 두 개의 분리된 GraphQL API다. 같은 백엔드 데이터지만 접근 패턴이 다른 두 API를 별도로 운영한다.
| 흔한 오해 | 현실 |
|---|---|
| ”Storefront API는 REST 대체재” | Storefront API는 처음부터 GraphQL로 설계됐다 (REST 없음) |
| “Admin API와 Storefront API는 schema가 같다” | 전혀 다르다 — 의도된 분리 |
| ”Storefront에 rate limit이 없다” | 전통적 호출 수 제한은 없지만 cost 기반 leaky bucket은 있다 |
권한·트래픽 패턴이 다른 사용자가 있다면 GraphQL을 하나로 합치지 말고 둘로 갈라야 한다 — Shopify가 가르치는 패턴.
How — 어떻게 운영하나
1) Storefront API vs Admin API의 철학적 분리
결정적 차이 — Storefront API의 토큰은 브라우저 JavaScript에 박혀 있어도 안전하다. Admin API의 토큰은 절대 브라우저에 있으면 안 된다.
| 항목 | Storefront API | Admin API |
|---|---|---|
| 주 사용자 | buyer (익명) | merchant/app (인증) |
| 권한 | 읽기 위주 + cart/checkout | 읽기·쓰기 전부 |
| 토큰 노출 | 공개 OK | 서버 전용 |
| 트래픽 패턴 | 폭증 가능 (flash sale) | 안정적 |
| Cost 모델 | 초당 + leaky bucket | 초당 + leaky bucket (다른 값) |
| Schema | buyer 도메인만 | 전체 도메인 |
2) Leaky Bucket Cost System
GitHub이 시간당 5,000 points라면, Shopify는 초당 + 버킷 용량이라는 leaky bucket 모델을 쓴다.
핵심 수치 (shopify.dev/docs/api/usage/limits):
| API | Bucket capacity | Leak rate | Plan 별 |
|---|---|---|---|
| Storefront API | 2,000 points | 1,000 points/s | (모두 동일, buyer 친화) |
| Admin API (standard) | 2,000 points | 100 points/s | Shopify, Basic |
| Admin API (advanced) | 10,000 points | 500 points/s | Shopify Plus |
왜 leaky bucket인가:
- Burst tolerance — 짧은 폭증은 bucket이 흡수한다 (블랙프라이데이 첫 1초).
- Sustained rate — 평균 호출률이 leak rate를 넘으면 결국 막힌다.
- 공정성 — 한 클라이언트가 시간당 한도를 한 번에 쓰는 GitHub 모델보다 분산된 부하 패턴.
→ 트래픽 모양이 e-commerce의 spike pattern에 맞춰 설계됐다.
3) Query Cost 계산 — Admin API 기준
쿼리 한 줄마다 Shopify가 매긴 점수가 있다. extensions.cost로 조회 가능.
query {
products(first: 10) {
edges {
node {
id
title
variants(first: 5) { edges { node { id title } } }
}
}
}
}응답:
{
"data": { ... },
"extensions": {
"cost": {
"requestedQueryCost": 62,
"actualQueryCost": 22,
"throttleStatus": {
"maximumAvailable": 2000,
"currentlyAvailable": 1978,
"restoreRate": 100
}
}
}
}핵심 4값:
| 필드 | 의미 |
|---|---|
requestedQueryCost | 정적 분석으로 추정한 최대 비용 (connection의 first/last 곱) |
actualQueryCost | 실제 반환된 노드 수에 따라 다시 계산된 비용 (보통 더 작음) |
currentlyAvailable | 지금 남은 bucket 크기 |
restoreRate | 초당 회복 점수 |
→ requested로 사전에 막고, actual로 과금 정정. 두 값의 차이가 클라이언트가 얼마나 헛 페치를 했는지를 보여준다.
4) Scalar Money / Decimal 처리
e-commerce는 돈을 다룬다. JavaScript Number(IEEE 754)로 $0.10 + $0.20을 하면 0.30000000000000004가 나온다 — 프로덕션에 절대 못 쓸 결과.
Shopify의 schema가 정착시킨 패턴:
type MoneyV2 {
amount: Decimal! # 문자열 "10.50"
currencyCode: CurrencyCode! # ISO 4217 enum
}
scalar Decimal # 문자열 직렬화
enum CurrencyCode {
USD KRW JPY EUR GBP ...
}Decimal은 문자열 scalar다 — Float 오차 회피.- 통화는 enum으로 — 타이포 차단.
- Money가 아니라 MoneyV2 — 첫 시도에서 한 번 깨졌다는 흔적 (v1은 amount가 Float였음, 2019년 deprecated).
e-commerce GraphQL을 설계한다면 돈은 Float가 아니라 문자열 scalar — Shopify가 가르치는 절대 규칙.
5) Headless Commerce 아키텍처
Shopify의 Hydrogen(React 프레임워크, 2021~)이 보여주는 표준 아키텍처:
- Edge에서 SSR — Storefront API는 GraphQL이라 원하는 필드만 페치 → 페이로드 최소화 → edge 응답 빠름.
- Checkout은 Shopify 위임 — PCI/SCA/세금 복잡도를 자체 구현 안 함.
- Token이 공개 — 브라우저에서 직접 cart 조작 OK.
What — 구체 사양
Storefront API endpoint
POST https://{shop}.myshopify.com/api/2024-10/graphql.json
X-Shopify-Storefront-Access-Token: <public token>
Content-Type: application/json- 버전은 분기별 릴리즈 (
2024-10,2025-01, …) — URL에 박힘 - 한 버전은 최소 12개월 지원 (deprecation policy 공개)
Admin API endpoint
POST https://{shop}.myshopify.com/admin/api/2024-10/graphql.json
X-Shopify-Access-Token: <admin token>- 같은 분기 버전 체계
- 서버에서만 호출
공개된 cost 수치 (2024 기준, shopify.dev/docs/api/usage/limits)
| API | Plan | Bucket | Restore |
|---|---|---|---|
| Storefront | 모든 plan | 2,000 | 1,000/s |
| Admin standard | Shopify/Basic/Trial | 2,000 | 100/s |
| Admin advanced | Advanced/Shopify Plus | 10,000 | 500/s |
Throttled 응답
쿼리가 bucket을 넘기면:
{
"errors": [{
"message": "Throttled",
"extensions": { "code": "THROTTLED" }
}],
"extensions": { "cost": { ... } }
}- HTTP는 여전히 200이다 — GraphQL의 partial error 관행
extensions.cost.throttleStatus.currentlyAvailable을 보고 얼마나 기다릴지 계산
What-if — 잘못 이해하면
1) “Storefront API에 cost 신경 안 써도 된다”고 믿으면
→ Storefront도 2,000 bucket + 1,000/s leak이 있다. 봇이 cart 생성을 폭주시키면 throttle. 대응: cart mutation은 idempotency key와 함께, retry는 exponential backoff.
2) Admin과 Storefront를 하나의 graph로 합치려고 하면
→ Shopify가 18년간 쌓은 권한·트래픽 분리를 무너뜨림. 한 토큰이 checkout과 inventory를 동시에 보면 공격 표면이 곱. 대응: 권한 모델이 다르면 별도 graph. Shopify가 검증한 패턴.
3) Money scalar를 Float로 두면
→ 0.1 + 0.2 = 0.30000000000000004 영원. 결제 금액 1원 차이로 반품 폭주.
대응: 처음부터 Decimal scalar = 문자열. 통화는 enum.
4) requestedQueryCost를 과금 기준으로 잘못 알면
→ requested는 상한, actual이 실제 차감. 평균적으로 actual이 훨씬 작다.
대응: 클라이언트가 actual만 보고 공격적으로 페치해도 OK. 단 상한이 일시적으로 차단하는 패턴은 인지.
5) Hydrogen(Oxygen) 없이도 edge SSR이 자동이라고 믿으면
→ Hydrogen은 서버 컴포넌트 + 캐시 정책을 제공한다. 직접 Next.js로 짜면 캐시 무효화·preview·token rotation을 직접 구현. 대응: 헤드리스를 처음 한다면 Hydrogen, 이미 Next/Remix 자산이 많다면 Storefront API SDK만 빌려서 직접 통합.
Insight — 흥미로운 이야기
”Money v1 → MoneyV2의 흔적”
Shopify schema에서 MoneyV2라는 이름을 본 사람은 그 이전에 v1이 있었음을 안다. v1은 amount: Float였고, 2019년쯤 Float의 부동소수점 오차가 production에서 문제가 되어 Decimal scalar로 마이그레이션했다. 이 마이그레이션이 deprecation policy의 첫 큰 시험이었고 — 무사히 끝났기 때문에 Shopify의 schema 진화 모델이 굳었다. 흔적이 이름에 영원히 남는다.
”Storefront API의 공개 토큰이라는 발상”
REST 세계에선 공개 토큰이라는 개념이 어색했다. GraphQL 세계에서 — 공개 토큰은 schema의 일부 권한만 가진다가 자연스럽다. Shopify의 Storefront 토큰은 cart 생성, 상품 조회, checkout 시작만 할 수 있다. 재고 변경, 주문 환불 같은 mutation은 schema에서 아예 못 본다. 권한 = schema 가시성이라는 패턴이 여기서 굳어졌다.
”Rate limiting GraphQL APIs by Calculating Query Complexity”
2018년 Shopify Engineering 블로그(shopify.engineering/rate-limiting-graphql-apis-calculating-query-complexity)가 cost system의 알고리즘을 공개했다. GitHub이 값과 사용법을 공개했다면, Shopify는 알고리즘 구현 방법을 공개했다 — connection의 first/last를 정적 분석, resolver별 비용 함수, bucket 갱신 로직까지. 이 글이 지금까지도 cost system 구현의 표준 참고서다.
”분기별 버전 = REST 흔적”
Shopify의 분기별 API 버전(2024-10)은 REST API의 관행을 GraphQL로 옮긴 것이다. GitHub은 버전 없음 + preview로 갔고, Shopify는 버전 있음 + 분기별 deprecation으로 갔다. 어느 쪽이 옳다기보다 — e-commerce는 회계 연도·세금 정책·해외 진출 같은 외부 시간 축에 묶여 있어서 명시적 버전이 협업하기 쉽다는 판단.
요약 + 다이어그램
Shopify는 권한·트래픽 모양이 다른 두 클라이언트(buyer vs merchant)를 위해 Storefront/Admin 두 GraphQL API로 분리했다. Leaky bucket cost system이 GitHub의 시간당 모델을 진화시켰고, Decimal scalar는 e-commerce GraphQL의 사실상 표준이 되었다.
참고 자료
- Shopify API limits —
shopify.dev/docs/api/usage/limits - Storefront API docs —
shopify.dev/docs/api/storefront - Admin GraphQL API docs —
shopify.dev/docs/api/admin-graphql - Rate Limiting GraphQL APIs by Calculating Query Complexity —
shopify.engineering/rate-limiting-graphql-apis-calculating-query-complexity - Hydrogen documentation —
hydrogen.shopify.dev - Headless commerce architecture —
shopify.dev/docs/storefronts/headless/building-with-the-storefront-api
다음 문서:
04-netflix-studio-edge.mdx— 공개 API 이야기에서 내부 federation 운영의 최대 사례로 넘어간다.