은행 입금 동의 누락·알림 실패 건 자동 재처리로 개선
목차
은행 파트너 입금 처리 흐름에서 두 가지를 손봤다. 개인정보 동의 처리 로직 분리, 그리고 미처리 알림 자동 재처리 큐 추가. 작업 규모 자체는 크지 않은데 배경이 좀 있어서, 비슷한 상황에서 참고할 수 있게 정리해둔다.
왜 손봐야 했나
미처리 건이 매일 한두 건씩 쌓이고 있었다. 운영팀에서 수기로 정리하고 있었는데, 원인을 추적해보니 두 갈래로 나뉘었다.
| 케이스 | 원인 | 비중 |
|---|---|---|
| 동의 누락 | 폼 → 핸들러 전달 시 플래그 유실 | 약 60% |
| 알림 실패 | 외부 통신 타임아웃 | 약 40% |
수기 처리는 두 가지 문제가 있다. 휴먼 에러가 잦고, 영업일에만 돌아간다. 주말에 쌓인 건이 월요일 아침까지 밀리는 게 실질적으로 가장 컸다. 금융 흐름에서 미처리가 하루 이틀 방치되면 사용자 문의로 들어오거나 정산 불일치로 이어지는 케이스가 생기고, 그때부터는 원인 추적까지 얽혀서 처리 비용이 배로 늘어난다. 자동화가 늦어질 이유가 없었다.
동의 누락 케이스를 더 파보니 구조적 문제였다. 신청 폼에서 동의 체크박스 값을 받아 세션에 담고, 핸들러가 그 값을 꺼내서 은행사 API 호출 시 동의 이력을 함께 넘겨야 정상 처리되는 흐름이었는데, 이 전달 과정에서 플래그가 중간에 소실됐다. 폼 단에서는 체크가 됐지만 핸들러 입장에서는 값이 없으니 동의 미제출로 처리되고, 은행 측 검증에서 걸렸다. 케이스의 60%가 여기서 나온다는 게 처음엔 의외였는데, 신청 경로가 여러 개라 핸들러 경유 방식이 경로별로 달랐고 일부 경로에서만 플래그가 유실되는 구조였다.
어떻게 풀었나
핵심 변경은 동의 이력 저장을 입금 트랜잭션과 같은 원자 단위로 묶은 것이다. 기존 흐름에서는 동의가 폼 처리 시점에 기록되고, 입금은 은행사 호출 시점에 확정됐다. 두 단계 사이에 어떤 이유로든 상태가 어긋날 수 있는 구조였다. 동의는 됐는데 입금이 안 된 건, 입금은 됐는데 동의 이력이 없는 건, 이런 중간 상태가 쌓이면서 미처리 건을 만들어냈다.
입금 신청 → 동의 스냅샷 저장 → 은행사 호출 → 결과 반영
↑ 실패 시 동의 스냅샷도 함께 롤백
동의 스냅샷을 먼저 쓰고 은행사 호출이 실패하면 둘 다 롤백되니, 어느 시점에 프로세스가 죽어도 불일치 상태가 남지 않는다. 감사 로그 목적으로는 컨트롤러 단에서 신청 타임스탬프와 동의 타임스탬프를 함께 받도록 시그니처를 정리했다. 이슈 추적 시 두 값을 비교하면 어느 단계에서 어긋났는지 바로 보인다.
재처리 배치는 단순하게 갔다.
- 미처리 큐 조회 후 케이스별 분기
- 동의 누락 건은 동의 이력 재기록 후 은행사 재호출
- 알림 실패 건은 알림 발송만 재시도
- 3회 이상 실패 시 운영 알림으로 에스컬레이션
# 재처리 배치 의사코드
def process_pending_queue():
for item in fetch_pending_items():
if item.type == "consent_missing":
rewrite_consent_history(item)
result = call_bank_api(item)
elif item.type == "notification_failed":
result = resend_notification(item)
if not result.success:
item.retry_count += 1
if item.retry_count >= 3:
escalate_to_ops(item)
else:
requeue(item, backoff=exponential(item.retry_count))
재시도 간격도 고민 포인트였다. 알림 실패의 경우 외부 통신 일시 장애가 원인이면 즉시 재시도는 의미가 없다. exponential backoff를 적용해서 배치 주기와 맞물리도록 했는데, 결과적으로 1분 - 5분 - 20분 패턴이 됐다. 이 정도면 일시 타임아웃 상황은 대부분 커버되고, 그래도 실패면 구조적 문제라고 봐도 된다.
3회 임계치는 아직 감으로 잡은 숫자다. 외부 통신 타임아웃 케이스의 경우 경험상 1-2회 이내 대부분 해소되니 3회면 충분하다고 봤는데, 이건 운영 데이터 보고 조정할 예정이다. 재처리 큐에서 임계치 설정은 항상 트레이드오프다. 너무 낮으면 정상 회복 가능한 건을 운영 알림으로 올려서 노이즈가 되고, 너무 높으면 실제 이상 케이스를 늦게 잡는다. 외부 연동의 특성, 평균 회복 시간을 같이 보면서 맞춰가는 수밖에 없다.
배운 점
리팩터링하면서 가장 선명하게 정리된 건, "폼에서 받은 값"과 "외부 시스템에 보낼 값"의 라이프사이클을 다르게 봐야 한다는 거다. 같은 동의 플래그여도 폼 단계에서는 사용자의 의사 표시고, 핸들러 단계에서는 감사 로그 레코드다. 이 둘을 한 변수로 흘려보내면 어느 시점에 값이 바뀌거나 사라졌는지 추적이 안 된다. 특히 신청 경로가 여러 개일 때는 각 경로마다 라이프사이클 어느 단계를 탔는지 명시적으로 설계해두지 않으면 비슷한 버그가 계속 생긴다.
금융 도메인에서 이 문제가 두드러지는 이유는 외부 시스템 의존성이 강하기 때문이다. 자체 DB에만 쓰는 흐름이면 트랜잭션 경계가 명확한데, 은행사 API 같은 외부 호출이 중간에 끼면 경계를 어디에 그을지 더 신경 써야 한다. 외부 호출은 결과를 보장할 수 없으니, 호출 전 상태와 호출 후 상태를 구분해서 저장하고 실패 시 어느 쪽을 롤백할지 미리 정해둬야 한다. 이번에 동의 스냅샷을 트랜잭션 안으로 가져온 게 그 맥락이다.
테스트는 미처리 케이스를 의도적으로 만든 픽스처를 깔고 돌렸다. 운영에서 실제 발생했던 케이스를 재현한 데이터라 커버리지가 실질적이었다. 이론적인 엣지 케이스 테스트보다 훨씬 빠르게 회귀 버그를 잡아준다. 운영에서 일주일 더 모니터링하면서 재시도 임계치와 배치 주기를 같이 볼 예정이다. 재처리 큐는 만들었을 때보다 운영하면서 파라미터를 다듬을 때 진짜 세팅이 완성되는 면이 있어서, 지금은 초안이라고 보는 게 맞다.
댓글 0
첫 댓글 달아줘.