LY Corporation Tech Blog

LY Corporation과 LY Corporation Group(LINE Plus, LINE Taiwan and LINE Vietnam)의 기술과 개발 문화를 알립니다.

기획서 없이 내재화하기: 검증 로직으로 동일함을 증명하다

들어가며

안녕하세요. LINE Plus에서 Global E-Commerce 개발을 맡고 있는 장효택입니다.

기존 시스템을 새로운 환경으로 옮기거나 내재화하는 작업은 개발자에게 숙명과도 같습니다. 이때 가장 곤혹스러운 순간은 기존 로직의 근거가 되는 기획서가 없거나, 소스 코드조차 참조할 수 없는 블랙박스 상태일 때입니다.

저희는 외부 시스템에 의존하던 다양한 모듈을 내재화하는 과정에서 이 문제에 직면했습니다. ‘지금 우리가 만든 코드가 기존과 정말 동일하게 작동하는가?’라는 질문에 답하기 위해, 단순히 코드를 작성하는 것을 넘어 동일함을 증명할 수 있는 시스템을 구축해야 했습니다.

본 글에서는 사양서 한 장 없는 막막한 상황 속에서, Kafka 생태계를 적극 활용해 어떻게 검증 체계를 세우고 안정적으로 시스템을 전환했는지 그 여정을 공유하고자 합니다.

도메인 이해: 통합 커머스 검색의 핵심은 상품, 카탈로그, 수신

본격적인 검증 이야기를 시작하기 전에 서비스의 근간이 되는 세 가지 개념인 상품과 카탈로그, 수신이 무엇인지 짚고 넘어가려 합니다. 

먼저 ‘상품’은 판매자가 등록한 개별 아이템의 고유 정보입니다. 예를 들어, 여러 판매자가 각기 다른 가격과 배송 조건으로 ‘iPhone 17’을 팔고 있다면 모두 독립적인 개별 상품이 됩니다.

‘카탈로그’는 이러한 수많은 상품 중에서 동일한 모델들을 하나로 묶어 사용자에게 최적의 정보를 제공하는 상위 객체입니다. 카탈로그는 단순히 상품을 묶는 것을 넘어 가공된 가치를 전달합니다. iPhone 17이라는 카탈로그 아래에 여러 판매자의 상품을 모아 실시간 최저가 정보를 제공할 수도 있고, 생수나 음료 같은 상품군을 모은 카탈로그에서는 '100ml당 가격'과 같은 객단가 지표를 산출해 보여주기도 합니다.

아래는 실제 서비스 화면을 캡처한 것으로 수많은 상품이 하나의 카탈로그로 묶여 사용자에게 노출되는 모습을 보여줍니다.

카탈로그 예시

이 모든 데이터의 시작점에는 ‘수신’이라는 거대한 파이프라인이 존재합니다. 수신은 수많은 판매자가 보내오는 대규모 상품 데이터를 저희 시스템으로 가져오는 첫 번째 관문입니다. 각기 다른 형식의 파일을 다운로드해 데이터 정합성을 검사하고 저희 내부 표준 형식으로 변환해 최종적으로 상품과 카탈로그 정보에 반영하는 일련의 긴 여정을 포함합니다.

수천만 건의 카탈로그와 수억 건의 상품이 유기적으로 연결되어 글로벌 사용자에게 노출되는 만큼 데이터 하나가 서비스 전체에 미치는 영향력은 막대합니다. 이에 따라 복잡한 데이터 관계와 정교한 계산 로직을 놓치지 않고 안전하게 새로운 시스템으로 옮기는 것이 내재화의 본질이라고 할 수 있습니다.

검증 시스템의 뼈대: 입력과 출력을 명확히 정의한 검증 루프 만들기

검증 시스템의 목적은 단순히 틀린 부분을 찾는 것이 아닙니다. 개발자가 무엇이 왜 틀렸는지를 즉시 파악하고 수정할 수 있도록 지원하고, 최종적으로 내재화 전후 차이가 0에 수렴하도록 만드는 환경을 구축하는 것입니다.

저희가 이번 내재화 과정에서 비교해야 할 대상은 수천만 건의 카탈로그와 수억 건의 상품, 그리고 하루에도 수백 번씩 실행되는 수신 스케줄로 그 규모가 거대했습니다. 따라서 사람이 일일이 수동으로 검증하는 것은 불가능했고, 이는 자동화한 검증 파이프라인을 도입하는 결정적 배경이 되었습니다.

데이터 스트림을 활용한 무중단 검증 루프

저희는 거대한 데이터를 안정적으로 처리하며 개발자에게 실시간으로 피드백을 주기 위해 다음과 같은 유기적인 파이프라인을 설계했습니다. 파이프라인의 전체 아키텍처는 다음과 같습니다. 데이터 발생부터 개발자 액션까지 하나의 거대한 루프로 이어집니다.

파이프라인 전체 아키텍처

  1. 트리거(입력): DB 변경이나 개발자 요청 혹은 특정 시점의 파일 수신이 검증의 시작점입니다. 기존 시스템의 흐름을 방해하지 않도록 Kafka를 통해 비동기로 이벤트를 전파합니다.
  2. 실행 및 비교(프로세스): 검증 모듈은 동일한 입력을 기존과 신규 시스템 양쪽에 주입합니다. 그러면 각 서비스의 특성(조회, 업데이트, E2E)에 맞는 최적화된 비교 로직이 작동하며 정합성을 판단합니다.
  3. 가공 및 적재(출력): 비교 결과 중 '불일치' 데이터는 실시간 통계로 변환해 알림으로 전송하거나 별도 저장소에 상세 내역을 적재합니다.
  4. 분석 및 개선(액션): 개발자는 대시보드나 Slack 알림을 통해 실시간 정합성 추이를 확인합니다. 확인 후 오류 패턴을 분석해 로직을 수정하고 다시 1번으로 돌아가 차이가 사라졌는지 확인합니다.

검증의 핵심: 입력과 출력을 명확하게 정의하기

어떤 검증이든 결국 핵심은 ‘무엇을 넣고 무엇을 비교할 것인가’를 정의하는 데 있습니다. 저희는 다음과 같이 입력과 출력을 정의했습니다.

  • 입력: 양쪽 시스템이 동일한 상태에서 시작할 수 있도록 보장하는 값(예: 동일한 ID, 동일한 시간대 스냅숏, 동일한 상품 파일)
  • 출력: 시스템 성격에 따라 달라지는 최종 산출물(예: API 응답 객체, DB 업데이트 결과 값, 최종 등록된 상품 데이터)

입력과 출력, 이 둘을 명확히 연결할 수 있다면 내부 로직이 아무리 복잡한 블랙박스라도 시스템이 동일하다는 것을 통계로 증명할 수 있습니다. 검증 과정을 반복해 불일치 건수를 0에 수렴시키는 순간 비로소 자신 있게 '내재화 완료'를 선언할 수 있습니다.

검증 시스템의 뼈대를 살펴봤으니 이제 실제로 이 뼈대를 어떻게 적용했는지 살펴볼 차례입니다. 내재화 과정에서 마주친 수많은 검증 상황 중 서로 성격이 다른 다음 세 가지 대표 케이스를 추려 소개하고자 합니다.

  • 조회 로직 검증
  • 업데이트 로직 검증
  • 전체 프로세스 단위 검증

조회 로직 검증: 블랙박스 극복하기

가장 먼저 소개할 사례는 카탈로그 조회 API의 내재화입니다. 기존에는 타사 시스템에서 제공하는 조회 기능을 이용하고 있었는데요. 이를 저희 시스템으로 온전히 옮겨오는 것이 목표였습니다.

이 작업의 가장 큰 문제는 로직의 근거가 될 사양서나 기획서, 심지어 기존 코드조차 참조할 수 없는 완벽한 블랙박스 상황이었다는 점입니다. 이 API는 응답 필드만 100개가 넘고 다양한 필터링과 정렬 조건이 얽혀 있어 기획서 없이 로직을 완벽히 재현하기가 상당히 까다로웠습니다. 동일한 데이터베이스를 사용함에도 불구하고 내부 가공 로직이 베일에 쌓여 있어 기존 API와 신규 API의 응답을 1:1로 완벽히 일치시키는 과정에 상당한 난관이 있었습니다. 가령 정렬 조건이 명시되지 않았을 때의 기본 처리 방식이나, 실제 DB 데이터와 다르게 API 레벨에서 반환하는 기본값들은 코드를 보지 않고서는 파악하기가 매우 어려웠습니다.

저희는 이러한 사양의 간극을 메우기 위해 실시간으로 발생하는 데이터 변경을 트리거 삼아 기존 API와 신규 API의 응답을 끊임없이 비교하며 그 차이를 좁혀 나가는 전략을 택했습니다.

아래는 조회 로직을 검증하는 전체 아키텍처 및 흐름을 나타낸 다이어그램입니다. 복잡해 보일 수 있지만 앞서 설명한 트리거와 실행·비교, 가공·적재, 분석·개선 흐름을 대입해 보면 쉽게 이해하실 수 있습니다.

조회 로직을 검증하는 전체 아키텍처 및 흐름을 나타낸 다이어그램

모든 과정의 시작인 트리거 역할은 카탈로그 DB 변경을 실시간으로 포착하는 CDC(change data capture, 변경 데이터 캡처)가 담당합니다. DB의 바이너리 로그를 실시간으로 스트리밍해 데이터 변경을 감지하는 CDC 덕분에 다양한 카탈로그의 상태에서 실시간으로 검증을 시작할 수 있었습니다.

감지된 이벤트는 데이터 중앙 통로인 Kafka를 통해 검증 모듈로 전달돼 실행 및 비교 단계로 진입합니다. Kafka는 대량의 트래픽을 안정적으로 중계함과 동시에, 검증 로직이 실제 서비스 성능에 영향을 주지 않도록 물리적으로 격리하는 보호막 역할을 합니다. 검증 모듈은 이 이벤트를 기반으로 기존과 신규 API를 동시에 호출하는 듀얼 API 호출을 수행하며 수백 개의 응답 필드를 1:1로 정밀 대조합니다.

검증하는 과정에서 사전에 사양을 확인할 수 없었던 정렬 조건이나 반환된 기본값이 DB 데이터와 다른 문제들이 가장 빈번하게 드러났습니다. 명시적인 정렬 조건을 몰라서 기존 API와 응답 순서가 미세하게 어긋나는 경우에는 그 안의 실제 데이터값을 개별적으로 추출해 대조하는 방식을 택했습니다. 이를 통해 실제 로직의 정합성을 정확하게 체크하며 블랙박스의 베일을 하나씩 벗겨낼 수 있었습니다. 기본값 문제 역시 대조 과정을 통해 해결할 수 있었습니다. 예를 들어, 특정 데이터가 없는 카탈로그를 조회할 때 기존 API가 어떤 값을 반환하는지 역으로 추적했습니다. 이 과정에서 코드 레벨에서 특정 값을 기본으로 채워주고 있음을 파악했습니다.

검증 과정에서 발생한 방대한 '차이(diff)' 데이터는 다시 결과 토픽에 적재해 가공 및 적재 처리합니다. 저희는 수백만 건의 로그 속에서 특정 상품이나 필드의 오류 패턴을 순식간에 찾아낼 수 있는 OpenSearch와, 복잡한 코딩 없이 실시간 통계를 산출해 Slack 알림을 보내는 파이프라인을 완성해 주는 ksqlDB를 사용합니다. 이런 과정을 거쳐 시각화된 데이터와 지표를 바탕으로, 개발자는 대시보드나 알림을 통해 발견한 오류 패턴을 즉각 진단하고 로직에 반영하는 과정을 반복합니다.

아직 다루지 않은 처리율 제한(rate limit)이나 성능 측정은 검증 과정에서 발생하는 노이즈를 제어하고 신규 시스템의 속도까지 실시간으로 모니터링하기 위한 최적화 로직입니다. 이 로직들이 어떻게 검증의 효율과 시스템의 안정성을 동시에 잡아냈는지는 이어지는 섹션에서 설명하겠습니다.

하나의 공통 로직으로 여러 클래스 비교하기

조회 결과의 동일성을 증명하는 과정에서 했던 큰 고민은 ‘수많은 필드를 어떻게 공통 로직으로 처리할 것인가’였습니다.

저희는 우선 모든 API 응답을 클래스로 매핑하는 대신 응답 객체를 Map<String, Object> 구조로 변환하여 재귀적으로 탐색하는 방식을 도입했습니다. 덕분에 필드 구조가 아무리 복잡해도 필드명(Key)을 기준으로 값을 대조하는 유연한 검증 로직을 만들 수 있었습니다.

하지만 단순히 값만 비교해서는 충분하지 않았습니다. 데이터 내용은 같지만 리스트 내 순서가 달라 불일치로 판정되는 경우가 빈번했기 때문입니다. 이를 해결하기 위해 1차 비교에서 실패할 경우 문자열 자체를 정렬한 뒤 2차 비교를 수행하는 로직을 추가했습니다. 이를 통해 로직 자체의 결함과 단순 사양 차이를 명확히 구분해낼 수 있었습니다.

다음은 이 과정을 나타낸 다이어그램입니다.

하나의 공통 로직으로 여러 클래스 비교하는 흐름

검증 로직을 통해 식별된 불일치 필드 정보는 아래와 같이 표준화된 메시지 규격에 담겨 Kafka 토픽으로 프로듀싱되며, 이는 OpenSearch Connector를 통해 정의된 프로퍼티에 따라 OpenSearch 인덱스로 자동 적재됩니다.

비교 결과

개발자는 이렇게 축적된 데이터를 바탕으로 특정 오류 패턴을 신속하게 조회하거나 대시보드를 구축해서 전체 정합성 통계를 실시간으로 파악할 수 있습니다. 여기에 Kafka 스트림을 실시간 SQL로 처리하는 ksqlDB를 결합해, 유입되는 불일치 이벤트를 즉시 집계하고 이상 징후 발생 시 Slack 알림을 전송하는 파이프라인을 완성했습니다. 이를 통해 개발자가 이슈 인지부터 디버깅까지 최소한의 리드타임으로 대응할 수 있는 가시성 체계를 확보했습니다.

전환 초기에는 미처 파악하지 못한 차이 때문에 수백만 건의 오류 로그가 쏟아지기도 했습니다. 동일한 필드에서 발생하는 중복 오류는 분석 몰입도를 떨어뜨리는 잡음에 불과했기에, 저희는 필드당 분당 최대 추출 건수를 제한하는 처리율 제한을 적용했습니다. 이를 통해 개발자에게 분석에 집중할 수 있는 적정 수준의 샘플만 OpenSearch로 전달했고, 결과적으로 개발자는 더 빠르게 로직을 수정하고 재검증할 수 있게 되었습니다.

이 검증 파이프라인의 가치는 단순히 정합성을 맞추는 데 그치지 않았습니다. 기존 API와 신규 API를 동일한 시점에 병렬로 호출하는 구조 덕분에 자연스럽게 실시간 트래픽 환경에서 성능을 비교할 수 있었습니다. 응답값을 비교하는 로직 사이에 각 API의 응답 소요 시간을 측정하는 코드를 추가해서 신규 내재화 API가 기존 API와 비교해 어느 정도로 속도를 개선했는지 객관적인 지표로 확인할 수 있었습니다.

업데이트 로직 검증: 단순 조회가 아닌 상태 변화 다루기

앞선 사례가 정적 데이터를 읽어오는 조회에 집중했다면 이제 소개할 사례는 데이터의 흐름과 변화를 다루는 카탈로그 통계 업데이트 로직입니다. 수억 건의 상품 데이터가 변경될 때마다 이를 집계해 카탈로그 단위의 통계를 업데이트하는 이 과정은, 단순 조회와 달리 '상태 변화'를 다룹니다. 특히 내재화 과정에서 구버전의 비효율적인 로직을 리팩토링하고 개선하는 작업을 병행했기에 개선한 로직이 기존과 동일한 결과를 내는지 증명하는 것이 필수였습니다.

다음은 업데이트 로직을 검증하는 전체 흐름을 나타낸 다이어그램입니다.

업데이트 로직을 검증하는 전체 흐름을 나타낸 다이어그램

실시간 시뮬레이션을 이용한 업데이트 검증

업데이트를 검증하기 위해 기존 조회 검증 파이프라인의 큰 틀을 유지하되 내부에 '통계 시뮬레이션' 단계를 추가했습니다. 전체 아키텍처는 앞서 설명한 조회 검증 구조와 매우 유사하기 때문에 별도 다이어그램 설명은 생략하겠습니다. 핵심은 '이벤트 발생 시점의 예측치와 결과치를 대조하는 것'에 있습니다.

구체적으로, 카탈로그 필드가 변경돼 CDC 이벤트가 발생하면 검증 모듈이 신규 통계 로직을 즉시 가동해 예상 결과값을 미리 산출합니다. 이후 이 예측값을 기존 로직이 업데이트한 카탈로그 DB의 실제 결과값과 실시간으로 대조합니다. 이때 앞서 조회 검증에서 효과를 보았던 Map 기반 재귀 비교 로직을 활용해 통계 객체 내부의 깊숙한 필드까지 꼼꼼히 체크했습니다.

비동기 환경에서의 시간차 이슈 극복

업데이트 검증에서 마주친 가장 큰 걸림돌은 비동기 스트림 특유의 지연(lag)이었습니다. 기존 통계 로직과 신규 검증 로직이 모두 Kafka를 통해 비동기로 작동하다 보니, 검증 모듈이 비교를 시도하는 시점에 기존 로직에서 DB 업데이트가 아직 완료되지 않아 데이터가 일시적으로 불일치하는 상황이 빈번하게 발생했습니다.

저희는 먼저 불필요한 비교에 소요되는 리소스를 줄이기 위해 업데이트 주체에 따른 필터링을 도입했습니다. 카탈로그 통계와 관련 없는 필드가 변경될 때마다 검증을 수행하는 것은 자원 낭비입니다. 따라서 DB 변경 이벤트 중 기존 카탈로그 통계 모듈 때문에 업데이트가 발생한 경우에만 비교 프로세스가 작동하도록 설계했습니다. 이를 통해 검증 대상을 명확히 설정해서 검증 이벤트 발생량을 획기적으로 줄일 수 있었습니다.

필터링을 거친 후에도 발생하는 미세한 시차는 'N회차 재시도 큐' 전략으로 해결했습니다. 데이터가 불일치할 경우 즉시 오류로 판단하지 않고 해당 이벤트를 큐의 맨 뒤로 다시 보냅니다. 이후 일정 횟수 이상 재시도했는데도 값이 다르다면 비로소 실제 로직의 차이로 인식하고 결과 토픽으로 발행했습니다.

기존 통계 스트림의 마지막 단계에 검증 로직을 직접 결합해 지연을 완전히 없애는 방법도 고민했지만, 기존 스트림에 부하 및 지연이 발생할 수 있었기에 시스템 안정성을 위해 별도 검증 프로세스를 유지했습니다. 덕분에 기존 통계 처리 속도에 영향을 주지 않으면서도 독립적이고 안전하게 리팩토링 및 내재화 결과물을 검증할 수 있었습니다.

ETL 배치 검증으로 기존 비교 로직에서 놓친 누락 트리거까지 확인

모든 데이터가 완벽히 일치해 가던 찰나, 예상치 못한 사각지대가 발견됐습니다. 업데이트가 발생했어야 하는 시점에 아무런 작동도 하지 않는 케이스는 기존 비교 로직만으로는 잡아낼 수 없었던 것입니다.

카탈로그 통계는 카탈로그에 속한 상품의 가격이나 이미지와 같은 개별 필드 변화나 카탈로그 자체의 메타 정보 변경을 실시간으로 감지해 재계산하는 구조입니다. 문제는 이 감지 로직을 리팩토링하는 과정에서 발생했습니다. 수많은 필드 중 어떤 조합의 변경이 통계 업데이트를 트리거해야 하는지와 관련된 복잡한 조건을 구현하는 과정에서 특정 상황에 발동되는 트리거 자체가 누락돼 데이터가 과거 값에 머무르는 결함이 숨어 있었습니다.

이를 해결하기 위해 실시간 스트림과는 별개로 ETL 데이터를 활용한 배치 검증 체계를 설계했습니다. 저희는 실시간으로 카탈로그 정보를 Iceberg 테이블에 최신화하고 있었는데요. 여기에 쿼리를 실행해 하루에 한 번 모든 카탈로그의 통계를 전수 조사했습니다.

전수 조사를 수행하자 실시간 검증에서는 미처 발견하지 못했던 트리거 누락 케이스가 여럿 발견됐습니다. 저희는 놓친 원인을 분석해 로직을 보정하고 다시 배치 쿼리로 확인하는 과정을 반복하며 검증 결함을 보완해 나갔습니다.

실시간 검증으로 통계 로직의 정확도를 올리고 ETL 배치 검증으로 통계 트리거의 완결성을 확보한 이 단계까지 통과하고 나서야 신규 통계 시스템이 기존 시스템과 완벽히 동일한 생명 주기로 작동한다고 확신할 수 있었습니다.

전체 프로세스 단위 검증: E2E 관점에서 입출력 비교하기

마지막으로 소개할 사례는 수신 파이프라인의 내재화입니다. 수신 파이프라인은 판매자의 상품 파일을 다운로드하는 순간부터 내부 로직을 거쳐 유효성 검사를 마치고 최종 상품으로 등록되기까지의 매우 긴 여정을 포함합니다.

상품은 수십 개의 복잡한 필드로 구성되어 있는데 판매자마다 자신이 보유한 일부 필드만 선택적으로 제공하는 경우가 많습니다. 따라서 어떠한 필드 조합이 들어오더라도 기존 시스템과 동일하게 처리해 내는 것이 내재화의 핵심 목표였습니다. 모듈별 단위 테스트도 중요하지만 결국 ‘동일한 파일을 넣었을 때 동일한 상품 객체가 생성되는가’를 확인하는 E2E(end to end) 관점의 검증이 반드시 필요했습니다.

파이프라인 복제를 통한 쉐도우 검증

저희는 기존 시스템과 구조가 완벽히 동일한 신규 내재화 수신 파이프라인을 구축해 병렬로 가동했습니다. 우선 검증의 신뢰도를 확보하기 위해 입력의 동일성을 최우선으로 설정, 동일한 시점에 양쪽 파이프라인이 같은 판매자의 상품 파일을 동시에 수집하도록 환경을 조성했습니다. 이 과정에서 서비스의 안정성을 유지하기 위해 프로세스를 철저히 격리했습니다. 기존 파이프라인은 실제 서비스에 그대로 영향을 주도록 유지하는 한편, 신규 파이프라인의 결과물은 별도의 검증용 저장소에 쌓이도록 구성하여 기존 환경에 영향을 주지 않고 안전하게 데이터를 확보할 수 있었습니다. 이렇게 수집한 파이프라인의 최종 산출물인 상품 데이터와 각 단계별로 기록된 수신 데이터를 대조해서 내재화 로직의 정합성을 정밀하게 검증했습니다.

동일한 파일에서 나온 상품들의 스냅숏 비교

상품 정보는 판매자가 입력하는 대량의 파일을 통해 하루에도 수차례 업데이트될 뿐만 아니라 실시간 API를 통해서도 수시로 변경됩니다. 이처럼 데이터가 빈번히 수정되고 채널이 다양하다 보니, 시간이 조금만 차이나도 서로 다른 버전의 데이터를 처리하는 상황이 발생할 수 있었습니다. 단순히 상품 ID만으로는 지금 비교하려는 두 데이터가 같은 파일에서 생성된 산출물이라는 것을 보장할 수 없었던 것입니다.

저희는 비교의 기준점을 명확히 하기 위해 상품 고유값인 SKU(stock keeping unit, 재고 관리 단위)와 판매자의 업데이트 시점을 나타내는 merchantUpdateDate를 결합해 스냅숏의 고유 키로 활용했습니다. 이 키를 기준으로 데이터를 그룹화하자 하루에 여러 번 업데이트된 상품이어도 각 결과물이 어느 시점의 파일을 기반으로 생성된 산출물인지 정확히 특정할 수 있었습니다. 고정된 '스냅숏’을 기준으로 필드를 대조함으로써 신뢰할 수 있는 검증이 가능해졌고, 불일치가 발생하면 해당 키를 기반으로 로그를 역추적해 어떤 모듈에서 로직 차이가 발생했는지 즉시 디버깅할 수 있는 환경이 갖춰졌습니다.

검증 흐름 및 구조

상품에는 수많은 필드가 있기에 검증 초반에는 방대한 양의 불일치가 검출됐는데요. 그중에는 실제 로직 오류가 아닌 단순 사양 변경이나 의도된 차이도 섞여 있었습니다. 이와 같은 잡음이 섞인 채로 검증 결과가 계속 노출되면 개발자가 알림에 무뎌지면서 정작 중요한 결함을 놓칠 위험이 커집니다.

저희는 분석 피로도를 낮추고 핵심 결함에 집중하기 위해 비교 대상 필드를 선택적으로 관리하는 필터링 로직을 도입했습니다. 확인이 완료된 사양 차이나 불필요한 필드는 비교 대상에서 제외해 유의미한 변경 사항만 검출되도록 개선했습니다.

검증 결과의 데이터 구조는 기존 시스템의 로직을 계승하되 상품을 명확히 식별할 수 있는 몇 가지 필드를 추가해 확장성을 확보했습니다.

상품 식별 필드 추가한 데이터 구조

이미 앞서 구축해둔 OpenSearch와 ksqlDB 기반의 파이프라인이 존재했기에 새로운 검증 작업에서도 별도 공수 없이 손쉽게 시스템을 연결해 가시성을 확보했습니다. 이를 통해 개발자는 언제, 어떤 판매자의, 어떤 상품의, 어떤 필드에서 차이가 발생했는지 한눈에 파악할 수 있습니다.

수신 파이프라인 단계별 비교

상품 필드 하나하나를 대조하는 세밀한 검증도 중요하지만, 한 파일 내의 유효 아이템 수나 수신 단계별 상태값처럼 큰 단위의 정합성을 맞추는 것 또한 반드시 필요했습니다. 내재화 과정에서 상품 수신 테이블 구조가 변경되면서 기존 시스템과 신규 시스템의 테이블 구조가 완전히 달라졌고, 단순히 DB 레벨에서 데이터를 맞추기에는 너무나 많은 테이블이 얽혀 있었습니다. 처리 결과를 확인하기 위해 매번 복잡한 조인 쿼리를 수행해야 하는 상황에서 수작업으로는 필요한 만큼 분석 효율을 올릴 수 없었습니다.

저희는 이 병목을 해결하기 위해 스케줄별 처리 결과를 비교하는 로직을 도입했습니다. 각 스케줄이 완료될 때마다 자동화한 파이프라인을 통해 전체 처리 결과는 물론 파일 내 유효 건수와 오류 건수 등이 기존 시스템과 동일하게 산출되는지 검증됩니다. 이를 통해 DB 구조가 완전히 달라진 상황에서도 거시적인 관점의 수신 프로세스 정합성을 확인할 수 있었습니다.

다음은 전체 아키텍처 및 흐름을 나타낸 다이어그램입니다.

전체 아키텍처 및 흐름

이 프로세스는 우선 스케줄러를 통해 신규 시스템에서 처리가 완료된 데이터 목록을 가져오는 것으로 시작합니다. 데이터 수집 단계에서 이미 원천적인 오류가 발생한 경우에는 즉시 불일치 대상으로 분류하여 예외 상황을 격리하고, 정상적으로 수신된 데이터만 본격적으로 비교를 시작합니다. 이때 서로 다른 환경의 데이터를 정확히 매칭하기 위해 단순히 시스템의 ID를 대조하는 대신 데이터의 실제 유입 경로인 원천 소스 URL을 기준점으로 삼았습니다. 물리적으로 분리돼 있고 테이블 구조가 다른 두 DB일지라도 데이터의 뿌리가 되는 경로값을 대조함으로써 흩어져 있는 처리 결과를 1:1로 정확하게 묶는 그룹화가 가능했습니다.

매칭된 데이터는 크게 두 가지 핵심 지표를 검증합니다. 먼저 아이템 건수를 비교해 양쪽 파이프라인에서 처리된 유효 데이터 수와 에러 데이터 수의 오차를 체크합니다. 이어서 처리 건수가 일치하더라도 각 시스템의 세부 로직에 따라 최종 처리 결과는 다를 수 있기에 각 단계별 처리 상태값을 대조해 검증 프로세스의 완결성을 증명합니다.

최종적으로 이 모든 수치와 상태가 완벽히 일치할 때만 성공 메시지를 생성합니다. 만약 어느 한 곳이라도 어긋난다면 즉시 개발자에게 Slack 알림을 보냅니다. 알림에는 상세 비교 UI로 연결되는 링크를 포함시켜서 개발자가 복잡한 쿼리를 직접 작성할 필요 없이 시각화된 정보와 도구로 즉각 디버깅을 시작할 수 있도록 설계했습니다.

성공/실패 메시지

효율적인 디버깅 도구 제공: OpenSearch 대시보드와 검증 전용 관리자 페이지(admin)

검증 로직이 아무리 정교해도 개발자가 수백만 건의 로그 속에서 직접 원인을 찾는 것은 모래사장에서 바늘 찾기와 같습니다. 저희는 개발자가 효율적으로 디버깅할 수 있도록 가시성을 높이는 도구를 마련해 제공했습니다.

오류 패턴을 파악할 수 있는 OpenSearch 대시보드 제공

OpenSearch에 쌓인 차이(diff) 데이터는 단순한 로그가 아닙니다. 통계로써 가치가 있는 데이터입니다. 저희는 대시보드를 이용해 다음과 같은 정보를 실시간으로 모니터링했습니다.

  • 정합성 추이 및 상품 통계: 전체 검증 대상 중 일치 건수가 100%로 수렴해가는 과정을 시각화해 내재화 프로젝트의 완료 시점을 객관적으로 가늠했습니다.
  • 오류 유형 및 필드별 그룹화: 어떤 필드에서 불일치가 집중적으로 발생하는지 패턴을 분석했습니다. 예를 들어 100만 건의 오류가 발생하더라도 실제 원인은 '특정 필드의 타입 변환 오류' 하나인 경우가 있었는데, 대시보드는 이러한 공통 오류를 그룹화해서 개발자가 단 하나의 로직 수정으로 수십만 건의 노이즈를 한 번에 해결할 수 있게 도왔습니다.
  • 타임라인 기반 이상 징후 감지: 언제 데이터 변경이나 불일치가 집중적으로 감지되었는지 시계열로 파악해 특정 배포 시점이나 외부 시스템의 변화가 로직에 미친 영향을 즉각 추적할 수 있었습니다.

결과적으로 OpenSearch 대시보드는 개발자에게 어디를 고쳐야 하는지 알려주는 나침반 역할을 담당했습니다. 이는 개발자가 방대한 데이터에 매몰되지 않고 핵심 결함을 빠르게 해결할 수 있는 든든한 기반이 되었습니다.

검증 전용 관리자 페이지 제공

내재화 프로젝트에서 가장 큰 적은 눈으로 직접 확인하기 힘든 방대한 데이터였습니다. 수천만 건의 상품과 복잡하게 얽힌 필드를 매번 SQL 쿼리만으로 확인하는 것은 불가능에 가까웠고, 개발자의 분석 효율을 심각하게 저하하는 원인이었습니다.

이에 저희는 육안으로 일일이 DB 테이블을 쳐다보는 대신 직관적인 UI를 통해 데이터를 확인할 수 있는 관리자 페이지를 만들어 제공했고, 이는 디버깅 속도를 높이는 데 큰 도움이 되었습니다.

우선 카탈로그 비교 영역에서는 압도적으로 많은 필드 수를 효율적으로 다룰 수 있는 방법을 제공하는 데 집중했습니다. 예를 들어 모든 필드를 나열하는 대신 개발자가 원하는 특정 필드만 검색해서 보거나, 실제 값이 다른 필드만 골라낼 수 있는 기능을 도입했습니다. 단순히 데이터가 다르다는 사실을 아는 것을 넘어, 배열 내의 순서만 바뀐 것인지 혹은 특정 값이 누락된 것인지를 하이라이트하여 보여주는 디테일도 더했습니다. 이러한 세밀한 시각화는 수많은 데이터 잡음 속에서 진짜 로직 오류를 찾아내는 데 걸리는 시간을 단축시켰습니다.

아래 이미지는 카탈로그 비교 화면의 예시입니다. 개발자가 문제를 직관적으로 파악할 수 있도록 불일치 필드는 붉은색, 순서만 다른 경우는 노란색으로 시각화했습니다. 여기에 검색과 필터링 기능을 더해 특정 오류 패턴을 신속히 추적할 수 있도록 설계했습니다.

카탈로그 비교 화면 예시

카탈로그 통계 검증 역시 시뮬레이션 기능을 도입해 완성도를 높였습니다. 아래와 같이 카탈로그 ID를 입력하면 신규 로직을 가동하여 통계가 어떻게 계산되는지 미리 확인해 볼 수 있습니다. 이를 통해 결과값에 차이가 있다면 통계 계산 로직이나 트리거 로직의 문제가 발생했다고 진단할 수 있습니다. 또한 통계는 여러 상품 정보가 응집된 결과물이기 때문에 해당 통계에 영향을 준 원천 상품 리스트와 필드값까지 한 화면에서 추적할 수 있도록 설계해 카탈로그 내의 복잡한 관계를 쉽게 확인할 수 있도록 제공했습니다.

카탈로그 시뮬레이션 기능 화면

또한 아래와 같이 수신 파이프라인의 각 단계별 처리 상태와 상품 수를 가시화해 제공했습니다. 기존에는 여러 테이블을 조인해야만 파악할 수 있었던 정보를 한 화면에 좌우로 배치해 비교할 수 있기 때문에 어떤 단계에서 상품 수가 유실됐거나 에러가 발생했는지 즉각 인지할 수 있습니다.

PMR 비교 결과 뷰어

과거에는 이와 같은 관리자 페이지를 제공하려면 프런트엔드를 따로 공부해야 했고 화면 구성에도 상당한 고생이 따랐는데요. 이제는 AI 덕분에 간단한 관리자 페이지는 비교적 쉽게 구축할 수 있는 환경이 마련됐습니다. 따라서 데이터를 일일이 대조하는 수고를 들이기보다는 초기에 전용 도구를 만들어 사용하는 것이 수천만 건의 데이터를 검증해야 하는 장기적 관점에서 볼 때 압도적으로 효율이 높다고 생각합니다.

마치며

기획서와 사양서가 없는 상태에서 진행한 이번 내재화 프로젝트는 큰 깨달음을 주었습니다. 시스템이 아무리 거대하고 복잡한 블랙박스일지라도 ‘입력과 출력을 명확히 정의하고 문제를 작게 쪼개어 접근’하면 동일성을 증명해 낼 수 있다는 사실입니다.

물론 가능하면 코드와 기획서가 있는 상황에서 개발하는 것이 가장 좋을 것입니다. 하지만 피치 못할 상황에서 마주한 이 검증 과정이 결과적으로 시스템의 안정성을 100% 확신할 수 있게 해준 소중한 자산이 되었습니다. 이 글이 저희의 사례가 비슷한 고민을 하는 다른 분들에게 실질적인 도움이 되기를 바라며 이만 마칩니다.