기본 흐름 구성
우리 서비스에서는 특정 이벤트 발생 -> Producer가 SQS에 메시지를 Enqueue -> Consumer가 Polling으로 Dequeue 후 알림톡 발송 -> 성공 시 메시지 삭제 흐름으로 알림톡 모듈을 구성하였다.
과금 방식
그리고 과금(잔액 차감)은 "큐에 들어간 순간 바로 차감"이 아니라, Enqueue 시점에는 예약 차감 & 임시 과금 상태를 저장하고 Dequeue 후 발송 성공 시점에 확정 차감 & 실제 잔액을 차감하는 식으로 구성하였다.
Enqueue 단계에서 잔액을 바로 차감하지 않는 이유를 설명하자면,
SQS는 기본적으로 적어도 한 번 전달될 수 있고, 네트워크/타임아웃/컨슈머 장애로 인해 같은 메시지가 중복 처리될 가능성이 있다. 또한, 외부 알림톡 벤더 API는 언제든 일시 장애가 날 수 있고, 그러면 발송은 지연되거나 재시도될 수 있다. 만약 Enqueue 순간에 바로 잔액을 차감해 버리면 발송 실패/지연이 발생했을 때, 차감은 됐는데 발송은 안 되는 불일치가 생길 수 있고, 중복 메시지 처리 시, 동일 요청에 대해 잔액이 2번 차감될 위험이 있다. 이때 실패 케이스를 복구하려면 이미 차감된 돈을 다시 돌려주는 복잡한 보상 트랜잭션이 필요해진다. 그래서 우리는 Enqueue에서는 예약 차감만 하고, Consumer가 실제 발송을 끝낸 뒤에만 확정 차감 하도록 분리하였다.
추가로, 상태 전이(RESERVED -> CONFIRMED)를 둔 이유를 설명하자면,
1. 멱등성(idempotency)을 확보하기 위해
- SQS 메시지는 같은 요청이 중복으로 들어오거나(프로듀서 재전송), 컨슈머가 처리 중 장애가 나서 다시 처리될 수 있다. 이때, 임시 과금 테이블의 STATUS가 이미 CONFIRMED라면, 이 요청은 이미 확정 처리된 것으로 간주하고 성공 처리(재차감 방지) 하도록 만들 수 있다.
2. 장애 복구 시 재처리 기준점을 확보하기 위해
- RESERVED 상태와 CONFIRMED 상태의 구분이 있으면 장애가 났을 때에도 판단이 명확해진다.
- RESERVED만 남아있으면 발송 실패 or 미처리된 상황이므로 재시도 or 보상(예약 해제) 대상
- CONFIRMED면 이미 끝났으므로 중복 처리 X
3. 운영 관점에서 추적/감사(Audit) 가능
- 사용자의 "돈은 차감됐는데 왜 알림톡이 안 왔나요?"와 같은 질문에 답하려면 상태 전이가 있어야 추적이 가능하다.
가용 잔액 체크
Enqueue 시에는 가용 잔액을 체크하는데, 이때 예약 차감이 있는 경우까지 고려하기 위해, 예약 차감액을 위한 컬럼을 두었다.
이렇게 계산하는 이유를 간단한 예시로 설명하자면,
고객 A에게 잔액이 1000원이 있고, 알림톡 1건당 100원이 차감된다고 하자. 이때, 고객 A에게 알림톡을 발송해야 하는 이벤트가 5번 발생했고, 해당 알림톡이 Enqueue까지만 된 상황이라면, 예약 차감액(HOLD)이 500원이고, 잔액은 아직 차감되지 않았기 때문에 1000원이 그대로 남아있다. 하지만, 예약 차감액 500원이 있기 때문에, 추가 알림톡 발송은 최대 5건만 가능하다. 즉, 잔액은 1000원이지만 실제로 사용 가능한 금액은 500원인 것이다. 이 상황에서 알림톡을 추가로 6건 발송하려고 한다면 그 6번째 요청은 처리되지 않아야 한다.