Onboarding
이따금씩 스마트폰 SMS에 짬짝할인 쿠폰이 도착할 때마다 이용했던 컬리의 새벽배송서비스를 체험했을 때였습니다. 저녁즈음 결제한 상품이 자정을 갓 넘어서자마자 도착해 있는 것이 신기함을 넘어 미스터리하기까지 했습니다. 주문한 상품이 스토어에서 물류창고 그리고 택배사를 거쳐 주문자의 집 앞까지 도달하는 데엔 너무나 많은 내외적 변수가 도사리고 있을거라 생각했기 때문입니다.

'요즘 난다는 주요 커머스회사들의 물류시스템은 그 문제를 어떻게 해결했는지'가 궁금했습니다. 미스터리한 수준에 극단적인 품질을 경쟁력으로 삼는 요즘 커머스 기업의 물류도메인을 경험할 기회는 결고 쉽지 않는 일이기도 하지만 말입니다. 그래도 그 궁금증을 해소할 법한 도전정도는 해 볼 만하다고 생각했습니다.
관련 도메인 자산을 자체적으로 보유하고 있는 회사들 중에서도 최고라 하는 회사들만을 지원했고 아주 운이 좋게도 국내를 대표하는 패션 플랫폼기업에서 그 여정을 시작하게 되었습니다. 커머스 물류라는 이름의 망망대해에 그렇게 타점을 찍고 힘차게 노를 젖기 시작했습니다.
About Logistics
최근에서야 알게된 것이 물류라는 명칭은 신기하게도 군사용어에서 유래되었다는 사실입니다. 전시 중 매 긴장상태에 있는 군사들에게 어떻게 하면 힘을 덜 들이고 물자를 적재적소에 이동시켜, 전투를 효율적으로 수행할지 전략을 세우는 프랑스 병참용어인 Logistique 라는 용어가 지금에 물류학(=Logis + stics)로 이어져오고 있다고 합니다.

실제 물류는 입고와 출고라는 양 극단의 존재에서 시작합니다.
입고과정에선 어떤상품이 어디에 얼마나 전달되는지가 잘 관리되어야 합니다. 그렇게 입고된 상품은 광활한 물류창고 내부 어딘가에 어떻게 적치가 되고, 꺼내오게 할지가 중요하고요. 최종적으로는 이런 일련의 과정들이 그때마다 상황에 맞게 전략과 정책이 유연하게 적용되고 아무 문제 없이 실행될 수 있어야 합니다. 그 반대인 출고 역시 마찬가지이고요.

OMS라 불리는 주문관리시스템은 바로 그 역할과 책임을 갖습니다. 화주의 물자를 어떤 정책으로 물류창고에 입고시킬지, 그리고 출고를 할 때 물류창고에 있는 수많은 재고들 중 어떠한 전략을 통해 재고를 할당하고 출고를 시킬지에 대해 지시하고 전달하는, 화주(혹은 주문자)와 물류창고 사이에 일종의 수레와 같은 매개 역할을 담당합니다.
네모난 바퀴(?)가 장착된 수레. OMS

입사 당시 포지션은 새로운 WMS시스템을 구축하는 팀에 합류했었습니다. 얼마 지나지 않아 그때하고 있던 업무를 내려두고 OMS 팀으로 급파하게 되었는데 당시 OMS는 심각한 기술부채로 인해 매일 크고 작은 장애들이 무자비하게 발생되고 있던 상황이었습니다. 문제의 OMS를 구하기 위한 일환으로 전사적으로 PM을 비롯한 여러 인적리소스가 투입되었는데 그중 저도 그 험난할 것 같던 파도에 함께 올라탈 기회를 얻게 되었던 것입니다. 당시에 우리의 OMS가 가지고 있던 문제는 크게 다음과 같이 정의해 볼 수 있었습니다.
구조의 문제
당시에 OMS는 서비스를 담당하는 부분이 코드기반이 아닌 RDBMS에 저장된 SP(StoredProceduer)를 직접 호출하는 형태로 되어 있었습니다. 이 서비스 운영방식에서 오는 구조적 한계는 다음과 같습니다.
- 구조적 파편화
- 확장성의 한계
- 형상관리의 어려움
- 객체지향적이지 못한 설계
구조적 파편화

일반적인 3 Tier 구조에서 SP를 사용한다는 것은 각 Tire마다 부여된 역할과 책임이 파편화되거나 붕괴되기 취약합니다. 비즈니스로직이 DB에 강한 결합도를 갖는 구조이기 때문에 데이터드리븐의 경계가 모호해지면서 복잡도는 올라가고 그에 따른 유지보수의 비용도 커지게 됩니다.
확장성의 한계

거의 모든 비즈니스가 DB에 귀속됨에 따라 성능을 좌우하는 자원 역시 역시 DBMS에 의존하게 됩니다.
한꺼번에 많은 양의 트래픽 요청이 몰려왔을 때 서비스컨테이너 자원은 여유로운데 반해 DBMS의 CPU사용량은 요동을 치게 됩니다. 문제는 DBMS는 서비스 컨테이너와는 다르게 매우 비싼 자원일 뿐 아니라 유연한 선형적 확장이 어렵기 때문에 리소스운용에 비효율을 낳고 불필요한 비용을 지출할 수밖에 없었습니다.
형상관리의 어려움

비즈니스의 수정이 일어났을 때 코드가 아닌 DBMS에서 직접 접근하여 수정작업을 이루어지기에 버전관리가 어렵고 수정사항을 기록하고 과거에 내용을 추적해 가는 데에 어려움이 존재했습니다.
특히 서비스 코드레벨과 DB내부의 SP내부에서 수정사항이 동시에 일어났을 경우 두 부분의 변경점을 함께 인지하여 팀원들 간에 유지보수를 해 나아가야 하기에 자칫 그 지점에 장애가 발생하기라도 하면 디버깅을 하는데 애를 먹고, 그 이전상태로의 롤백의 과정 또한 복잡했습니다.
객체지향적이지 못한 설계

결과적으로 SP를 기반으로 하는 서비스개발은 객체지향적인 사고와 고민으로부터 멀어지게 하는 결과를 낳게 되기도 합니다.
처리구조의 관점의 주체가 객체가 아닌 처리대상이 되는 데이터가 되기 때문에 DB 스키마를 기준으로 순차적인 쿼링을 유도하는 방법론에 치우쳐 개발을 하게 되고 이는 곧 순도 높은 절차지향적인 사고에 갇힌 개발방법론에 빠르게 스며들게 합니다.
상황적 변화

OMS는 WMS 외에도 우리 시스템 내, 외부에 ERP, STORE, 3PL 등 다양한 시스템 간에 연동을 맺으며 연쇄적인 체인을 이루어가고 있었습니다. 점차적으로 서비스 트래픽 빈도수가 증가하기 시작하면서 그로 인한 긴급한 장애대응 횟수도 늘어나고 연동과정에서 성능의 품질뿐 아니라 데이터의 신뢰성과 정합성에 대한 중요성도 전보다 훨씬 더 커지게 되었습니다.
소통

빠르게 성장하는 우리의 비즈니스를 따라가기 위해 고군분투하던 당시 OMS는 오직 기술적인 부채만 후불청구서에 달아놓진 않았습니다. 이가 아닌 잇몸으로 버텨왔던 당시의 상황에서 시스템에 대한 문서화가 제대로 이루어져 있지 않아 해당 시스템의 선지자 위치에 있던 팀원과 이후에 합류한 팀원 간의 시스템, 서비스, 정책과 같은 히스토리를 얼라인하기까지의 과정이 녹록지 않았습니다.
Refactoring
문제를 해결하기 위해 많은 논의가 오갔습니다. 현재에 구조적 한계에서 오는 장애케이스들을 유형별로 정의하여 대응방안을 문서화하고 장애모니터 채널을 더욱 촘촘히 강화하였지만 이는 어디까지나 현상에 대한 대응일 뿐 근본적인 문제에 대한 해결은 아니었습니다.
우선적으로 DBMS에 거의 모든 걸 의존하고 있던 SP기반의 서비스 구조에서 관심사 별로 분리된 레이어드 아키텍처기반의 신규 프로젝트를 구축하여 서비스단위별로 점진적 전환을 착수하였습니다. 그 과정에서 서비스 성격에 따라 발생되어 오던 장애사례와 해결방안을 몇 가지 소개하자면 다음과 같습니다.
Usecase1. OMS → WMS 출고를 지시할 때

OMS는 오전, 어른오후, 늦은 오후, 저녁 이렇게 하루 4번씩 고정된 주기로 스토어로부터 완료된 주문 건들을 일괄로 수집해 옵니다.
이렇게 수집된 주문정보들은 재고할당, 배송지, 주소정제, 실적 표준화 및 데이터 정제과정을 거쳐 본격적으로 WMS에 출고지시를 요청합니다. 이러한 일련의 과정을 우리는 컷오프라고 부릅니다.
컷오프
특정된 시간까지 누적된 주문완료건을 수집하여 재고를 할당하고 상태를 갱신하며 출고지시 데이터를 만들어 출고지시를 내리는 과정
Issue

- 평균 컷오프당 주문데이터의 양은 평균 5-7천 건 많게는 1만 건 이상을 상회합니다.
- SP기반의 서비스구조 특성상 컷오프를 구성하는 각 단계별 비즈니스의 복잡도와 처리되는 양에 따라 처리비용이 커질수록 DBMS의 부하도 함께 치솟습니다.
- 급기야 교착상태(deadlock)에 무한정 빠지거나 처리도중 timeout이 발생하여 프로시저가 그대로 종료되어 버립니다.
- 이런 상황은 처리해야 하는 데이터 양이 클 경우 외에도 또는 DBMS의 리소스컨디션에 따라 불현듯 발생됩니다.
- 문제는 해당 장애가 발생될 경우 비즈니스임팩트가 중대할 뿐 아니라 발생 이후에 대사처리과정 또한 매우 민감하고 어려운 작업이었기에 이에 대한 개선이 시급히 요구되어 오던 상황이었습니다.
Improve

- 서비스 마이그레이션이 단행하는 과정에서 새로운 스프링배치프레임워크를 도입하였습니다.
- 컷오프를 구성하는 일련의 태스크들을 Job단위로 쪼개어 정의하고 각 Job 간에 의존성을 고려한 Flow를 새롭게 설계하였습니다.
- 또한 과거 일련의 순서를 기준으로 순차적인 흐름으로 처리되던 방식을 필요에 따라 병렬처리가 되도록 변경하였습니다.
- 이로써 아무리 많은 양의 데이터를 안정적이고 신뢰성 있게 처리가 가능해졌습니다.
- 동시다발적인 Job의 배치처리 또한 병렬로 구성하여 보다 처리되는 시간도 단축시킬 수 있었습니다.
- 무엇보다 반복, 재시도, Skip을 유연하게 조정가능하게 됨에 따라 장애가 발생되더라도 대사처리를 전보다 훨씬 더 쉽고 간편하게 할 수 있게 되었습니다.
Usecase2. OMS ← WMS 출고처리 결과를 받을 때

- 출고지시를 받은 주문 건들에 대해서는 WMS에서 일련의 과정을 거쳐 출고작업을 완료합니다.
- 출고가 완료되는 대로 그 결과내역을 OMS로 전달합니다.
- OMS는 WMS로 전달받은 출고처리결과를 아래와 같은 과정을 통해 최종적인 출고완료처리를 하게 됩니다.

Issue
- 앞서 출고지시와는 다르게 출고결과는 다양한 내, 외부 시스템 간 연계처리가 수반되고 상태에 대한 실시간성 보장이 요구됩니다.
- 각 태스크 간 긴밀한 의존성이 존재하고 이벤트의 밀도가 높은 만큼 처리과정이 길기 때문에 만약 장애가 발생했을 때 대사처리가 복잡하고 어려웠습니다.
- 출고지시와 마찬가지로 장애가 발생될 경우 비즈니스임팩트가 크고 그와 연계되는 시스템에 전파범위도 컸던 상황에 이를 극복할 기술적 해결방안이 필요했습니다.
Improve


- 기존에 고전적인 동기식방식으로 처리되는 구조를 버리고 Kafka의 이벤트 스트리밍을 새롭게 도입하였습니다.
- 수신된 출고처리결과를 순차적인 흐름에 따라 정의된 서비스를 호출하는 것이 아니라 Topic에 모아 처리할 수 있도록 중앙집중화된 형태로 설계하였습니다.
- 동일 목적의 데이터를 여러 파티션에 분배하고 데이터를 병렬로 처리가 가능하기 때문에 각각의 컨슈머를 이벤트별로 그룹핑하여 병렬로 처리되게끔 설계하였습니다. 이로써 동일 시간당 데이터처리량을 더욱 향상할 수 있게 되었습니다.
- 처리과정에서 오류가 발생했을 때에도 해당 수신데이터는 삭제되지 않고 파일시스템에 그대로 남아있기 때문에 쉽고 빠르게 재처리를 할 수 있게 되었습니다.
- kafka 클러스터 내부에 자체적인 장애가 발생되더라도 여러 대의 브로커에 저장된 Replica를 통해 복구가 가능하기 때문에 안전하고 지속적으로 데이터를 처리할 수 있게 되었습니다.
커머스 물류도메인에서 1년 반이라는 시간
조직 합류 초기엔 기대감과 약간에 두려움이 공존해 있었습니다.
직전의 직장에서 프런트엔드 개발 포지션에 있다 다시 백엔드 개발 포지션으로 회귀하는 시점인데다 물류 도메인에서 사용하는 생소한 언어들과 물류현장의 기본 배경지식 또한 제 스스로 빠르게 극복하고 체득해 가야 할 선결과제였으니까요.
그럼에도 요즘 이커머스기업의 물류도메인 개발조직은 뭐라도 좀 다르고 뭔가가 더 민첩하고 유연한 기술 생태계 안에서 일하는 우아한 일상일 것이라 상상했습니다. 물류프로덕트팀이라 쓰여있는 거대한 함선에 오르고 나니 그제야 보이고야 말았습니다. 먼발치서 밀려오는 파도와 순식간에 휘몰아치는 태풍, 하늘에서 난데없이 떨어지는 포탄과 같은 시스템 장애까지. 상상했던 우아한 삶이란 여지없이 무너지고. 깨닫고 보니 저의 군함은 맥아더장군님의 지휘아래 인천 앞바다로 상륙 중이고. 인생(?)은 실전이었습니다.
언제 터질게 될지 모르는 기술부채 잠식상태인 OMS를 구하기 위해 새로운 프로덕트 리더분들와 스마트한 개발진이 속속히 합류하면서 우리의 목표와 방향을 빠르게 설정하고 과감하게 빌드업해 나갔습니다.
문제를 해결하기 위한 속도는 전보다 훨씬 더 리드미컬하게 나아갔습니다. 문제의 본질에 딥다이브 하였고 오너쉽을 가지고 문제를 해결하는 법을 서서히 체득해 갔습니다. 혼자 해결할 수 있는 문제는 없다는 것을 기본전제로 두고 아주 사소한 것이라도 기어이 오버커뮤니케이션하면서 문제를 서로 간 얼라인하고 그 속에서 물꼬를 틔어갔습니다.

1년 반이라는 시간이 흐른 지금 시스템 내부에 군데군데 나있던 균열이 이젠 제법 견고하게 메꾸어졌습니다. 정체 모를 네모난 모양에 바퀴를 달고 덜컹덜컹 달리던 수레가 이제는 둥근 모양의 바퀴로 탈바꿈하면서 마침내 어떤 짐이든 안전하게 빠르게 싣고 나를 수 있게 되었습니다.

안될 것 같았던 문제를 해결하거나 극복했을 때 얻는 뿌듯한 감정은 좀처럼 잘 잊혀지지 않습니다. 그보다 더 값지고 귀하게 기억되는 건 결과에 다다르기까지 양 옆 동료들과 치열하게 호흡을 맞춰 해결해 갔던 과정 그 자체였기도 합니다. 내가 하고 일, 아니 우리가 하는 일은 코드를 작성하는 일이 아니라 궁극적으로 문제를 해결하는 일이라는 걸 새삼스레 깨닫습니다.
물류 도메인에서 1년 반이라는 시간은 그렇게 거침없이 흘러갔습니다. 그때에 치열했던 경험치를 지팡이 삼아 오늘도 역시 담대히 걸어 나아가고 있습니다. 처음이라 낮설었던 탓에 매사 1초 정도 느렸던 것과는 다르게 이젠 제법 1초 앞 정돈 내다보며 걸어갈 수 있는 시야도 확보해 가면서 말이죠. 선선한 바람이 불어오는 게 어느덧 가을이라는 것도 보게됩니다.
끝.
회고시점에 재직 중이던 회사에서의 여정을 마무리하고 2024년 9월부터 새로운 스타트업에서 여정길을 계속해서 이어가고 있습니다. 그때에 값진 경험과 여러 시행착오로 기록된 오답노트를 지도삼아 계속해서 좋은 개발자로 성장해가고자 합니다.