Introduction & Requirements
고객이 운동화 한 켤레를 장바구니에 담았습니다. 배송지를 입력하고 결제 버튼을 눌렀는데 화면이 계속 돌아갑니다. 고객은 버튼을 다시 누릅니다. 같은 시각 다른 고객도 마지막 한 켤레를 구매하고 있습니다.
서버는 이 상황에서 서로 다른 질문에 답해야 합니다.
고객에게 보여준 금액이 아직 유효한가. 마지막 재고는 누구에게 배정됐는가. 카드 승인은 실제로 성공했는가. 주문은 이미 만들어졌는가. 같은 요청이 다시 들어왔을 때 어느 단계부터 이어가야 하는가.
가장 단순한 구현은 재고를 조회하고, 결제 API를 호출하고, 주문을 저장하는 것입니다. 그러나 재고 조회와 차감 사이에 다른 요청이 들어올 수 있습니다. 결제는 성공했는데 응답만 사라질 수도 있습니다. 주문 저장 직후 프로세스가 종료되면 고객은 완료 화면을 보지 못합니다.
이 문제는 요청이 적어도 발생합니다. 트래픽이 커지면 같은 상품에 대한 경쟁, 외부 결제 지연, 재시도가 겹치면서 발생 빈도가 높아집니다. 따라서 서버 수를 늘리기 전에 구매 과정에서 반드시 지켜야 할 조건부터 정해야 합니다.
Shopify Help Center는 체크아웃 단계에서 재고를 확인하며, 고객이 결제 정보를 제출할 때 재고를 보류한다고 설명합니다. 장바구니에 담는 것과 재고를 확보하는 것은 서로 다른 동작입니다.
Shopify는 2026년 5월 공개한 재고 예약 사례에서 예약 상태를 Redis에서 MySQL로 옮겼다고 설명했습니다. 예약과 재고 원본을 같은 데이터베이스에서 원자적으로 변경하고, 인기 상품의 수량 행에 요청이 집중되는 문제에는 판매 단위별 예약 행과 크기가 제한된 풀을 사용했습니다.
이 글은 해당 공개 동작과 설계 사례를 출발점으로 삼습니다. 이후의 API, SQL 스키마, 상태 이름, 트래픽, SLO는 교육을 위한 설계 가정입니다. Shopify 전체의 현재 내부 구현을 재현한 것이 아닙니다.
재고 추적이 활성화되어 있고, 품절 이후 판매를 허용하지 않는 상품을 기준으로 설명하겠습니다. 선주문이나 재고 추적을 하지 않는 상품에는 다른 판매 정책이 필요합니다. Shopify의 외부 결제 재고 확인 문서도 재고 추적과 품절 후 판매 설정을 확인 조건으로 구분합니다.
일관성이 지켜야 하는 조건
하나의 체크아웃에서는 주문이 최대 하나만 생성되어야 합니다. 같은 구매를 재시도한 것인지, 별도의 구매인지 구분할 수 있는 식별자가 필요합니다.
예약과 판매 확정이 같은 재고를 중복 소비하면 안 됩니다. 이 보장은 서버가 관리하는 판매 가능 수량을 기준으로 합니다. 창고 분실이나 시스템에 아직 보고되지 않은 오프라인 판매까지 데이터베이스가 알아낼 수는 없습니다.
고객이 동의한 상품, 수량, 통화, 금액과 결제 요청이 일치해야 합니다. 결제 도중 가격을 다시 계산해 고객이 승인하지 않은 금액으로 바꾸면 안 됩니다.
결과가 불확실한 결제는 실패와 구분해야 합니다. 응답을 받지 못했다는 사실만으로 새로운 결제를 시작하지 않습니다.
출고는 재고와 결제 조건을 충족한 주문에만 허용합니다. 주문 접수, 결제 승인, 매입 완료, 출고 가능 상태를 하나의 성공 여부로 합치지 않습니다.
Functional Requirements
고객은 상품 수량, 배송지, 배송 방법을 변경하고 서버가 계산한 최종 금액을 확인할 수 있습니다.
고객은 결제를 제출하고, 인증이 필요하면 인증을 마친 뒤 같은 구매의 진행 상태를 이어서 확인할 수 있습니다.
시스템은 판매 가능한 위치의 재고를 예약하고, 주문 확정 시 해당 예약을 주문에 배정합니다.
시스템은 같은 요청의 반복 전송과 서로 다른 키로 들어온 동일 체크아웃의 동시 제출을 모두 방어합니다.
시스템은 결제 승인, 매입, 승인 취소, 환불을 구분하고 각 작업을 안전하게 재시도합니다.
시스템은 결제 결과를 확인할 수 없는 경우 진행 상태를 보존하고 외부 결제 사업자와 대조합니다.
판매자는 주문과 결제의 현재 상태를 확인하고, 실패하거나 취소된 구매의 재고가 반환되었는지 추적할 수 있습니다.
시스템은 한 판매자의 행사 트래픽이 다른 판매자의 체크아웃 처리 용량을 과도하게 점유하지 못하게 합니다.
Non-Functional Requirements
다음 수치는 모두 설계를 위한 가정이며 Shopify의 공개 SLA가 아닙니다.
체크아웃 조회는 서버 처리 시간 기준 p99 300ms, 구매 요청의 영속적 접수는 p99 500ms를 목표로 합니다. 외부 카드 인증과 결제 완료 시간은 별도로 측정합니다.
유효한 접수 요청에 대한 월간 API 가용성 목표는 99.95%로 둡니다. 결제 거절, 재고 부족, 용량 제한, 내부 오류를 구분해 집계하고 전체 구매 완료율도 함께 관찰합니다.
주문 생성, 재고 예약, 결제 작업 생성은 해당 원본 상태에서 강한 정합성을 요구합니다. 상품 화면의 재고 표시와 판매 분석에는 짧은 갱신 지연을 허용합니다.
성공 응답을 반환한 구매 의도와 주문은 프로세스 장애 이후에도 복구할 수 있어야 합니다. 지역 전체 장애에서의 데이터 유실 범위는 복제 정책과 함께 별도로 정의합니다.
동일 체크아웃의 동시 실행과 판매자 간 데이터 접근을 차단합니다. 모든 구매 상태 조회에도 소유권 검사를 적용합니다.
카드 원문과 보안 코드를 일반 체크아웃 DB나 로그에 저장하지 않습니다. 결제 토큰과 외부 작업 참조만 제한적으로 보관합니다.
결제 결과 미확정 상태의 체류 시간, 미처리 보상 작업 수, 재고 불일치를 운영 지표로 관리합니다.
행사 부하에서도 대기열 길이와 DB 연결 수에 상한을 두고, 신규 구매 유입과 이미 접수한 구매의 복구에 처리 용량을 나누어 배정합니다.
Out of Scope
상품 검색, 추천, 광고와 판매자용 상품 편집 화면
세율 산정 규칙 전체와 배송 사업자별 요금 계산 구현
카드 네트워크, 은행 정산, 판매자 지급 시스템의 내부 구현
구독 청구, 여러 결제 수단의 분할 결제, 여러 판매자의 상품을 한 번에 구매하는 거래
창고 작업과 반품 검수의 전체 과정
네트워크가 끊긴 POS에서 재고 확인 없이 판매하는 정책
세금과 배송비 계산기는 외부 또는 내부 의존성으로 다룹니다. 최종 계산 결과를 구매에 고정하는 책임은 체크아웃에 남깁니다.
Traffic & Capacity Assumptions
체크아웃 조회와 수정
하루 체크아웃 세션을 1,000만 건으로 가정하겠습니다. 세션당 조회와 수정 요청이 평균 8번이면 하루 8,000만 요청입니다. 평균 약 초당 926건이며, 피크를 평균의 20배로 잡으면 약 초당 1만 8,500건입니다.
조회와 주소 수정은 결제 제출보다 훨씬 자주 발생합니다. 이 요청을 모두 재고 잠금이나 결제 처리와 연결하면 고객이 배송지를 고치는 동안에도 중요한 쓰기 경로가 경쟁하게 됩니다. 조회, 견적 계산, 구매 확정을 구분해야 합니다.
구매 제출과 주문
세션의 20%가 결제 제출까지 진행한다고 가정하면 하루 구매 시도는 200만 건입니다. 평균 약 초당 23건이며, 같은 피크 배수를 적용하면 초당 463건입니다. 이 가운데 60%가 주문으로 이어진다면 하루 주문은 120만 건입니다.
전체 평균만으로 인기 상품의 경합을 예측할 수는 없습니다. 별도 행사 부하 시험에서는 한 판매자에게 초당 2,000건의 구매 시도가 몰리는 상황을 검증하겠습니다. 이 값은 앞의 정상 운영 피크와 별개인 스트레스 조건이며, 실제로 이를 허용하려면 플랫폼 용량 계획도 함께 높여야 합니다.
재고와 결제 동시 처리량
구매 시도당 재고 항목을 평균 2개로 가정하면 정상 피크에서 초당 약 926개 항목의 예약을 시도합니다. 품목마다 다른 행을 변경하는 경우와 거의 모든 요청이 같은 SKU를 변경하는 경우는 같은 QPS라도 처리량이 다릅니다.



