개발 slecs

공급사 포털 상품 관리 크롤링·재고 수정 버그 개선

목차

supplier-portal의 my-products 쪽을 이번에 한꺼번에 정리했다. 검색 필터링, 갤러리 렌더링, URL 크롤링 로직, 재고 인라인 수정 - 각각 따로 신고 들어온 건 아니었고, 관련 화면을 뒤지다 보니 엣지 케이스들이 줄줄이 나왔다. 변경한 파일은 내부 클래스 2개, 뷰/스타일 2개, 설정 및 문서 1개, SQL 매퍼 1개. 숫자만 보면 작아 보이는데 각 레이어가 맞물려 있어서 하나 건드리면 옆을 같이 확인해야 했다.

버그의 실제 원인

공급사 포털처럼 다수의 공급자가 각각 상품을 올리는 구조는 데이터 상태 조합이 생각보다 많다. 상품이 검수 대기인지, 승인인지, 일시 중지인지에 따라 집계 쿼리 결과가 달라지고, 갤러리 노출 여부도 갈린다. URL 크롤링은 외부 연동이라 응답 포맷이 기대와 다를 때 null이 그냥 흘러 들어오는 구간이 있었다.

핵심은 SQL 매퍼에서 집계 조건이 잘못 걸려 있거나 아예 빠진 것이었다. 재고 수량을 합산할 때 특정 상태의 레코드가 포함되거나 빠지면 화면에 표시되는 숫자와 실제 재고가 다르게 보인다. 이런 유형의 버그는 "조건 하나가 없다"는 단순한 실수인데 증상은 "숫자가 가끔 이상하다"로 나타나서 원인을 찾는 데 시간이 걸린다. 수정 전후 쿼리 구조를 단순화해서 표현하면 이런 식이다:

-- 수정 전: 상태 필터 없이 전체 합산
SELECT SUM(stock_qty)
FROM product_stock
WHERE product_id = #{productId}

-- 수정 후: 유효 상태만 포함
SELECT SUM(stock_qty)
FROM product_stock
WHERE product_id = #{productId}
  AND status IN ('ACTIVE', 'RESERVED')

조건 하나가 빠지면 취소된 입고 건까지 재고로 잡혀서 숫자가 부풀려진다. 사용자 입장에서는 "재고가 있다고 하는데 실제로는 없다"는 상황이 생기는 것이다.

URL 크롤링 쪽은 외부 응답이 null이거나 형식이 다를 때 내부 클래스에서 NPE로 터지던 걸 방어 처리했다. 크롤링 로직은 항상 "응답이 없거나 형식이 다를 수 있다"는 전제로 짜야 한다는 걸 다시 확인했다.

// 수정 전: null 체크 없이 바로 접근
String imageUrl = response.getItems().get(0).getUrl();

// 수정 후: null-safe 처리
String imageUrl = Optional.ofNullable(response)
    .map(CrawlResponse::getItems)
    .filter(items -> !items.isEmpty())
    .map(items -> items.get(0).getUrl())
    .orElse(null);

재고 인라인 수정은 화면에서 바로 수치를 바꾸는 UX인데, 저장 후 갱신되는 값이 방금 입력한 값이 아닌 이전 캐시 값을 보여주는 문제가 있었다. 프론트 스크립트에서 서버 응답을 받아 해당 셀만 DOM 갱신하도록 바꿨다. 전체 페이지 리로드는 빠른 우회 방법이지만 UX가 끊기는 느낌이 있어서 이번 기회에 정리했다.

수정 흐름: 증상 말고 원인

버그 하나를 잡을 때 증상만 고치면 비슷한 자리에서 다시 터진다. 이번에는 SQL 조건 수정 → 내부 클래스 로직 보정 → 뷰 렌더링 수정 → 프론트 스크립트 순으로 내려오면서 각 레이어를 같이 확인했다.

실제로 체크하는 순서:

  • 같은 로직이 다른 경로에도 있는지 - 컨트롤러나 서비스가 여러 엔드포인트에서 공유되는 경우 한쪽만 고치면 다른 쪽이 남는다
  • 수정이 기존 정상 케이스를 망가뜨리지 않는지 - 조건을 추가할 때 특정 케이스가 누락되는 상황이 생길 수 있다
  • 해당 화면과 API 양쪽에서 직접 재현해서 확인
  • 관련 숫자를 다른 화면과 cross-check - 재고 화면과 주문 화면에서 같은 수치가 일치하는지

엣지 케이스를 꼼꼼히 따지는 게 귀찮아 보여도 나중에 같은 버그로 다시 오는 시간 비용이 훨씬 크다. 특히 여러 화면이 동일한 데이터를 다른 방식으로 보여줄 때는 cross-check이 유일한 검증 수단이 되는 경우가 많다.

공급사 포털 도메인에서 다시 배운 것

사내 서비스라도 상품 관리, 재고, 주문이 연결되는 순간 숫자 정합성이 핵심이 된다. SQL 집계, 상태 머신, 예외 처리, 화면 렌더링, 권한 체크가 얽혀 있어서 어느 하나만 빠져도 특정 사용자에게 이상한 화면이 나온다. 기능 하나가 단순히 버튼 하나 추가하는 것으로 끝나지 않는다는 걸 이런 작업을 할 때마다 다시 느낀다.

재고 숫자는 공급사가 실제 운영 판단에 쓰는 수치라서 틀리면 신뢰 문제로 이어진다. "대충 맞는 것 같다"로 넘어가면 반드시 같은 자리에서 다시 온다. 이건 금융이나 결제 도메인만의 이야기가 아니라 누군가 실제 의사결정에 쓰는 숫자를 다루는 모든 화면에 해당한다.

커밋은 논리적으로 독립된 단위로 쪼갰다. SQL 매퍼 수정, 내부 클래스 수정, 뷰 수정을 하나로 묶지 않고 각각 커밋했다. 나중에 어느 변경에서 문제가 생겼는지 찾을 때 덩어리 커밋 하나보다 작은 커밋 여러 개가 훨씬 낫다. 커밋 메시지도 "SQL 조건 수정"보다 "재고 집계 시 취소 상태 레코드 제외 - 부풀려진 수량 버그 수정" 쪽으로 쓰려고 한다. 다음에 같은 파일 건드릴 때 git log가 문서 역할을 해줘야 하니까.

변경 전 현재 동작의 수치나 화면을 메모해 두고 수정 후 같은 케이스로 확인하는 습관도 계속 유지 중이다. 특히 숫자가 얽힌 버그는 "고친 것 같다"와 "실제로 고쳐졌다"를 구분하는 것 자체가 중요한 작업이다.

댓글 0

첫 댓글 달아줘.