대부분의 드랍쉬핑 스토어를 여는 사람들은 지름길을 찾습니다. 포럼을 뒤지며 잘 팔리는 제품을 찾고, 저렴한 가상 비서(VA)를 고용하며, 알고리즘이 하룻밤 사이에 부를 가져다주길 바랍니다. 저에게 그런 방식은 매력적이지 않았습니다. 저는 드랍쉬핑을 엔지니어링 문제로 보았습니다. 단순히 빨리 돈을 벌려는 것이 아니었습니다. 재고 동기화 문제를 해결하고, 실제 시장 변화에 반응하는 가격 책정 알고리즘을 구축하며, 정신을 잃지 않고 공급업체 API와 씨름하고 싶었습니다. 스토어는 제가 Node.js와 PostgreSQL을 사용하여 구축한 시스템의 부산물이었습니다.

스토어를 백엔드 서비스처럼 다루기

드랍쉬핑을 마케팅 수단이 아닌 분산 시스템 과제로 바라보기 시작하는 순간, 문제들은 흥미로워집니다. 세 곳의 서로 다른 공급업체가 재고를 관리할 때 어떻게 스토어의 정확성을 유지할 수 있을까요? 공급업체가 예고 없이 원가를 변경할 때 어떻게 경쟁력 있는 가격을 유지할까요? 스프레드시트에 파묻히지 않고 50개의 SKU에서 5,000개로 늘어나는 카탈로그를 어떻게 관리할 수 있을까요?

저는 이러한 질문에 답하기 위해 파이프라인을 구축했습니다. Node.js는 이벤트 기반 아키텍처를 담당했습니다. 여러 공급업체 연결을 동시에 처리하기 위해 논블로킹(non-blocking) I/O가 필요했기 때문입니다. PostgreSQL은 확고한 데이터의 원천(source of truth) 역할을 했습니다. 저는 스키마 설계에 깊은 관심을 기울였습니다. 허술한 재고 테이블은 존재하지 않는 품목을 초과 판매하는 순간 악몽으로 변하기 때문입니다.

파이프라인 구축하기

핵심 작업은 간단했습니다. 공급업체 API에서 제품 데이터를 가져오는 것이었습니다. 실제로 이는 서로 통신하도록 설계되지 않은 엔드포인트로부터 SKU, 설명, 이미지, 재고 수준 및 가격을 수집하는 것을 의미했습니다. 저는 Node.js로 폴링(polling) 서비스를 작성하여 시차를 두고 공급업체 피드에 접속하도록 했습니다. 모든 유입 페이로드는 내부 스토어 데이터베이스에 반영되기 전에 검증 및 매핑 레이어를 거쳤습니다.

PostgreSQL은 제품, 변형(variant), 가격 이력, 동기화 로그를 위한 별도의 테이블로 구조화했습니다. 공급업체가 조용히 필드 이름을 바꾸거나 숫자가 있어야 할 자리에 null을 보내더라도, 파이프라인이 이를 포착하여 스토어를 오염시키는 대신 실패 기록을 남겼습니다. 로그 행을 보고 어떤 엔드포인트가 깨졌는지, 언제 발생했는지, 어떤 필드가 잘못되었는지 정확히 알 수 있었습니다. 이러한 관측성(observability) 덕분에 공급업체가 주말 사이에 API를 "업그레이드"하기로 결정했을 때 여러 번 위기를 넘길 수 있었습니다.

효과적이었던 부분

자동화는 엄청난 시간을 절약해 주었습니다. 초기에는 공급업체의 스프레드시트를 다운로드하고, 수동으로 정리하고, 이미지를 포맷팅하고, CSV를 스토어에 업로드하는 수동 방식을 시도했습니다. 하지만 카탈로그가 수십 개를 넘어서자 이는 불가능해졌습니다. 자동화된 파이프라인은 제가 다시는 스프레드시트를 만질 필요 없이 새로운 품목 등록, 가격 업데이트, 재고 조정을 처리했습니다.

제품 설명의 확장은 템플릿을 통해 이루어졌습니다. 거의 동일한 500개의 품목에 대해 각각 고유한 문장을 쓰는 것은 지속 가능하지 않습니다. 대신, 소재, 크기, 색상과 같은 공급업체 속성을 가져와 구조화된 설명 블록에 주입하는 템플릿 레이어를 구축했습니다. 결과물은 변환하기에 충분히 깔끔했고, 일관성이 있어 천 개의 새로운 SKU를 추가할 때도 수동 카피라이팅이 필요하지 않았습니다.

가격 모니터링 또한 기대 이상이었습니다. 주요 제품의 일부에 대해 경쟁사 가격을 추적하는 가벼운 모니터링 레이어를 구축했습니다. 가격 변동이 감지되면 시스템은 제가 설정한 가드레일 내에서 마진을 자동으로 조정했습니다. 공급업체가 도매가를 인하하면, 판매가는 며칠이 아닌 몇 분 내에 그 변화를 반영할 수 있었습니다. 이러한 즉각적인 대응은 마진이 낮은 품목에서 눈에 띄는 차이를 만들어냈습니다.

문제가 발생한 부분과 그 이유

공급업체 API는 일관성이 없습니다. 이는 불평이 아니라 지질학적 사실과도 같습니다. 어떤 파트너는 예측 가능한 페이지네이션(pagination)과 함께 깔끔한 JSON을 제공합니다. 다른 파트너는 월요일에는 camelCase 태그를, 수요일에는 snake_case 태그를 포함한 XML을 반환합니다. 속도 제한(rate limits)은 관대할 때도 있고 가혹할 때도 있습니다. 다운타임은 적절한 상태 코드 대신 HTML 에러 페이지를 통해 전달됩니다. 결국 2003년에 설계된 것처럼 동작하는 엔드포인트를 위해 방어적인 파서(parser)와 재시도 로직을 작성하게 됩니다.

재고 동기화 과정에서 발생한 레이스 컨디션(race conditions) 때문에 밤잠을 설쳤습니다. 상상해 보세요. 두 고객이 불과 몇 초 차이로 마지막 남은 재고를 주문하거나, 구매자가 결제 버튼을 누르는 바로 그 순간 공급업체의 웹훅(webhook)이 재고가 0이 되었다고 알리는 상황을 말이죠. 처음에 제가 구현했던 '조회 후 업데이트(read-then-update)' 로직은 처참하게 실패했습니다. 결국 회전율이 높은 SKU를 위해 원자적(atomic) PostgreSQL 트랜잭션과 비관적 잠금(pessimistic locking)을 사용하여 동기화 레이어를 다시 작성해야 했습니다. 이는 실제 돈이 걸린 상황만큼 그 어떤 튜토리얼도 미리 알려줄 수 없는, 동시성(concurrency)에 관한 고통스럽고도 실전적인 교훈이었습니다.

저의 가장 큰 실수는 고객 지원 자동화를 간과한 것이었습니다. 데이터 파이프라인에만 집착했고, 그로 인해 발생하는 인간적인 후폭풍은 뒷전으로 미뤄두었습니다. 주문은 늦게 도착했고, 공급업체는 잘못된 색상의 제품을 배송했습니다. 제가 API 타임아웃을 디버깅하는 동안 고객들이 보낸 이메일은 몇 시간 동안 받은 편지함에 쌓여만 갔습니다. 티켓 라우팅도, 자동 응답도, 챗봇 핸드오프(handoff)도 없었습니다. 기술적 인프라는 탄탄했습니다. 하지만 휴먼 인프라가 부재했고, 그 공백은 불안정한 웹훅보다 훨씬 더 사업에 큰 타격을 주었습니다.

엔지니어처럼 이미지 테스트하기

제품 이미지에 대해 사이드 실험을 진행했습니다. 세션 기반 버킷팅(bucketing)과 연동된 간단한 URL 파라미터 라우팅을 사용하여 사용자마다 서로 다른 히어로 이미지(hero images)를 보여주었습니다. 한 변형(variant)은 제품을 단순한 흰색 배경에 보여주었고, 다른 변형은 실제 책상 위 라이프스타일 설정에서 보여주었습니다. 주문 흐름과 직접 연결된 기본적인 이벤트 로깅을 사용하여 각 버킷의 전환율을 추적했습니다.

작은 변화가 참여도(engagement)를 높였습니다. 라이프스타일 샷이 항상 승리하는 것은 아니었지만, 승리했을 때의 상승 폭(lift)은 제가 우선순위를 정하는 방식을 바꿀 만큼 의미 있었습니다.