Skip to content
isdnetworks
Go back

매칭은 양방향으로 확인한다

(수정됨)

원화 입금 대조에서 매칭 안 된 건이 수천 건 나왔다. 은행 입금 내역 파일과 우리 입금 기록을 대조하는 작업이었다.

Table of contents

Open Table of contents

증상 — 미매칭 수천 건

첫 가설은 단순했다.

입금 코드 컬럼이 비어 있어서 매칭이 안 된다

파일을 열면 빈 칸이 바로 눈에 보이니 NULL 쪽으로 먼저 갔다.

그런데 NULL 인 행을 COUNT 해 보니 그렇게 많지 않았다. 그것을 전부 채워도 대부분이 그대로 남는다는 뜻이라 다른 원인을 찾아야 했다.

원인 — 다섯 겹으로 쌓인 규칙

매칭 코드를 읽으니 JOIN 하나로 끝나는 구조가 아니었다.

일부 은행은 입금 코드로 매칭하는 것을 아예 건너뛰고 있었다.

if (in_array($bank, self::SKIP_DEPOSIT_CODE_MATCH)) {
    // 이 은행은 입금자명에 코드가 안 실린다 — 금액·시각으로 맞춘다
}

SKIP_DEPOSIT_CODE_MATCH 에 든 은행은 입금 코드가 아닌 다른 키로 맞춰야 한다.

일부 은행 파일은 문자셋이 달랐다.

입금자명   EUC-KR, 20바이트에서 절단

EUC-KR 파일을 LOAD DATA INFILE 로 그냥 올리면 입금자명이 깨진다. MySQL 5.7 참고 매뉴얼의 그 절은 서버가 파일을 character_set_database 로 해석하고 SET NAMES 는 입력 해석에 영향이 없다고 적으므로, 파일 문자셋이 다르면 CHARACTER SET euckr 절을 줘야 우리 값과 맞는다.

분할 입금은 원 신청이 아니라 부분 입금 항목에 붙어야 했고 후보가 여럿이면 실패로 보는 분기도 있었다.

if (count($candidates) !== 1) {
    // 매칭 실패
}

$candidates 가 정확히 하나로 특정될 때에만 매칭으로 통과시키고 있었다.

마지막으로 리뉴얼 때 이관된 회원은 두 표의 식별자 체계가 달랐다.

입금 신청 테이블   신 구조 member_no
가상계좌 테이블    옛 구조 회원 번호

입금 신청 테이블 과 가상계좌 테이블 을 직접 JOIN 할 수 없어 member_map 한 단계를 거쳐야 한다.

처음 본 것은 파일 컬럼과 DB 컬럼의 대응 하나였는데 실제로는 은행 예외와 문자셋과 분할 입금 분기와 유일성과 이관 불일치가 그 위에 얹혀 있었다. 컬럼 매핑만 보고 있었으니 나머지 네 겹은 보일 수가 없었다.

조치 — 순서대로 좁혔다

다섯 규칙을 한꺼번에 걸지 않고 순서대로 적용했다.

1. 은행별로 나눈다
2. 문자셋을 맞춰 다시 올린다
3. 분할 입금은 부분 입금 항목으로 대상을 바꾼다
4. 이관 회원은 member_map 을 거쳐 조인한다
5. 양방향 1:1 확인

각 단계에서 매칭되는 건수가 얼마나 느는지를 COUNT(*) 로 그때마다 셌다.

한꺼번에 거는 것 대신 하나씩 벗기는 쪽을 골랐다. 이유는 다섯을 한꺼번에 걸어 버리면 어느 규칙이 몇 건을 설명했는지 알 수 없어서다. 하나씩 벗겨 내니 규칙마다의 기여가 숫자로 남았다.

검증 — 양방향 1:1 확인

매칭 스크립트에 규칙을 하나 두었다.

-- 방향 A: 은행 파일 → 우리 것
SELECT COUNT(*) FROM krw_deposits WHERE match_key = <은행 행 키>;
-- 정확히 1이어야 함

-- 방향 B: 우리 것 → 은행 파일
SELECT COUNT(*) FROM bank_rows WHERE match_key = <우리 키>;
-- 정확히 1이어야 함

krw_deposits 쪽과 bank_rows 쪽 COUNT(*) 가 둘 다 1일 때에만 매칭으로 인정한다.

krw_deposits 쪽만 재고 넘어가면 통과와 오염을 가르지 못하고 두 방향이 같은 답을 낼 때에만 짝이 하나로 정해진다.

주의 — 한 방향만 보면 다대일이 통과한다

왜 한쪽으로 부족한지는 이런 모양에서 그대로 드러난다.

은행 행 A  →  우리 신청 X   (1건, 통과)
은행 행 B  →  우리 신청 X   (1건, 통과)

A 도 B 도 각각은 1건이라 은행 쪽에서 보면 둘 다 통과한다.

반대 방향으로 보면 그 자리에서 잡힌다.

우리 신청 X  →  은행 행 A, B   (2건, 실패)

X 하나에 A 와 B 가 둘 다 붙어 있으니 실패로 걸린다.

한 방향만 봤으면 이것이 통과해 데이터가 오염된다. 입금 대조에서 그것은 한 입금이 두 신청에 잡혀 잔액이 두 번 올라간다는 뜻이었다.

결과 — 남은 몇 건

돌린 결과는 이랬다.

전체       수천 건
해결       거의 전부
남음       몇 건

남은 몇 건은 각자 개별 사정이 있는 것들이라 손으로 하나씩 봤다.

단계별로 세어 두었기 때문에 그 몇 건이 어느 단계에서 막혔는지도 함께 알 수 있었다. 처음에 세운 NULL 가설은 눈에 보이는 것이었고 실제 원인은 코드를 읽어야 나오는 쪽에 있었다.

정리


Share this post on:

Previous Post
24시간 멈추면 안 되는 시스템을 맡는다는 것
Next Post
먼저 뜬 것이 나중에 내려가야 했다