2019년, 캘리포니아 온타리오의 3PL 창고에서 나는 이런 장면을 직접 봤다. 포장 스테이션에 서른~마흔 개의 박스가 쌓여 있고, 작업자가 핸드헬드로 하나씩 스캔한다. 시스템은 모든 주문에 "라벨 생성됨"을 표시하는데, 정작 테이블 위에 있는 라벨은 절반뿐이었다. 프린터는 용지를 멀쩡히 잘 급지했다. 그냥... 부족했던 거다. 고객센터 전화가 벌써 오고 있었다. 왜 배송 추적이 업데이트되지 않느냐는 구매자들의 문의였다. 그날 오후 우리는 체인 전체를 뒤집어봤고, 결국 운송사와는 아무 상관이 없었다. 우리 재시도 로직이 멱등 키를 싣지 않았던 거다. 타임아웃 난 요청이 사실은 운송사 쪽에서 성공했는데, 시스템은 실패로 판단하고 다시 발사했다. 두 번째 호출은 같은 라벨을 돌려주고, 첫 번째 레코드를 덮어썼다. 첫 번째 박스는 이미 다른 인쇄 라벨을 붙이고 출고된 뒤였다. 정산이 안 맞았던 건 덮어쓰인 라벨이었다.

결론부터 말한다. 운송사 라벨 API가 라벨을 "잃어버리는" 경우, 90%는 운송사 잘못이 아니라 자사의 재시도 로직과 상태 머신 잘못이다. 나머지 10% 중 일곱 부분 정도는 인쇄 큐, 세 부분은 운송사 쪽이다. 과격하게 들리나? 나는 미국 창고 현장에서 20년을 보냈고, 두 손으로 셀 수 없을 만큼 많은 라벨 API를 연동했다. 근본 원인은 언제나 아래 여섯 단계 중 하나다. 증상에서 근본 원인까지, 순서대로 밟는 디버깅 체인을 제시한다. 오후 반나절이면 특정할 수 있다.

아직 운송사를 탓하지 마라 — 요청 로그부터 봐라

고장 나면 1단계는 언제나 로그다. 운송사 대시보드가 아니라, 자사 시스템에 남은 "라벨 요청이 실제로 나갔는지, 운송사가 뭐라고 답했는지" 기록이다.

어디를 볼까: WMS나 미들웨어의 API 로그 테이블. 건강한 모습: 모든 호출마다 타임스탬프, 주문 번호, 요청 본문, 응답 본문, HTTP 상태 코드, 지연 시간이 기록된다. 확인할 것 세 가지.

첫째, 요청과 응답 전체가 저장되는가? "성공/실패"만 저장하고 응답 본문은 버리는 시스템이 많다. 그건 블랙박스다. 완전한 요청/응답 쌍 없이 사라진 라벨을 디버깅하는 건 거의 찍기다. 내가 본 최악의 설정은 주문 번호와 "OK"만 로그에 남겼다. 고장 나면 팀이 운송사 포털에서 주문 하나하나 대조해야 했다. 오후 반나절 일이 사흘로 늘어났다.

둘째, 타임아웃이 있었나, 재시도는 몇 번인가? 라벨 API의 타임아웃은 정상이다. 운송사는 피크 때 느려지고, 때로 10초를 넘는다. 진짜 질문: 타임아웃 후에 재시도했는가, 그 재시도에 멱등 키가 실렸는가? 타임아웃은 실패가 아니다. 자사 시스템이 응답을 못 받는 동안, 운송사 쪽에서는 요청이 성공했을 수 있다. 무턱대고 재시도하면 라벨이 두 장 인쇄된다. 더 나쁜 건, 같은 추적 번호를 공유하는 라벨 두 장이 나오고, 시스템은 두 번째만 인식하는 경우다. 첫 번째 라벨은 고스트가 된다. 박스에 물리적으로 붙어 있는데, 시스템에는 안 보인다.

셋째, 응답의 추적 번호가 시스템에 저장된 것과 일치하는가? 운송사 마스터 추적 번호와 박스 단위 번호를 헷갈려서 잘못된 필드를 저장하는 연동도 있다. 라벨은 안 없어졌다. 보는 곳이 틀린 거다.

로그가 완전하면, 이 단계 하나만으로 80%는 문제가 드러난다. 로그가 불완전하면, 다른 걸 디버깅하기 전에 로깅부터 고쳐라. 데이터 없는 결론은 미신이다.

한 주문이 API를 두 번 치면, 라벨이 몇 장 나올까?

2단계: 멱등성. 여기는 재난 지대이며, 내가 처음에 꺼낸 이야기의 근본 원인이다.

건강한 모습: 같은 주문 번호가 몇 번이든 API를 치면, 운송사 쪽에는 유효한 라벨이 정확히 한 장. 반복 호출은 새 라벨을 찍는 대신 첫 번째 결과를 돌려준다. 이 보장은 멱등 키에서 나온다. 보통 주문 번호 + 업무 고유 식별자를 요청에 실어 보낸다.

어디를 볼까: 자사 API 래퍼. 라벨 요청이 정말로 멱등 필드를 전송하는가? 대부분의 운송사 라벨 API가 지원하는데(정확한 필드명은 운송사 최신 문서를 확인), 안 보내는 창고 연동이 많다. 더 교활한 변형: 보내긴 하는데 세분성이 잘못된 경우. "주문 번호 + 타임스탬프"를 키로 쓰는 예. 타임스탬프는 매번 다르니 안 보내는 것과 같다. 재시도할 때마다 새 라벨이 찍힌다.

검증 방법: 라벨이 사라진 주문을 하나 가져와서, 로그에서 API가 몇 번 호출됐고 각 호출이 같은 추적 번호를 돌려줬는지 센다. 두 번 호출해서 추적 번호 두 개가 다르게 돌아오고, 시스템에 저장된 건 두 번째뿐이라면, 첫 번째 추적 번호의 라벨이 "사라진" 그거다. 실제로 존재한다. 어딘가 박스에 붙어 있다. 박스는 출고됐다. 시스템은 모른다. 고객센터가 보는 건 "라벨은 생성됐는데 추적 업데이트가 없음"이다.

그리고 데이터베이스 차원의 안전장치 하나: 라벨/추적 번호 컬럼에 유니크 제약이 필요하다. 없으면 중복 쓰기가 절대 에러가 안 난다. 더러운 데이터가 조용히 쌓이고, 알아챘을 땐 아무것도 안 맞는다. 이 제약 추가는 공짜고 즉시 효과가 난다.

라벨은 생성됐는데 상태가 기록되지 않았다: 상태 머신을 봐라

3단계: 비동기 콜백과 상태 머신. 일부 라벨 API는 비동기다. 요청을 보내면 운송사는 "접수됨"으로 답하고, 실제 라벨 파일과 추적 번호는 수 초~수 분 뒤 콜백으로 온다.

건강한 모습: 주문 상태가 깨끗하게 흐른다. 예를 들어 pending → creating → created → printed. 각 전환마다 타임스탬프. 콜백이 아직 안 왔으면, 주문은 "creating"에 있어야지, 어쩌다 "created"가 돼 있으면 안 된다.

어디를 볼까: 상태 전환 로그. 건너뛴 상태를 조심해라. 콜백은 안 왔는데 상태가 "created"다. 대개는 너무 낙관적인 폴링 잡이거나, 요청을 보낸 순간 성공으로 찍어버리는 프론트엔드 로직이 원인으로, 콜백을 안 기다린다.

더 지독한 변형: 콜백은 왔는데, 자사 콜백 엔드포인트에서 에러가 났다. 서명 검증 실패, 데이터베이스 락 타임아웃 같은. 그러면 운송사는 "전달됨"인데 자사는 저장 못 했다. 양쪽이 제각각 이야기를 한다. 콜백 엔드포인트의 에러 로그를 봐라. 증거는 거의 항상 거기 있다. 아무도 안 읽었을 뿐이다.

내 결론: 상태 머신은 라벨 파이프라인에서 가장 제대로 만들 가치가 있는 부분이다. 건너뛴 상태를 허용하는 상태 머신은 상태 머신이 아니다.

프린터: 가장 억울한 희생양

앞의 세 단계가 깨끗하면, 인쇄를 봐라. Zebra 프린터는 생각보다 잡을 자주 떨군다.

건강한 모습: 라벨 파일이 인쇄 큐에 들어가고, 프린터가 한 장씩 토해내고, 각 용지에 인쇄 확인이 붙는다. 어디를 볼까: 인쇄 서버의 큐 로그. 각 잡이 "완료"인가, "에러/취소"에 앉아 있는가? Zebra 프린터에는 고전적인 버릇이 있다. 리본이나 라벨 용지가 떨어지거나 헤드가 과열되면, 누구에게 시끄럽게 알리는 대신, 프론트엔드는 "전송됨"이라고 하면서 잡은 그냥 큐에 멈춰 있다. 작업자는 용지가 안 나오는 걸 보고, 시스템이 라벨을 안 만든 줄 알고 다시 인쇄를 누른다. 인쇄 확인 핸드셰이크 없이는 그게 중복 인쇄다.

출고 스테이션에서 운송사 배송 라벨 부착

또 다른 고전: 네트워크 프린터가 끊겼다가 다시 연결되면, 큐에 쌓인 백로그가 한꺼번에 쏟아져 나온다. 작업자는 몇 장 뜯어서 붙이고, 나머지는 뒤섞인다. 잘못 붙인 라벨, 빠진 라벨 천지다. 그래서 내 예방 체크리스트는 인쇄 확인 영수증을 고집한다. 프린터가 각 용지를 확인해주고, 그때야 시스템이 상태를 "printed"로 넘긴다. 영수증 없는 인쇄는 안 한 인쇄다.

운송사 쪽: 속도 제한과 주소 검증, 조용히 삼켜진다

마지막으로, 운송사. 정직한 결론: 진짜 운송사 쪽 원인은 드물다. 일 년에 몇 번이 고작이다. 하지만 조사가 가장 어렵다. 문제가 내 손에 없기 때문이다.

어디를 볼까: 운송사 개발자 포털의 API 호출 기록과 에러 코드. 두 가지 카테고리에 집중한다.

첫째, 속도 제한. 프로모션 시즌이나 피크에 QPS가 계정 한도를 넘으면 운송사가 요청을 거부한다. 깔끔한 429를 돌려주는 곳도 있고, 솔직히 말하면, 한 대형 운송사는 스로틀 시 특정 엔드포인트에서 200을 돌려주면서, 조용한 에러 메시지를 본문에 숨겨 둔다. 코드가 HTTP 상태만 확인하면 라벨이 생성됐다고 믿는다. 안 됐다. 그건 조용한 실패다. 조사할 때는 상태 코드가 아니라 응답 본문을 읽어라.

둘째, 주소 검증 실패. 미국 주소는 까다롭다. 아파트/스위트 번호 한 자리 틀림, ZIP+4 불일치, 그러면 운송사 쪽 검증이 주문을 거부한다. 당연히 명확한 주소 에러로 돌아와야 하는데, 어떤 연동은 검증 에러를 일반 예외로 삼켜버리고 "라벨 생성 실패"만 로그에 남긴다. 작업자는 실패를 보고 수동으로 주문을 입력한다. 시스템 기록과 살짝 다른 주소로. 박스는 출고되고, 아무것도 안 맞는다. 이 카테고리는 사후 디버깅보다 사전 주소 검증이 열 배 중요하다.

구체적인 속도나 스로틀 수치는 여기선 인용하지 않겠다. 운송사 정책은 빨리 바뀌니, 공식 사이트의 최신 공개 정책을 따르라.

매일 정산하라: 마지막 안전망이자, 가장 먼저 만들었어야 할 것

6단계이자, 내일 당장 만들었으면 하는 것: 매일 운송사 청구서와 라벨 건수를 정산하라.

로직은 단순하다. 어제의 자사 시스템 "라벨 생성됨" 건수는 어제의 운송사 청구서 라벨 건수와 같아야 한다. 차이가 나면, 그 차이가 사라진 라벨이다. 체인의 어느 단계가 삼켰든, 숫자는 거짓말하지 않는다.

방법: 매일 밤 운송사 청구서나 사용 보고서를 가져와서(대부분 운송사 포털에서 익스포트 된다), 추적 번호로 자사 시스템의 라벨 기록과 건건이 대조하는 일일 정산 잡을 작성한다. 예외 목록은 매일 아침 운영과 IT 메일함에 도착한다. 첫 실행에 놀랄 수도 있다. 그건 과거의 부채가 한꺼번에 드러난 거다. 좋은 거다. 그동안 메커니즘이 맨몸으로 돌아갔다는 뜻이니까.

알림은 두 단계를 권한다. 업무 레벨: 영업 시간 중 10분간 라벨이 하나도 생성되지 않으면 알림. 정상 운영에서 포장 스테이션이 10분간 라벨 한 장 안 만들 리는 없다. 발동하면 거의 확실히 API 다운이다. 정산 레벨: 일일 예외 건수가 5를 넘으면 사람에게 에스컬레이션. 임계값은 자사 물량에 맞게 조정하되, 임계값은 꼭 둬라. 임계값 없는 모니터링은 모니터링이 아니다.

예방 체크리스트: 이 다섯 가지를 하면 사라진 라벨은 기본적으로 없어진다

디버깅은 병을 고치고, 예방은 건강을 지킨다. 다섯 항목, 투자 대비 효과 순:

항목 할 일 비용
타임아웃 재시도 정책 타임아웃 시에만 재시도, 항상 멱등 키 포함. 최대 3회, 지수 백오프 개발 반나절
추적 번호 유니크 추적 번호 컬럼에 유니크 인덱스. 중복 쓰기는 시끄럽게 실패 몇 분
상태 머신 조이기 상태 건너뛰기 금지. 콜백 올 때까지 주문은 "creating"에 대기, 콜백 타임아웃 시 수동 검토로 에스컬레이션 1–2일
인쇄 확인 영수증 프린터 확인이 상태 전환을 이끈다. 영수증 없음 = "미인쇄" 인쇄 서버 협조 필요
일일 정산 잡 밤에 자동 비교, 예외 목록 메일 발송, 5건 초과 시 에스컬레이션 1일

또 하나: 완전한 요청/응답 로그를 최소 90일간 보관하라. 스토리지를 탓하지 마라. 사라진 라벨 분쟁이 책상에 떨어지면, 그 로그가 증거다.

마지막으로 진짜 질문 하나를 남긴다. 오늘 밤, 지난 한 달간 라벨 API를 두 번 이상 친 주문이 몇 건인지 확인해봐라. 그 숫자가 고스트 라벨 집단의 상한이다. 잠이 좀 안 올 수도 있다. 그래도 고객센터 전화에 새벽에 깨는 것보단 낫다.

영어판 번역본입니다. 내용이 다를 경우 영어판을 우선합니다.