정산에서 매칭 안 된 건이 5,987건 나왔다. 원본 파일과 우리 데이터를 대조하는 작업이었다.
Table of contents
Open Table of contents
증상 — 미매칭 5,987건
첫 가설은 단순했다.
특정 컬럼이 비어 있어서 매칭이 안 된다
엑셀을 열면 빈 칸이 바로 눈에 보이니 NULL 쪽으로 먼저 갔다.
그런데 NULL 인 행을 COUNT 해 보니 그렇게 많지 않았다. 그것을 전부 채워도 대부분이 그대로 남는다는 뜻이라 다른 원인을 찾아야 했다.
원인 — 다섯 겹으로 쌓인 규칙
매칭 코드를 읽으니 JOIN 하나로 끝나는 구조가 아니었다.
일부 채널은 특정 번호로 매칭하는 것을 아예 건너뛰고 있었다.
if (in_array($channel, self::SKIP_ORDER_ID_MATCH)) {
// 이 번호로는 매칭 안 함
}
SKIP_ORDER_ID_MATCH 에 든 채널은 주문 번호가 아닌 다른 키로 맞춰야 한다.
일부 채널은 옵션 식별자를 인코딩해 특정 형식으로 넣고 있었다.
_F{인코딩된 값}
원본 파일에 _F 로 시작하는 그 형식이 들어 있으니 디코드해야 우리 값과 맞는다.
교환 주문은 원주문이 아니라 재발송 항목에 붙어야 했고 후보가 여럿이면 실패로 보는 분기도 있었다.
if ($candidates->count() !== 1) {
// 매칭 실패
}
$candidates 가 정확히 하나로 특정될 때에만 매칭으로 통과시키고 있었다.
마지막으로 이관된 상품은 두 표의 식별자 체계가 달랐다.
연동 테이블 신 구조 식별자
항목 테이블 옛 구조 식별자
연동 테이블 과 항목 테이블 을 직접 JOIN 할 수 없어 한 단계를 거쳐야 한다.
처음 본 것은 엑셀 컬럼과 DB 컬럼의 대응 하나였는데 실제로는 채널 예외와 인코딩과 교환 분기와 유일성과 이관 불일치가 그 위에 얹혀 있었다. 컬럼 매핑만 보고 있었으니 나머지 네 겹은 보일 수가 없었다.
조치 — 순서대로 좁혔다
다섯 규칙을 한꺼번에 걸지 않고 순서대로 적용했다.
1. 채널별로 나눈다
2. 인코딩된 키를 디코드한다
3. 교환 건은 재발송으로 대상을 바꾼다
4. 이관 건은 한 단계 거쳐 조인한다
5. 양방향 1:1 확인
각 단계에서 매칭되는 건수가 얼마나 느는지를 COUNT(*) 로 그때마다 셌다.
다섯을 한꺼번에 걸어 버리면 어느 규칙이 몇 건을 설명했는지 알 수 없다. 하나씩 벗겨 내니 규칙마다의 기여가 숫자로 남았다.
검증 — 양방향 1:1 확인
매칭 스크립트에 규칙을 하나 두었다.
-- 방향 A: 원본 → 우리 것
SELECT COUNT(*) FROM ours WHERE key = <원본 키>;
-- 정확히 1이어야 함
-- 방향 B: 우리 것 → 원본
SELECT COUNT(*) FROM source WHERE key = <우리 키>;
-- 정확히 1이어야 함
ours 쪽과 source 쪽 COUNT(*) 가 둘 다 1일 때에만 매칭으로 인정한다.
ours 쪽만 재고 넘어가면 통과와 오염을 가르지 못하고 두 방향이 같은 답을 낼 때에만 짝이 하나로 정해진다.
주의 — 한 방향만 보면 다대일이 통과한다
왜 한쪽으로 부족한지는 이런 모양에서 그대로 드러난다.
원본 A → 우리 것 X (1건, 통과)
원본 B → 우리 것 X (1건, 통과)
A 도 B 도 각각은 1건이라 원본 쪽에서 보면 둘 다 통과한다.
반대 방향으로 보면 그 자리에서 잡힌다.
우리 것 X → 원본 A, B (2건, 실패)
X 하나에 A 와 B 가 둘 다 붙어 있으니 실패로 걸린다.
한 방향만 봤으면 이것이 통과해 데이터가 오염된다. 정산에서 그것은 같은 금액이 두 번 잡힌다는 뜻이었다.
결과 — 남은 여섯 건
돌린 결과는 이랬다.
전체 5,987건
해결 5,981건 (99.9%)
남음 6건
남은 여섯 건은 각자 개별 사정이 있는 것들이라 손으로 하나씩 봤다.
단계별로 세어 두었기 때문에 그 여섯 건이 어느 단계에서 막혔는지도 함께 알 수 있었다. 처음에 세운 NULL 가설은 눈에 보이는 것이었고 실제 원인은 코드를 읽어야 나오는 쪽에 있었다.
정리
- 첫 가설이 단순하면 표면일 가능성이 높다
- 매칭 규칙이 여러 겹일 수 있다
- 컬럼 매핑만 보면 나머지 겹이 안 보인다
- 채널 예외와 인코딩과 교환 분기와 유일성과 이관 불일치를 다 본다
- 한 방향만 보면 다대일이 그대로 통과한다
- 양쪽에서
COUNT(*)가 1 인지 확인한다 - 매칭이 오염되면 정산 금액이 두 번 잡힌다
- 규칙을 하나씩 벗겨 내며 기여를 센다
- 단계별로 세면 안 풀린 것이 어디서 막혔는지 안다