케이뱅크 송금 알림 파서 분리로 정산 누락 해결
목차
케이뱅크 파트너 비중이 올라오면서 결국 손을 댔다. 시중은행 두 곳으로 버티던 게 한계에 왔고, 정산 누락 케이스가 서서히 쌓이는 걸 더 두고 볼 수 없었다.
수신 흐름 자체는 단순하다.
- 고객이 송금 → 메신저로 알림 도착
- 수신 컴포넌트가 메시지 캡처
- 파서가 금액/송금인/받는계좌 추출
- 매칭 큐에 적재 후 holds 처리
얼핏 보면 파서 하나가 은행별 포맷만 커버하면 끝날 것 같다. 그런데 실제로는 그렇지 않다. 은행마다 알림 포맷이 사내 표준 없이 제각각 자라왔고, 업데이트 시점도 제멋대로다. 케이뱅크가 특히 그랬다.
포맷 차이가 왜 이렇게 무서운가
케이뱅크 알림이 기존 은행과 뭐가 다른지 처음엔 대수롭지 않게 봤다. 실제로 나란히 놓고 비교하면 이런 식이다.
# 기존 은행
[입금] 홍*동 / 1,234,000원 / 계좌 1234-56 / 13:22
# 케이뱅크
케이뱅크 입금
홍○동 님이 보내셨어요
금액: 1234000 원
받으신 분: 1234-56****
2026.03.19 13:22:10
줄바꿈 위치부터 다르고, 통화 표기가 1,234,000원이 아니라 1234000 원으로 오고, 마스킹 문자가 *이 아니라 ○다. 계좌 마스킹 자릿수도 다르다. 날짜 포맷도 한 줄로 따로 분리되어 내려온다.
이 상태에서 기존 단일 정규식에 케이뱅크 알림을 밀어 넣으면 부분 일치가 일어나는 케이스가 생긴다. 운 나쁘게 금액 필드가 빈 문자열이 아니라 다른 필드 값으로 채워지기도 한다. 더 무서운 건, 매칭 큐에 들어갈 때 유효해 보이는 데이터처럼 보인다는 거다. 파서가 명시적으로 실패를 던져줬으면 바로 잡을 수 있는데, 부분 일치로 엉뚱한 값을 들고 통과해버리면 파서 레이어에선 에러가 없다. 매칭 단계에서 틀어지거나, 아니면 정산이 밀릴 때 뒤늦게 발견한다. 후자가 훨씬 나쁘다.
이게 결국 "정규식 한두 줄만 추가하면 되는 작업"이 아니라는 걸 1차 릴리스 직후 롤백으로 배웠다. 케이뱅크 예외를 기존 파서에 끼워 넣었더니 기존 은행 포맷 중 특정 패턴에서 부분 일치가 발생했다. 스테이징 트래픽 수준이 낮아서 통과했다가 라이브에서 잡혔다.
메시지 파서 분리
롤백 후 구조부터 다시 봤다. 기존 파서는 단일 클래스에서 은행 타입에 따라 분기를 때려 넣는 구조였다. 처음엔 if bank == "A" 블록 하나였는데 은행이 두 개 되면서 이미 두 겹이었고, 케이뱅크를 추가하면 세 겹이 된다. 여기에 케이뱅크용 예외 처리를 끼워 넣는 순간 기존 패턴이 흔들릴 가능성이 구조적으로 내재된다.
그래서 발신자 식별 후 은행별 파서로 라우팅하는 방식으로 분리했다.
| 항목 | Before | After |
|---|---|---|
| 정규식 위치 | 단일 클래스 하드코딩 | 은행별 파서 분리 |
| 신규 은행 추가 | 기존 정규식 수정 → 회귀 위험 | 새 파서 추가 → 격리 |
| 테스트 | 통합 케이스만 | 은행별 단위 테스트 |
| 오류 추적 | 어떤 은행인지 불명 | 파서 타입으로 즉시 식별 |
[기존] 메시지 → 거대 정규식 → 결과
[개선] 메시지 → 발신자 식별 → 은행별 파서 → 결과
핵심은 기존 파서를 전혀 건드리지 않는다는 거다. 케이뱅크 파서는 독립 클래스로 추가했고, 기존 두 은행 파서는 코드 한 줄도 안 바꿨다. 회귀 리스크를 구조적으로 막는 것 자체가 목표였고, 그 측면에선 원하는 대로 됐다.
트레이드오프도 있다. 파서 인터페이스가 통일되어 있어야 하고, 라우터가 발신자 식별 규칙만 담당하는 구조여야 이게 유지된다. 처음부터 이렇게 설계했으면 좋았겠지만, 은행이 하나였을 땐 오버엔지니어링처럼 보였다. 두 번째 은행 붙일 때 분리했어야 했는데, 그땐 "정규식 한 줄" 유혹을 이겨내지 못했다. 세 번째에야 제대로 손댄 셈.
은행별로 파서가 격리되면 단위 테스트도 훨씬 쓰기 편해진다. 케이뱅크 파서를 테스트할 때 다른 은행 케이스를 신경 쓸 필요가 없다. 실제 알림 원문 몇 개를 픽스처로 넣어두고 필드 추출 결과만 검증하면 된다. 이 픽스처들이 나중에 포맷 변경 감지용으로도 쓰인다.
알림 수집기 개선
파서 분리만 하고 끝낼 수도 있었는데, 이번에 같이 손댄 게 수집기 쪽이다. 기존엔 알림 1건당 처리 1건 구조였는데, 메신저가 묶음 푸시를 던지는 경우 뒤쪽 알림이 유실되는 버그가 있었다. 고트래픽 시간대에 집중적으로 터졌다.
개선한 부분:
- 멀티 알림을 라인 기준으로 분할 후 개별 파싱
- 파싱 실패 메시지는 폐기하지 않고 별도 큐로 격리 (수동 재처리용)
- 동일 송금 중복 수신 방지를 위해 메시지 해시 + 수신 시각 윈도우로 디듀프
- 파서 실패율을 메트릭으로 노출 → 포맷 변경 즉시 감지
디듀프의 시각 윈도우는 짧게 잡는 게 포인트다. 완전 동일한 메시지가 수초 이내에 두 번 오는 경우만 걸러야 하고, 시간이 충분히 지난 같은 금액 송금은 별건으로 처리해야 한다. 윈도우를 길게 잡으면 정상 송금을 중복으로 판정한다. 해시는 금액+송금인+계좌 조합으로 만들고, 발신 시각 필드가 있으면 거기까지 포함한다.
실패 격리 큐가 의외로 효과적이었다. 포맷이 바뀌어서 파서가 못 잡는 경우, 버리면 영영 추적 불가능한데 큐에 쌓아두면 포맷 파악 후 재처리할 수 있다. 이번 케이뱅크 추가 과정에서도 초기 패턴이 맞지 않아 격리된 메시지들을 이 큐에서 꺼내 분석했다. 그 샘플들이 정규식 다듬는 데 결정적이었다.
파서 실패율 메트릭은 붙여두길 잘했다. 은행이 포맷을 슬쩍 바꾸는 경우, 알림이 오다가 갑자기 실패율이 튀면 바로 보인다. 정산이 밀리고 나서 원인 추적하는 것보다 훨씬 낫다.
회고
은행별 메시지 포맷 차이를 얕봤다가 1차 릴리스 직후 롤백한 게 뼈아팠다. 정규식 한 줄 추가는 쉬워 보이지만, 송금 알림은 한 글자만 못 잡아도 매칭 실패가 누적되고 파트너 정산이 통째로 밀린다.
이번에 정착된 원칙 하나: 새 은행 붙일 때 기존 파서는 건드리지 않는다. 새 파서를 추가하고, 라우터에 발신자 식별 규칙만 끼워 넣는다. 말로는 간단하지만 이걸 지키려면 파서 인터페이스가 처음부터 통일되어 있어야 하고, 라우터가 선택만 담당해야 한다. 은행 하나짜리 시스템에서 이 구조를 설계해두는 건 과한 것처럼 보인다. 그래서 항상 두 번째 은행에서 결정이 갈린다. 그때 분리해두지 않으면 세 번째에서 롤백한다. 이번이 딱 그 패턴이었다.
다음 추가할 은행은 또 어떤 변종 포맷을 들고 올지 벌써부터 의심스럽다. 적어도 이번엔 새 파서 파일 하나 추가하면 끝나는 구조가 됐으니, 기존 정규식 건드리다 회귀 내는 상황은 막을 수 있을 것 같다.
댓글 0
첫 댓글 달아줘.