한 채널에서 중복 반품을 정리해 달라는 요청을 받고 GROUP BY order_no HAVING COUNT(*) > 1 로 집계하니 3천 건이 넘게 나왔다. 보고하려다 사업부가 말한 것이 화면에서 보이는 중복인데 그것이 수천 건일 리가 없다는 데서 걸렸다.
Table of contents
Open Table of contents
원인 — 규모가 가리킨 계층
규모가 안 맞으면 세고 있는 것이 다른 것이라는 신호다. 이 시스템의 반품은 마켓에서 클레임을 가져와 쌓는 channel_claims 와 그것으로 우리 반품을 만드는 returns 둘로 돼 있었다.
내가 센 것은 channel_claims 였고 수집이 중복됐다는 것과 반품이 중복 생성됐다는 것은 다른 얘기다.
검증 — 표본이 확인한 실제
집계된 것 중 일부를 뽑아 returns 를 조회했다. deleted_at IS NULL 인 반품 수가 대부분 하나였다.
수집은 두 번 됐는데 반품은 하나만 만들어졌고 뒤의 수집이 기존 것을 만나 새로 안 만든 것이었다. 수집 계층 집계를 그대로 썼으면 실제보다 수백 배에서 수천 배 과대 추정한 것이 되고 그 숫자로 삭제 대상을 고르면 멀쩡한 것을 지운다.
판단 기준 — 바뀐 대상 기준
두 가지 방법이 있었는데 channel_claims 집계를 그대로 두고 표본 비율로 업무 계층 규모를 추정하는 것과 returns 의 deleted_at IS NULL 기준으로 다시 세는 것이다. 뒤쪽을 골랐다. 이유는 삭제 대상 목록은 추정이 아니라 행 단위로 정확해야 해서다. 대신 returns 와 channel_claims 를 잇는 조인을 한 번 더 짜는 시간을 감수했다. 이미 지워진 것은 중복이 아니므로 삭제 여부 조건을 함께 건다.
이 기준 변경이 규모 산정과 정리 계획과 대상 선별을 한꺼번에 바로잡았고 계층을 정하는 것이 집계의 첫 단계여야 했다.
수집 중복의 원인 분해
channel_claims 의 3천여 건이 무엇인지도 봤다. 가설 하나는 고객이 요청했다가 철회하고 다시 요청하는 패턴이었다.
같은 주문에 status = 'withdrawn' 클레임이 있는지를 EXISTS 로 걸어 세니 그 패턴이 500건이 안 됐다. 나머지 대부분은 다른 원인의 수집 중복이었고 그 원인은 이번에 안 봤다.
주의 — 문제를 나누고 시점을 적기
요청은 중복 반품 정리였고 그것은 업무 계층 얘기이므로 수집 계층 중복은 별도 원인 조사로 분리했다. 한 조사에서 두 문제가 나오면 나눠야 하고 섞으면 둘 다 흐려진다.
건수를 보고하면서 조회 시점도 밝혔는데 이 데이터는 계속 늘어나 며칠 뒤에 세면 다르기 때문이다. YEAR(created_at) 별 분포도 함께 적었더니 몇 해에 걸쳐 고르게 퍼져 있어서 최근에 갑자기 생긴 것이 아니라 오래 쌓인 것이었고 그러면 급하게 조치할 일이 아니다.
정리
- 규모가 안 맞으면 세고 있는 계층을 의심한다
channel_claims의 중복이returns의 중복은 아니다- 잘못된 계층으로 세면 수백 배에서 수천 배 과대 추정된다
- 삭제 대상은
deleted_at IS NULL기준으로 센다 - 원인이 여럿이면 존재 조건으로 갈라 센다
- 한 조사에서 두 문제가 나오면 나눈다
- 건수에 조회 시점을 밝힌다
- 연도별 분포를 보면 긴급도가 갈린다