자동화 slecs

입금 큐 성공 후 주문 매칭이 자동으로 안 되던 문제 해결

목차

결제대행사에서 입금 통지가 들어오고, 큐도 정상 처리되어 SUCCESS까지 찍혔는데 파트너 화면에는 "미매칭"이 뜨는 상황이었다. 운영팀은 그 화면을 보고 매일 수동으로 매칭 버튼을 누르고 있었고, 나는 그걸 코드 리뷰 중에 우연히 들었다.

"아, 원래 그렇게 하는 거 아닌가요?" 하고 넘어갈 수도 있었는데, 같은 버튼을 반복해서 누르는 운영 행동은 항상 코드 갭을 가리키는 신호다. 일단 파봤다.

어디서 끊겼는지

구조를 따라가 보니 문제는 단순했다. 입금 통지 수신 → 큐 적재 → 큐 처리(SUCCESS 마킹)까지는 연결돼 있었고, 그 다음 "이 입금을 어떤 주문에 붙일지" 결정하는 매칭 단계가 별도 스케줄러나 수동 호출에 의존하고 있었다.

단계 기존 동작 변경 후
입금 통지 수신 큐 적재 큐 적재
큐 처리 SUCCESS 마킹만 SUCCESS 마킹 + 매칭 트리거
주문 매칭 스케줄러 대기 / 수동 큐 커밋 직후 자동

스케줄러 주기가 짧으면 그나마 낫지만, 주기가 길거나 스케줄러가 한 번이라도 미끄러지면 그 간격만큼 미매칭이 쌓인다. 매칭 지연이 실시간성 요구가 강한 주문 흐름에서 운영 공수로 전환되어 버린 셈이다.

증상을 다시 정리하면 이렇다.

  • 입금 통지는 정상 수신
  • 큐 row 상태 SUCCESS
  • 파트너 화면에는 "미매칭"으로 표시
  • 운영팀이 수동으로 매칭 버튼을 반복 클릭

트랜잭션 경계를 어디서 자를 것인가

큐 SUCCESS 분기 끝에 매칭 호출을 그냥 끼워 넣으면 제일 빠르다. 근데 그렇게 하면 큐 상태 변경과 주문 매칭이 같은 트랜잭션 안에 묶인다. 매칭 로직이 느리거나 외부 의존이 있으면 그 시간 동안 큐 row에 락이 걸린 채로 유지된다. 매칭이 실패하면 큐 SUCCESS 자체도 롤백된다. 이건 원하는 동작이 아니다.

"큐가 끝났다"와 "비즈니스가 끝났다"는 다른 사건이다. 큐 SUCCESS는 인프라 관점에서 "이 메시지는 처리됐다"는 확인이고, 매칭은 도메인 관점에서 "입금을 주문에 연결하는 비즈니스 동작"이다. 둘을 같은 트랜잭션 안에 두면 한쪽 실패가 반대편의 상태도 뒤집어버린다.

그래서 경계를 명시적으로 끊었다.

// 의사 코드
markQueueSuccess(queueId)   // 트랜잭션 A
publishMatchEvent(queueId)  // A 커밋 후 호출
matchOrder(queueId)         // 트랜잭션 B

트랜잭션 A가 커밋되고 나서 매칭 트리거를 날린다. publishMatchEvent는 현재 동기 호출로 유지했지만, 나중에 이벤트 디스패처로 바꿔도 인터페이스는 동일하다. 매칭(트랜잭션 B)이 실패해도 큐 SUCCESS는 살아있어야 하니, 매칭 호출 전체를 try/catch로 감싸고 예외는 로깅만 한 뒤 흘려보냈다. 매칭 누락분은 기존 감시 잡이 다시 주워가도록 기존 구조를 그대로 뒀다.

매칭 로직 자체는 새로 짜지 않았다. 이미 있던 매칭 로직의 진입점만 자동화한 것이라, 실제 변경 범위는 큐 처리 유틸의 SUCCESS 분기 몇 줄이 전부다.

이 패턴을 Outbox Pattern이라고 부르기도 한다. 상태 변경과 후속 이벤트 발행을 같은 트랜잭션에 묶어두면 발행 실패 시 상태도 롤백되고, 반대로 상태 커밋 후 발행 전에 프로세스가 죽으면 이벤트가 유실된다. 완전한 Outbox를 도입하면 이벤트를 DB에 같이 적었다가 별도 릴레이가 발행하는 구조가 되는데, 지금 당장 그 수준의 내구성이 필요한지는 트래픽과 요구사항에 따라 다르다. 지금은 동기 호출 + 재시도 잡 구조로 충분하다고 판단했다.

돌아보며

버그 리포트가 없었다는 게 흥미롭다. 기능 자체는 동작하고 있었고, 그냥 느릴 뿐이었으니까. 운영팀은 그게 원래 프로세스인 줄 알고 적응해버린 상태였다. 코드만 보면 이 갭이 안 보인다. 큐 처리 코드는 SUCCESS를 제대로 마킹하고 있었고, 매칭 로직도 별도로 잘 작동하고 있었다. 문제는 그 사이가 자동으로 이어지지 않는다는 것뿐이었는데, 코드 리뷰로는 잘 안 잡힌다.

운영 행동을 관찰하는 게 이런 케이스를 잡는 가장 빠른 방법이다. "왜 매번 같은 버튼을 누르고 있지?"라는 질문 하나가 시작이었다. 자동화 결함은 불편함으로 존재하다가, 그 불편함이 루틴이 되는 순간 보이지 않게 된다.

남은 숙제도 있다.

  • 자동매칭 실패율 메트릭을 별도로 추출해서 알람 연결
  • 매칭 실패 사유 코드 표준화 (현재 메시지가 자유 텍스트라 집계가 안 됨)
  • 큐 SUCCESS 후 후속 작업이 더 늘어날 경우를 대비해 단발 호출 대신 이벤트 디스패처로 정리하는 리팩터 검토

실패 사유 표준화가 제일 빨리 챙겨야 할 것 같다. 지금은 로그를 눈으로 읽어야 하는 구조라, 매칭 실패가 어떤 패턴으로 발생하는지 집계가 안 된다. 메트릭이 없으면 개선도 없다.

댓글 0

첫 댓글 달아줘.