🔷 GraphQL9. 실전 사례03 — Shopify Storefront API

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 APIAdmin API
주 사용자buyer (익명)merchant/app (인증)
권한읽기 위주 + cart/checkout읽기·쓰기 전부
토큰 노출공개 OK서버 전용
트래픽 패턴폭증 가능 (flash sale)안정적
Cost 모델초당 + leaky bucket초당 + leaky bucket (다른 값)
Schemabuyer 도메인만전체 도메인

2) Leaky Bucket Cost System

GitHub이 시간당 5,000 points라면, Shopify는 초당 + 버킷 용량이라는 leaky bucket 모델을 쓴다.

핵심 수치 (shopify.dev/docs/api/usage/limits):

APIBucket capacityLeak ratePlan 별
Storefront API2,000 points1,000 points/s(모두 동일, buyer 친화)
Admin API (standard)2,000 points100 points/sShopify, Basic
Admin API (advanced)10,000 points500 points/sShopify Plus

왜 leaky bucket인가:

  1. Burst tolerance — 짧은 폭증은 bucket이 흡수한다 (블랙프라이데이 첫 1초).
  2. Sustained rate — 평균 호출률이 leak rate를 넘으면 결국 막힌다.
  3. 공정성 — 한 클라이언트가 시간당 한도를 한 번에 쓰는 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)

APIPlanBucketRestore
Storefront모든 plan2,0001,000/s
Admin standardShopify/Basic/Trial2,000100/s
Admin advancedAdvanced/Shopify Plus10,000500/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 limitsshopify.dev/docs/api/usage/limits
  • Storefront API docsshopify.dev/docs/api/storefront
  • Admin GraphQL API docsshopify.dev/docs/api/admin-graphql
  • Rate Limiting GraphQL APIs by Calculating Query Complexityshopify.engineering/rate-limiting-graphql-apis-calculating-query-complexity
  • Hydrogen documentationhydrogen.shopify.dev
  • Headless commerce architectureshopify.dev/docs/storefronts/headless/building-with-the-storefront-api

다음 문서: 04-netflix-studio-edge.mdx — 공개 API 이야기에서 내부 federation 운영의 최대 사례로 넘어간다.