디지털 포워딩 자동 트래킹 고도화프로젝트_Part1

페인 포인트를 기반으로 로직 개편 및 사용자 경험 개선

by RICE
41410.jpg

"고객님, 지금 화물이 어디 있는지 모르겠어요"

"PM님, 또 VOC 들어왔어요. 화물이 어디 있는지 모르겠대요."

2024년 10월, 나는 운영팀으로부터 이런 연락을 한 달에 평균 11~12건씩 받고 있었다. 트래킹 기능은 분명히 작동하고 있었다. 화물의 위치 정보도 실시간으로 업데이트되고 있었다. 그런데 왜 화주들은 화물이 어디 있는지 모른다고 하는 걸까?

직접 화주사의 대시보드를 열어봤다. 그리고 이해했다.

화면에는 "CEP", "CPS", "CGI", "CLL" 같은 이벤트 코드들이 나열되어 있었다. 각 컨테이너마다 상태가 다르게 표시됐고, 어떤 건 "공 컨테이너 반출"이라고 쓰여있고, 어떤 건 "현지 화물 픽업 완료"라고 되어 있었다. 운송 현황은 "shipping"으로 표시되어 있었다.

"이게 대체 뭔 소린지 일반인이 어떻게 알아요?" 나도 PM인데 헷갈렸다.

문제는 명확했다. 우리는 시스템 중심으로 정보를 보여주고 있었다. 하지만 화주가 원하는 건 "내 화물이 지금 어디에 있고, 언제 도착하는가"라는 단순한 답이었다.

이것이 디지털 포워딩 서비스 트래킹 고도화 프로젝트가 시작된 배경이었다. 2024년 10월부터 2025년 3월까지 진행된 이 프로젝트는, 외부 데이터 프로바이더에서 유입되는 복수 이벤트 흐름을 분석하여 사용자 입장에서 운송 현황을 한눈에 파악할 수 있도록 UI와 DB 구조를 전면 개선하는 작업이었다.


문제의 본질: 데이터는 많은데 정보는 없다


트래킹 기능의 가장 큰 문제는 '데이터'와 '정보'를 구분하지 못했다는 거였다. 시스템은 엄청나게 많은 데이터를 수집하고 있었다.

수집 중인 데이터 소스:


Searates (해상 운송 추적 서비스)

Unipass (관세청 통관 시스템)

선사 직접 연동 데이터

AIS 연동 데이터

내부 운영 데이터


각 소스에서 들어오는 이벤트만 30개 이상이었다. CEP(공 컨테이너 반출), CPS(화물 픽업 완료), CGI(터미널 반입), CLL(선적 완료), VDL(출항), VAT(환적지 입항), VDT(환적지 출항), VAD(최종 입항), CDD(컨테이너 반입), CGO(컨테이너 반출), CER(컨테이너 반납)...

"이거 다 보여줘야 하나요?" 디자이너가 물었다. "아니요, 사용자는 이런 거 모르거든요." 내가 답했다.

"그럼 뭘 보여줘야 해요?"

이게 핵심 질문이었다. 30개의 이벤트 중에서 화주에게 의미 있는 정보는 무엇인가?


사용자 인터뷰: 진짜 궁금한 건 뭐야?

문제를 제대로 정의하기 위해 화주 5개사를 대상으로 인터뷰를 진행했다. 질문은 간단했다.

"화물을 추적할 때 가장 궁금한 게 뭔가요?"

화주A (전자제품 수입사): "언제 도착하는지만 알면 돼요. 세부적인 이벤트는 관심 없어요."
화주B (의류 수입사): "통관이 언제 끝나는지 알아야 해요. 그래야 매장에 배송 일정을 잡으니까요."
화주C (식품 수입사): "우리는 유통기한이 있어서, 항구에 도착하면 바로 알림 받아야 해요."
화주D (기계 부품 수출사): "선적이 잘 됐는지, 배가 제시간에 출발했는지가 중요해요."
화주E (원자재 수입사): "컨테이너가 4개인데, 하나만 늦게 도착하면 곤란해요. 전체가 언제 다 모이는지 알아야 해요."

인터뷰 결과, 화주들이 진짜 궁금한 건 세 가지였다:


지금 어디: 화물이 현재 어느 단계에 있는가

언제 도착: 예상 도착일이 언제인가

문제 없나: 지연이나 이슈가 있는가


나머지는 부가 정보였다. CEP니 CPS니 하는 이벤트 코드는 화주에겐 의미가 없었다.


사용자 여정 재설계: 시스템이 아닌 사람 중심으로


인터뷰 결과를 바탕으로, 운송 프로세스를 화주 관점에서 재정의했다.


기존 구조의 문제점

기존 트래킹은 이벤트 중심으로 설계되어 있었다. 1개 운송에 N개 컨테이너가 있고, 각 컨테이너마다 개별 이벤트가 발생한다. 문제는 컨테이너별 상태가 제각각이라는 거였다.

예를 들어 4개 컨테이너를 운송하는 경우:


컨테이너 A: 선적 완료 (CLL)

컨테이너 B: 선적 완료 (CLL)

컨테이너 C: 터미널 반입 완료 (CGI)

컨테이너 D: 화물 픽업 완료 (CPS)


이 상황에서 "운송 상태"는 뭐라고 표시해야 할까? 기존 시스템은 이걸 제대로 처리하지 못했다. 그냥 가장 최근 이벤트를 보여주거나, 아니면 모든 컨테이너 상태를 나열했다.

화주 입장에서는 혼란스러울 수밖에 없었다. "선적 완료인데 왜 터미널 반입 완료도 보이지?"


새로운 규칙: 모든 컨테이너 기준

문제 해결의 핵심은 간단했다. 운송 상태는 모든 컨테이너가 통과한 시점을 기준으로 변경한다.

4개 컨테이너 중 3개가 선적 완료(CLL)이고 1개가 터미널 반입(CGI)이면, 운송 상태는 여전히 "터미널 반입 중"이다. 나머지 1개가 선적 완료되어야 비로소 운송 상태가 "선적 완료"로 바뀐다.

스크린샷 2025-12-28 오후 3.12.36.png

이 규칙은 화주 입장에서 직관적이다. "아직 한 개가 안 끝났구나"를 바로 이해할 수 있다. 그리고 운영 관점에서도 명확하다. 모든 컨테이너가 특정 단계를 통과해야 다음 단계로 넘어간다.


사용자 친화적 상태명 재정의

두 번째 변화는 상태명을 화주가 이해할 수 있는 말로 바꾸는 것이었다.

기존 (시스템 용어):

CEP: 공 컨테이너 반출

CPS: 현지 화물 픽업 완료

CGI: 터미널 반입 완료

CLL: 선적 완료

VDL: 출항 완료

VAD: 최종 입항 완료

CDD: 컨테이너 반입


개선 (사용자 언어):

화물 준비 중

선적 준비 중

선적 완료

운송 중

입항 완료

통관 준비 중

통관 진행 중

배송 준비 중


"공 컨테이너 반출"이 뭔지 아는 화주는 거의 없다. 하지만 "화물 준비 중"은 누구나 이해한다. "컨테이너 반입(CDD)"보다는 "통관 준비 중"이 훨씬 와닿는다.

단순히 번역이 아니라, 화주의 업무 맥락에 맞는 표현으로 바꾼 거다.


UI 재설계: 한눈에 보이는 운송 현황


상태 정의가 끝나고, 이제 이걸 어떻게 보여줄지 고민할 차례였다.

기존 UI의 문제점

기존 트래킹 화면은 이랬다:

상단에 운송 상태 (shipping, portEntry 같은 코드)

중간에 컨테이너별 상태 테이블 (각각 다른 이벤트 코드)

하단에 이벤트 이력 (시간순으로 나열된 30개 이벤트)


화주들은 이 화면을 보고 스크롤을 내리다가, CS팀에 전화를 걸었다. "이거 무슨 뜻이에요?"

디자이너와 함께 스케치를 그리면서 고민했다. 어떻게 하면 복잡한 정보를 단순하게 보여줄 수 있을까?

타임라인 형태의 진행 상태

첫 번째 아이디어는 타임라인이었다. 운송 프로세스를 시각적 여정으로 보여주는 거다.

스크린샷 2025-12-28 오후 3.12.57.png

완료된 단계: 초록색

현재 단계: 진한 초록색 + 애니메이션

예정 단계: 회색


화주는 한눈에 "아, 지금 운송 중이구나. 입항까지 3단계 남았네"를 파악할 수 있다.


핵심 정보 우선 배치

타임라인 위에는 화주가 가장 궁금해하는 정보를 크게 배치했다.

상단 카드:

현재 상태: "운송 중" (큰 글씨)

예상 도착일: "2024년 11월 15일 예정" (다음으로 큰 글씨)

진행률: "60% 완료" (프로그레스 바)


알림 배너 (이슈가 있는 경우만):

"스케줄이 2일 지연되었습니다"

"통관 서류가 추가로 필요합니다"


중요한 건, 문제가 없으면 이 배너는 보이지 않는다는 거다. 화주는 "아무 메시지 없으면 정상"이라고 이해한다.


컨테이너 상세는 접을 수 있게

화주 중에는 컨테이너별 상세 정보가 필요한 경우도 있다. 특히 컨테이너가 여러 개일 때, 일부만 지연되는 경우를 확인하고 싶어 한다.

하지만 모든 화주가 이 정보를 원하는 건 아니다. 대부분은 전체 운송 상태만 알면 충분하다.

그래서 컨테이너 상세는 "접을 수 있는 섹션"으로 만들었다. 기본은 접혀있고, "상세 보기" 버튼을 누르면 펼쳐진다.

컨테이너 상세 정보:

컨테이너 번호

현재 상태

최근 이벤트 시간

예상 반입 시간 (입항 후)


이 정보는 컨테이너별로 다를 수 있지만, 일반 화주는 굳이 볼 필요가 없다. 필요한 사람만 펼쳐서 확인하면 된다.


스케줄 정보의 재구성


운송 현황 다음으로 화주가 자주 확인하는 건 스케줄이다. "언제 출발하고 언제 도착하는가."


기존 스케줄 표시의 문제

기존에는 ETD(예상 출항일), ETA(예상 입항일), ATD(실제 출항일), ATA(실제 입항일)를 모두 나열했다. 환적이 있으면 환적지의 ETD/ETA도 추가됐다.

문제는 이게 너무 복잡하다는 거였다. ETD와 ATD의 차이를 아는 화주가 몇이나 될까? 그리고 출항 전에는 ATD가 없으니 빈칸으로 표시되는데, 화주는 이게 에러인지 정상인지 헷갈려 한다.


동적 스케줄 표시

새로운 방식은 운송 상태에 따라 표시 내용을 바꾸는 거였다.

출항 전:

출발 예정: 2024년 11월 1일

도착 예정: 2024년 11월 15일


출항 후:

출발 완료: 2024년 11월 1일 (예정대로)

도착 예정: 2024년 11월 15일


입항 후:

출발 완료: 2024년 11월 1일

도착 완료: 2024년 11월 14일 (1일 빠름!)


스크린샷 2025-12-28 오후 3.13.25.png


핵심은 화주에게 의미 있는 정보만 보여주는 거다. 출항하지 않았으면 ATD는 의미가 없다. 입항했으면 ETA는 과거 정보다.

그리고 예정 대비 차이를 명확히 표시한다. "1일 지연" "2일 빠름" 같은 표현으로 화주가 상황을 바로 파악할 수 있게.


환적지 정보의 간소화

환적 운송의 경우, 중간에 다른 항구를 거쳐간다. 부산 → 싱가포르 → 로테르담 같은 식으로.

기존에는 각 구간의 ETD/ETA를 모두 나열했다. 그런데 화주는 중간 환적지의 날짜에 큰 관심이 없다. 최종 도착일만 중요하다.

그래서 환적 정보는 접을 수 있게 만들었다. 기본 화면에는:


주요 일정:

출발: 2024년 11월 1일 (부산)

도착 예정: 2024년 11월 20일 (로테르담)


"환적 상세 보기"를 누르면:

상세 경로:

부산 출발: 2024년 11월 1일

싱가포르 입항: 2024년 11월 8일

싱가포르 출항: 2024년 11월 10일

로테르담 입항: 2024년 11월 20일


일반 화주는 상세까지 볼 필요가 없고, 궁금한 사람만 펼쳐서 확인하면 된다.


데이터 구조 재설계

UI를 바꾸려면 데이터 구조도 함께 바뀌어야 했다. 기존 데이터베이스 구조는 이벤트 중심이었다.


기존 DB 구조의 문제점

기존 테이블 구조:

TransportHistories: 운송 이력 (선박 입출항 정보)

ContainerHistories: 컨테이너 이력 (개별 이벤트)

BidsEvents: 의뢰 관련 이벤트

ActivityLogs: 운영 활동 로그


문제는 이 테이블들이 서로 다른 목적으로 설계되어서, 사용자 화면에 필요한 정보를 조회하려면 여러 테이블을 조인해야 했다는 거다.

-- 기존 조회 쿼리 (의사코드) SELECT 운송상태,
(SELECT MAX(이벤트시간) FROM ContainerHistories WHERE ...) AS 최근이벤트,
(SELECT COUNT(*) FROM ContainerHistories WHERE 상태 = 'CLL') AS 선적완료수,
... FROM Bids JOIN TransportHistories
... JOIN ContainerHistories
... WHERE ...

쿼리가 복잡하고 느렸다. 그리고 비즈니스 로직(모든 컨테이너가 통과해야 상태 변경)이 애플리케이션 코드에 흩어져 있어서, 버그가 자주 발생했다.


새로운 상태 관리 구조


해결책은 "운송 상태"와 "컨테이너 상태"를 명확히 분리하고, 상태 변경 로직을 한곳에 집중하는 것이었다.

새 테이블 추가:

TransportStatus: 운송 전체 상태 (화주가 보는 상태)

ContainerStatus: 컨테이너 개별 상태


TransportStatus 테이블:

bid_id: 의뢰 ID

current_stage: 현재 단계 (화물준비, 선적준비, 운송중 등)

progress_percentage: 진행률 (0~100)

latest_event_time: 최근 이벤트 시간

all_containers_ready: 모든 컨테이너 준비 완료 여부

updated_at: 마지막 업데이트 시간


이제 사용자 화면 조회는 간단해졌다:

SELECT current_stage, progress_percentage, latest_event_time FROM TransportStatus WHERE bid_id = ?

상태 변경 로직은 백엔드의 한 함수에 집중됐다:

function updateTransportStatus(bidId)
{ // 1. 모든 컨테이너 상태 조회 const containers = getContainerStatuses(bidId);
// 2. 가장 느린 컨테이너 기준으로 운송 상태 결정 const slowestStage = Math.min(...containers.map(c => c.stage));
// 3. 진행률 계산 const progress = calculateProgress(slowestStage);
// 4. TransportStatus 업데이트 updateTransportStatus(bidId,
{ current_stage: slowestStage, progress_percentage: progress,
all_containers_ready: containers.every(c => c.ready),
latest_event_time: Math.max(...containers.map(c => c.updated_at))
}); }

이벤트 핸들링 재설계


데이터 구조를 바꾸고 나니, 이벤트 처리 로직도 전면 수정이 필요했다.


기존 이벤트 처리의 문제

기존에는 Searates에서 이벤트가 들어올 때마다, 해당 이벤트를 바로 데이터베이스에 저장하고 화면에 반영했다. 문제는 이벤트가 순서대로 오지 않는다는 거였다.

예를 들어:

11월 1일 출항 이벤트(VDL) 수신

11월 3일 선적 완료 이벤트(CLL) 수신 (늦게 도착)

화면에는 "출항 완료"로 표시되었다가 갑자기 "선적 완료"로 바뀜


화주 입장에서는 "배가 이미 떠났는데 왜 선적 중으로 바뀌지?"라고 혼란스럽다.


이벤트 검증 레이어 추가

새로운 구조에서는 이벤트를 받으면 바로 반영하지 않는다. 검증 단계를 거친다.

스크린샷 2025-12-28 오후 3.13.48.png

검증 규칙:

이벤트 시간이 현재 상태의 시간보다 과거면 경고

이벤트 순서가 논리적으로 맞는지 확인 (선적 전에 출항 불가)

중복 이벤트는 무시

순서가 안 맞으면 대기 큐에 저장, 이전 이벤트가 오길 기다림


이 로직 덕분에 이벤트가 순서 없이 와도, 화주 화면에는 항상 논리적으로 올바른 상태가 표시된다.


실시간 업데이트 vs 배치 처리

모든 이벤트를 실시간으로 처리하면 서버 부하가 크다. Searates는 30분마다 데이터를 갱신하는데, 운송 건수가 많으면 한 번에 수백 개 이벤트가 들어온다.

그래서 업데이트를 두 가지로 나눴다:

실시간 업데이트 (중요 이벤트):

출항 완료 (VDL)

입항 완료 (VAD)

통관 시작


이 이벤트들은 즉시 처리하고, 화주에게 알림을 보낸다.

배치 처리 (일반 이벤트):

컨테이너 개별 상태 (CEP, CPS, CGI 등)

환적지 입출항

스케줄 변경


이 이벤트들은 5분마다 배치로 모아서 처리한다. 화주 화면에는 큰 차이가 없고, 서버 부하는 크게 줄어든다.


실제 구현 중 마주한 함정들


설계는 완벽해 보였다. 하지만 실제 구현하면서 예상치 못한 문제들이 속출했다.


함정 1: Searates 데이터가 사라진다?

어느 날, 운영팀에서 황당한 제보가 들어왔다.

"PM님, 어제까지 선적 완료였던 화물이 오늘 다시 선적 준비 중으로 바뀌었어요."

로그를 확인해보니, Searates에서 데이터를 갱신할 때 이전에 있던 CLL 이벤

트를 빼고 보냈다. 우리 시스템은 "Searates에 없으면 발생하지 않은 이벤트"로 판단해서 상태를 롤백한 거다.

"이거 버그 아니에요?" 개발자가 항의했다. "Searates 쪽에 확인해봐야 할 것 같아요." 나는 답했다.

결론: Searates는 때때로 확정되지 않은 이벤트를 제거한다. 선사가 스케줄을 변경하면, 기존 이벤트가 무효가 되는 경우도 있다.

해결책:

한번 실제 발생한 이벤트(is_actual = true)는 절대 삭제하지 않는다

Searates에서 이벤트가 사라져도, 우리 DB에는 보관

대신 "확정되지 않음" 플래그를 추가해서, 화주 화면에는 회색으로 표시


함정 2: 환적지 정보가 갑자기 바뀐다

수출 의뢰 건 중 하나가 이상했다. 부산에서 LA로 가는 직항인 줄 알았는데, 갑자기 싱가포르 환적 정보가 나타났다.

"선사가 노선을 바꿨나요?" 내가 물었다. "아니요, 원래 환적이었는데 Searates가 처음엔 안 줬어요." 운영자가 답했다.

Searates는 선사로부터 데이터를 받는데, 선사도 처음엔 환적 정보를 확정하지 않는 경우가 있다. 그러다 출항 직전에 환적지를 확정하면, Searates 데이터에 갑자기 환적 정보가 추가된다.

해결책:

스케줄이 변경되면 화주에게 알림 발송

"환적지 추가" 이력을 별도로 기록

예상 도착일이 바뀌면 큰 글씨로 강조


함정 3: 컨테이너 번호가 중복된다

정말 황당한 케이스였다. 서로 다른 의뢰 건인데, 같은 컨테이너 번호를 사용하고 있었다.

"이거 데이터 오류 아니에요?" 개발자가 물었다. "아니요, 실제로 가능해요." 운영팀이 답했다.

알고 보니 컨테이너는 재사용된다. A 화주의 화물을 부산에서 LA로 운송하고, 빈 컨테이너를 다시 LA에서 뉴욕으로 이동시키는데, 그 컨테이너를 B 화주가 쓰는 거다.

우리 시스템은 컨테이너 번호를 unique key로 사용하고 있어서, 충돌이 발생했다.

해결책:

컨테이너 ID를 의뢰ID + 컨테이너번호의 조합으로 변경

같은 컨테이너 번호라도 의뢰가 다르면 별개로 관리

Searates에서 잘못된 컨테이너 정보를 주는 경우 감지


그래서 결과는?

2025년 3월, 5개월간의 개발과 테스트를 거쳐 새로운 트래킹 UI가 배포되었다.

주요 성과:

트래킹 관련 VOC: 월 평균 11~12건 → 3건 (약 75% 감소)

화주 만족도: 사용자 인터뷰에서 "훨씬 보기 편해졌다"는 긍정적 피드백

운영 효율: 자동 트래킹 오류로 인한 수동 개입 60% 감소

CS 응대 시간: 평균 15분 → 5분 (트래킹 설명 시간 단축)


하지만 숫자보다 의미 있었던 건, 화주들의 반응이었다.

"PM님, 이제 화물 어디 있는지 바로 알 수 있어요. 예전엔 전화해서 물어봐야 했는데."

"지연되면 바로 알림 와서 좋아요. 미리 대응할 수 있어서 도움이 많이 돼요."

5개월 전 CS팀으로부터 받던 "화물이 어디 있는지 모르겠다"는 전화는, 이제 거의 오지 않는다.


배운 것들


이 프로젝트를 통해 배운 교훈들:

1. 데이터가 많다고 정보가 되는 건 아니다

30개의 이벤트를 모두 보여주는 게 친절한 게 아니다. 사용자에게 의미 있는 정보만 추출해서 보여주는 게 진짜 UX다.


2. 사용자 언어로 말해야 한다

CEP, CPS, CGI 같은 시스템 코드는 개발자만 이해한다. 화주는 "화물 준비 중", "운송 중" 같은 일상 언어를 원한다.


3. 외부 데이터는 절대 믿으면 안 된다

Searates도, Unipass도, 완벽하지 않다. 데이터가 늦게 오고, 순서가 바뀌고, 때로는 사라진다. 항상 검증하고, 예외 케이스를 대비해야 한다.


4. 상태 관리는 신중하게 설계해야 한다

"모든 컨테이너가 통과해야 상태 변경" 같은 규칙은 처음부터 명확히 정의하고, 한곳에 집중해서 관리해야 한다. 흩어져 있으면 버그의 온상이 된다.


5. 점진적 개선이 답이다

한 번에 모든 걸 완벽하게 만들 수 없다. 핵심 기능부터 빠르게 배포하고, 사용자 피드백을 받아 개선하는 게 더 효과적이다.


다음 편 예고


사용자 화면 고도화는 끝났지만, 이야기의 절반만 끝난 거다. 화주가 보는 화면을 개선했다면, 이제 운영자가 사용하는 어드민도 개선해야 한다.

다음 편에서는 백오피스 트래킹 관리 기능을 어떻게 재설계했는지, 운영자의 수동 개입을 어떻게 줄였는지 다룰 예정이다. 특히 자동 갱신 기능, 이벤트 수동 조작, 예외 케이스 처리 같은 운영 효율화 이야기를 들려주겠다.

질문 있나?

복잡한 데이터를 사용자 친화적으로 보여주는 경험이 있다면 공유해달라. 특히 B2B 서비스에서 전문 용어를 일반인이 이해할 수 있는 언어로 바꾼 사례가 있다면 정말 궁금하다!



본 글은 실제 프로젝트 경험을 바탕으로 작성되었으며, 회사의 보안 정책에 따라 일부 내용은 각색되었다.

작가의 이전글적하목록 신고 자동화