사이드프로젝트 slecs

공급사 데이터 정합성 불일치를 SQL 분석 보고서로 진단

목차

supplier phase8 분석 보고서를 작성한 게 새벽 2시 44분이었다. 이 시간에 SQL 보고서를 만들고 있었다는 게 좀 웃기긴 한데, 불일치 징후가 포착된 이상 그냥 넘길 수가 없었다.

공급사(supplier) 관련 파이프라인에서 데이터 정합성 문제는 보통 조용하게 쌓인다. 어느 한 순간에 팍 터지는 게 아니라, 집계 수치가 미묘하게 안 맞고, 특정 케이스에서 처리가 누락되고, 나중에 가서야 "어 이거 왜 이래?" 하는 상황이 온다. phase8이라는 이름이 붙은 걸 보면 이 supplier 흐름이 여러 단계를 거치는 구조인데, 단계가 많을수록 어느 지점에서 틀어졌는지 로그만 봐서는 전체 그림이 안 보인다.

SQL 분석 보고서를 먼저 쓰는 이유

코드 수정부터 들어가고 싶은 충동이 있다. "어디서 틀렸는지 대충 보이는데 그냥 고치면 되지 않나" 싶은 거다. 근데 그렇게 하면 두 가지 문제가 생긴다.

하나는, 추정으로 고친 거라서 진짜 원인을 잘못 짚을 수 있다. 증상이 비슷하더라도 원인이 다를 수 있고, 엉뚱한 곳을 고치면 문제가 다른 형태로 재발한다. 둘은, 수정 이후에 "정말 나아졌나"를 확인할 기준이 없다. 수정 전 수치를 기록해놓지 않으면 이전 대비 개선 여부를 판단할 수가 없다.

그래서 순서가 중요하다. 보고서 먼저, 수정 나중. 이 순서를 지키면 수정 근거가 생기고, 재검증 기준도 생긴다.

이번에 사용한 분석 구조는 대략 이렇다:

-- 기대값과 실제값을 나란히 꺼내는 패턴
SELECT
  s.supplier_id,
  s.expected_count,
  COUNT(r.id) AS actual_count,
  s.expected_count - COUNT(r.id) AS diff
FROM supplier_phase8_summary s
LEFT JOIN phase8_records r
  ON r.supplier_id = s.supplier_id
  AND r.status != 'cancelled'
GROUP BY s.supplier_id, s.expected_count
HAVING diff != 0
ORDER BY ABS(diff) DESC;

이 패턴의 핵심은 "내가 있어야 한다고 생각하는 숫자"를 먼저 뽑고, 실제 테이블에서 집계한 숫자와 같은 열에 세로로 붙이는 것이다. HAVING diff != 0 한 줄이 이상 항목 전부를 걸러준다. 차이가 있는 행 하나하나가 버그 후보다.

보고서 구성 방식과 트레이드오프

결과를 HTML로 뽑은 건 공유 편의 때문이었다. 같이 보는 사람이 터미널 환경이 없어도 브라우저에서 바로 열 수 있고, 표와 차트를 넣기도 쉽다. 반면 버전 관리는 불편하다. HTML 보고서를 git에 넣으면 diff가 지저분하고, 안 넣으면 히스토리가 없다.

이번엔 파일명에 타임스탬프를 박는 방식으로 타협했다. 20260423_0244_supplier_phase8.html 식으로. 완벽한 해결은 아닌데 일단 "언제 어떤 상태였는지"는 파일명으로 추적할 수 있다.

보고서 내부 구성은 이렇게 잡았다:

  • 집계 개요 - 전체 건수, 처리 완료율, 누락 건수
  • 기대값 vs 실제값 비교 표
  • 이상 항목 목록과 원인 가설
  • 재현 케이스(어떤 입력에서 어떤 결과가 나왔는지)
  • 후속 액션 항목

이 중에서 "원인 가설" 칸이 사실 제일 중요하다. 데이터만 나열하면 보고서가 아니라 덤프다. 이상 항목마다 "왜 이게 안 맞는지"에 대한 가설을 한 줄이라도 적어두면, 나중에 수정 작업할 때 어디서부터 건드려야 하는지 방향이 잡힌다.

이번 분석에서 나온 발견 사항 요약:

항목 상태
데이터 정합성 일부 불일치 발견
처리 누락 건 확인 및 후속 조치 필요
쿼리 성능 허용 범위 내
엣지 케이스 추가 검토 필요

쿼리 성능이 허용 범위 내였다는 건 다행이었는데, 엣지 케이스 항목은 좀 찜찜하게 남았다. 특정 조건 조합에서만 발생하는 케이스라 재현이 까다로웠고, 이번 보고서에서 완전히 정리하지 못하고 "추가 검토 필요"로 열어뒀다. 이런 항목은 그냥 두면 잊어버리기 쉬워서 별도 티켓으로 떼어서 트래킹하는 게 맞다.

보고서 → 수정 → 재검증 사이클을 반복하는 구조

이번 phase8 작업에서 확립하려고 한 게 바로 이 사이클이다. 보고서에서 도출된 항목들은 각각 별도 커밋으로 픽스했고, 수정 후에 같은 쿼리를 다시 돌려서 diff가 0으로 떨어지는지 확인했다.

재검증 단계에서 "수정됐는데 왜 아직도 diff가 있지?"가 나오면 원인 가설이 틀린 거다. 그 시점에 다시 보고서로 돌아가서 가설을 수정하고, 다른 쿼리를 짜야 한다. 번거롭게 느껴지지만 이 루프를 돌리지 않으면 표면적인 수치만 맞춰놓고 실제 로직 오류가 남는 경우가 생긴다.

가장 효과적인 분석 방법은 결국 단순하다. 내가 예상한 숫자와 실제 DB에 있는 숫자를 나란히 놓고 비교하는 것. 차이가 벌어진 행이 문제의 위치를 직접 가리킨다. 거기서부터 역으로 타고 올라가면 어느 처리 단계에서 뭔가 빠졌는지 찾을 수 있다.

새벽에 이걸 하고 있었다는 건 솔직히 좋은 신호는 아닌데, 적어도 코드를 감으로 건드리는 것보다는 낫다. 데이터로 확인하고 고치는 습관이 이번 phase 작업을 통해 좀 더 굳어진 것 같다.

댓글 0

첫 댓글 달아줘.