Skip to main content

Posts

주문 로직의 8번째 재개발: 내부 아키텍처의 표준화와 로직 충돌 해소

1. 배경 - 이미 존재하던 주문 구조 REINDEERS의 주문 구조는 처음부터 복합적이었다. 견적형 주문과 바로구매형 주문이 병존하며, 포워딩과 운송 일정을 포함하는 다단계 프로세스가 이미 내부 MCP 환경에서 운용되고 있었다. 다만, 이 로직은 외부로 공개되지 않았고, 실제 서비스보다는 내부 자동화-테스트 환경에 최적화되어 있었다. 8번째 리빌드는 새로운 시스템을 만든 것이 아니라, 내부적으로 작동하던 고도화된 주문 엔진을 외부 고객사가 사용하는 서비스 레벨로 정식 통합하고 검증하기 위한 작업이었다. 2015년 IMARKET Thailand 설립 이후 수년간 운영해온 주문 로직이 7번의 리빌드를 거치며 내부적으로 성숙해 있었지만, 4,300개 이상의 파트너가 직접 사용하는 서비스 레벨에서는 다른 수준의 안정성이 요구되었다. 2. 문제 인식 - 로직 충돌과 중복 이벤트 내부 MCP 버전의 주문 시스템은 기능적으로 완성되어 있었지만, 확장성 측면에서는 한계를 드러냈다. 여러 Cloud Function과 MQ Exchange가 동시에 동일 이벤트를 수신하면서, 이벤트 중복 처리, 상태 불일치, TTL 만료 전 복구 불가 등의 문제가 반복되었다. # 과거 문제 사례 (중복 처리) [EVENT] payment.confirmed received (Q1) [EVENT] payment.confirmed received (Q4) → PO double-issued (2개의 purchase order 생성) → Redis TTL mismatch → DO 전환 실패 특히 포워딩 스케줄 확정 로직과 결제 처리 로직이 동시에 MQ를 발행하면서 순서가 어긋나는 경우가 발생했다. 그 결과, PO가 생성되기 전에 DO가 먼저 발행되거나, 운송 일정이 존재하지 않는 상태에서 통관 요청이 들어오는 등 논리적 오류가 다수 발견되었다. 이 문제는 내부 운영 환경에서는 수동 개입으로 해결할 수 있었다. 하지만 바이어 2,500곳, 공급사 1,800곳, 포워딩 업체 ...

팀을 네 번 교체하며 배운 것들 — 진짜 글로벌 팀을 만들기까지

1. 첫 번째 팀 -- 외주 개발사의 한계 REINDEERS의 첫 번째 팀은 외부 개발사였다. 계약은 단순했고, 목표는 "무역 플랫폼의 MVP 구축"이었다. 그러나 현실은 달랐다. 개발사는 무역을 이해하지 못했고, 제조 유통의 흐름을 전자상거래처럼 처리하려 했다. 결과적으로 견적(Quote), 주문(Order), 물류(Logistics), 결제(Payment)가 같은 테이블에 얽혀 있었다. 개발은 빨랐지만, 구조는 위험했다. 첫 번째 팀은 "코드를 만들었지만, 플랫폼은 만들지 못했다." 무역에서는 견적 하나가 여러 개의 발주서로 분리되기도 하고, 하나의 발주가 복수의 선적으로 나뉘기도 한다. 결제 조건은 T/T, L/C, D/A 등 거래 상대와 품목에 따라 완전히 달라진다. 외주 개발사는 이 모든 것을 하나의 "주문-결제" 테이블로 처리하려 했다. 일반적인 쇼핑몰이라면 가능한 설계였지만, B2B 무역에서는 첫 주부터 문제가 드러났다. 가장 치명적이었던 것은 통화(Currency) 처리였다. 태국 바트로 견적을 내고, 중국 위안으로 결제하고, 한국 원화로 정산하는 구조를 외주팀은 전혀 고려하지 않았다. 환율 변동에 따른 마진 계산, 선적 시점과 결제 시점의 환차손 -- 이런 것들은 사양서에 한 줄로 적혀 있었지만, 실제로 구현하려면 완전히 다른 데이터 모델이 필요했다. 외주팀에게 이것은 "추가 요구사항"이었지만, 우리에게는 플랫폼의 본질이었다. 2. 두 번째 팀 -- 내부화의 첫 시도 두 번째 시도는 "내부 개발팀 구성"이었다. 외주가 실패한 이유를 외부 탓으로 돌릴 수 없다고 판단했기 때문이다. 관리 인력과 몇 명의 풀스택 개발자가 합류해, 직접 시스템을 만들기 시작했다. 하지만 문제는...

REINDEERS, 4년의 여정 — 동남아 제조업 혁신을 향한 출발점

1. 출발 -- 2015년, 태국에서 시작하다 2015년, IMARKET Thailand가 태국 방콕에서 설립되었다. 왜 태국이었는가. CEO 김명훈 대표가 20년 이상 동남아에서 사업을 해온 경험에서 나온 선택이었다. 태국은 ASEAN에서 제조업 기반이 탄탄한 국가 중 하나였고, 한국 기업의 진출이 활발했으며, MRO(유지보수/수리/운영) 산업재 유통에 명확한 수요가 있었다. 초기 사업 모델은 플랫폼이 아니었다. 태국 내 제조업체에 산업재를 공급하는 MRO 유통 사업이었다. 공장에서 필요한 베어링, 공구, 안전장비, 화학제품 등을 한국, 중국, 일본 공급사로부터 조달해서 태국 현지 공장에 납품했다. 전화, 이메일, 엑셀로 운영했다. 당시에는 그것이 업계 표준이었다. 2. 2016-2019: 고객 기반 구축, 시장의 현실 학습 4년간 태국 현지에서 직접 영업하고, 납품하고, 클레임을 처리하며 산업재 B2B 무역의 실상을 배웠다. 고객 수는 점진적으로 늘어 2,500개 이상의 바이어 관계가 형성되었다. 동시에 한국, 중국의 공급사 네트워크도 1,800개 이상으로 확장되었다. 이 숫자는 마케팅으로 모은 것이 아니라 실제 거래를 통해 쌓인 것이다. 한 건 한 건의 견적, PO, 인보이스, 납품을 거치며 신뢰가 만들어졌다. 이 시기에 배운 것들이 나중에 플랫폼 설계의 핵심이 되었다. 첫째, 동남아 산업재 무역의 비효율은 구조적이라는 것. 바이어가 공급사를 찾으려면 전시회에 가거나 지인을 통해야 했다. 가격 비교는 담당자가 공급사별로 전화를 돌려 엑셀에 정리했다. 물류 추적은 포워더에게 매번 전화하는 것이었다. 이 비효율은 개별 기업의 문제가 아니라 산업 전체의 문제였다. 둘째, 국가마다 규제와 관행이 완전히 다르다는 것....

베타 오픈 준비: 국가별 시뮬레이션과 부하 테스트 자동화

베타 오픈 준비: 국가별 시뮬레이션과 부하 테스트 자동화 1. 테스트 목적과 시나리오 설계 베타 오픈을 앞두고 가장 중요한 검증 항목은 세 가지였다. 1) 다국가 접속 부하 분산 — 각국 DNS 및 CDN 라우팅 검증 2) 데이터 일관성 — Redis와 MQ의 복제 지연 검증 3) 무중단 배포 및 장애 복구 — Cloud Function의 자율 회복 테스트 테스트는 AI Ops-Agent가 생성한 가상 세션을 이용해 수행됐다. 각국의 평균 사용 환경(네트워크 속도, 언어, 기기 비율)을 기반으로 50,000명의 가상 사용자를 생성하고, 실제 주문, 견적, 채팅, 결제 시나리오를 반복 실행했다. 시나리오는 단순 페이지 조회부터 복합 트랜잭션까지 5단계로 구성되었다. 1단계는 상품 검색과 카탈로그 조회, 2단계는 장바구니 추가와 견적 요청, 3단계는 결제 처리, 4단계는 주문 상태 변경과 MQ 이벤트 전파, 5단계는 배송 추적과 세션 유지 검증이다. 각 단계의 성공/실패 기준과 허용 응답 시간이 사전에 정의되었다. 2. 부하 테스트 방법론과 리전별 트래픽 분배 부하 테스트 도구로는 k6 기반의 스크립트를 사용했다. 각 리전별로 독립적인 테스트 러너가 배치되어, 해당 리전의 DNS를 통해 실제 서비스 경로로 트래픽을 발생시켰다. 트래픽은 Cloud Function에서 MQ, Redis, DB까지 실제 서비스 경로를 그대로 통과한다. { "country": "TH", "user_id": "TH_839210", "actions": ["login","view_product"...

AI 품질 보정과 데이터 재생산 파이프라인

AI 품질 보정과 데이터 재생산 파이프라인 1. Translator-Agent 2.0의 설계 목표 9월 초부터 수집되는 데이터의 양이 폭증하면서 AI 번역 품질이 일관되지 않다는 문제가 보고되었다. 평균 BLEU 점수는 0.82 수준이었지만 언어 간 편차가 컸고, 특정 기술 문서에서 용어가 반복적으로 오역되었다. Translator-Agent 2.0의 목적은 AI가 스스로 품질을 예측하고, 낮은 품질의 데이터를 재생산하도록 만드는 것이었다. BLEU, TER, Context Vector를 이용한 품질 점수화 자동 재번역 루프 (Re-Translation Loop) Quality-Driven Event Routing (품질 점수 기반 라우팅) 자동 승인 및 검증 리포트 생성 이 시스템에서 "품질 보정"이라 함은 AI가 생성한 결과물을 AI가 다시 검증하는 구조를 말한다. 사람이 모든 번역 결과를 하나씩 검토하는 것은 물리적으로 불가능하므로, AI가 1차 생성 후 별도의 검증 파이프라인을 거쳐 기준을 충족하지 못하는 데이터만 재처리하는 방식을 채택했다. 2. 품질 평가 메커니즘 Translator-Agent 2.0은 번역이 완료되면 즉시 BLEU와 TER을 계산하고, 품질 점수를 생성한다. 이 점수는 0~1 사이 실수값으로 표현되며, 0.75 미만이면 재번역 큐에 등록된다. BLEU는 의미 유사도, TER은 문장 수정 비율을 측정한다. 품질 점수는 Redis의 Sorted Set에 저장되어 우선순위 처리가 가능하다. score = (bleu * 0.7 + (1 - ter) * 0.3) redis.zadd("i18n.quality", {key: score}) if score ...

외부 데이터 크롤링과 다국어 처리 자동화

외부 데이터 크롤링과 다국어 처리 자동화 1. 배경 — 외부 연동 대신 크롤링 선택의 이유 REINDEERS 플랫폼은 각국의 표준, 인증, 관세 데이터를 기반으로 상품 정보를 구조화해야 했다. 그러나 각 기관의 공개 API는 존재하지 않거나 접근이 제한적이었다. 태국 TISI, 한국 UNIPASS, 중국 GB표준, 말레이시아 SIRIM 등은 모두 웹 기반 HTML 구조만 제공했다. 이에 따라 API 연동 대신 크롤링 구조를 도입했다. 크롤링은 인증/표준 데이터뿐 아니라 환율과 해운 스케줄에도 적용되었다. 환율은 4개 은행(방콕은행, 하나은행, 중국은행, 메이뱅크)에서 매일 수집되며, 해운 스케줄은 HMM, KMTC, SM Line 세 개 선사에서 크롤링된다. 이 모든 외부 데이터가 플랫폼의 가격 계산, 물류 일정, 인증 검증의 기초가 된다. 2. 국가별 스크래퍼 아키텍처 크롤러는 국가별로 독립적인 설정을 가진다. 각 크롤러는 Cloud Function에서 주기적으로 호출되며, 수집 주기는 데이터의 성격에 따라 다르다. 환율 데이터는 매일 1회, 해운 스케줄은 월 1회, 인증 데이터는 주 1회 수집된다. 스크래퍼는 계층 구조로 설계되어 있다. 최상위에 공통 인터페이스(Scraper Layer)가 있고, 그 아래에 국가별 구현이 존재한다. 공통 인터페이스는 HTTP 요청, 응답 파싱, 데이터 정규화, 에러 핸들링의 표준을 정의한다. 국가별 구현은 해당 사이트의 HTML 구조와 인증 방식에 맞춰 구체적인 파싱 로직을 담당한다. # Scraper architecture example class BaseScraper: def fetch(self, url): ... def parse(self, html):...

MQ + Redis 기반 글로벌 데이터 일관성 구조

MQ + Redis 기반 글로벌 데이터 일관성 구조 1. 문제 인식 — 다국가 데이터 일관성의 복잡성 8월 초, 홍콩(HK), 서울(KR), 쿠알라룸푸르(MY), 방콕(TH)의 네 개 리전이 동시에 서비스되면서 가장 큰 문제는 데이터 일관성이었다. 각 리전의 Redis 캐시와 MySQL 데이터베이스가 서로 다른 타임스탬프로 업데이트되어, 동일 상품의 재고 수량, 세션, 장바구니 데이터가 불일치하는 현상이 자주 발생했다. 기존 복제(Replication) 방식은 지연이 300~800ms 수준으로 불안정했고, Redis async replication은 트래픽 피크 시 복제 손실이 발생했다. 이 문제는 단순 데이터 복제가 아니라 이벤트 레벨에서의 보정(Event-level Consistency) 으로 접근해야 했다. 특히 B2B 무역 플랫폼에서는 재고 데이터의 불일치가 실제 주문 오류로 직결된다. 한 리전에서 재고가 0인 상품이 다른 리전에서는 재고가 있는 것으로 표시되면, 주문이 접수된 후 취소되는 상황이 발생한다. 이는 공급사와 바이어 양쪽 모두에게 신뢰 문제를 만든다. 따라서 데이터 일관성은 단순 기술 과제가 아니라 비즈니스 신뢰도의 문제였다. 2. MQ 토폴로지 설계 — Topic Exchange와 Routing Key 우리는 Redis 복제 대신 MQ 기반의 "Event-driven Sync" 구조로 전환했다. LavinMQ의 Topic Exchange를 사용하여 이벤트를 라우팅한다. 각 리전의 데이터 변경 이벤트는 data.sync 토픽으로 발행되며, routing key는 data.sync.{source_region}.{data_type} 형식을 따른다. # Routing key examp...