최근 알림톡 발송 금지 시간대에 메시지가 발송되고,
TPS 초과로 실시간 알림톡이 누락되는 문제가 발생했다.
해당 문제를 어떻게 인지하고, 해결해 나갔는지 정리하고자 한다.
알림톡이란
알림톡은 기업이 고객에게 카카오톡을 통해 자동으로 전송하는 정보성 메시지이다
예를 들어, 보험료 납부 알림이나 상담 신청 확인 메시지가 이에 해당한다.
- 정기 발송 알림톡: 특정 시간에 스케줄링되어 대상자에게 발송됨
- 실시간 발송 알림톡: 사용자의 행동(예: 상담 신청, 보험 청구) 발생 시 즉시 발송됨
발송 금지 시간대 알림톡 발송 로직 누락 해결하기
배경
알림톡은 정보성 메시지이기 때문에 광고성 메시지는 아니다.
따라서 보내고자 하는 알림톡은 정보성 메시지인가 광고성을 띠지 않는가 검수 과정이 필요한데 이는 사람이 하는 작업이기 때문에 아무리 승인 받은 메시지라고 하더라도 광고성을 띤 메시지가 승인될 수 있다. 따라서 그 책임 발송하는 사람에게 있다.
그래서 우리 회사에서는 법적인 문제를 포함해서 받는 유저 입장을 고려해서 발송 금지 시간대를 정해놓았다.
오전 10시 이전과 오후 7시 이후에는 정기 발송 알림톡이 발송되어서는 안된다.
문제
발송 금지대 시간에 정기 발송 알림톡이 나가게 되었다.
원인
- 정기 알림톡의 발송 시간이 코드에 @Scheduled로 하드코딩되어 있었고, 한 개발자가 실수로 새벽 시간으로 수정해 커밋되었다.
- 발송 금지 시간대 체크 로직이 누락된 알림톡이 존재하였다. 새로운 알림톡마다 개별 메서드로 작성되어 공통 로직 없이 중복 구현되어 있었다.
해결 방안
- 먼저 코드로 관리되던 스케줄을 DB에서 관리하도록 구조를 개선했다. 그렇게 함으로써 각 알림톡마다 @Scheduled를 통해서 언제 보내야 하는지 파악해야 하는 문서화의 어려움과 실수로 수정할 수 있는 부분, 그리고 알림톡 시간이 변경될 때마다 배포가 이뤄져야 했던 문제들을 해결할 수 있었다.
- 발송 금지 시간대 체크 부분을 필수 로직으로 만들기 위해서 공통 로직을 만들었다. 이를 위해서 전략 패턴을 사용하게 되었다. 알림톡이 추가 되어도 공통 로직은 건들 필요가 없다. 해당 공통 로직은 알림톡의 템플릿 코드가 무엇인지만 알면 알아서 구현체를 호출해준다. 동적으로 구현체가 바뀌는 것이다.
따라서 아래와 같은 순서도가 완성되었다.

- 외부 API의 평균 응답 시간을 고려하여 비동기 대기큐의 크기를 고려하였으며
- 외부 API의 TPS를 고려하여 1초의 간격으로 요청이 갈 수 있도록 요청을 동시에 보내기 위해서 비동기로 호출하고 마지막에 해당 비동기 호출들의 응답이 돌아올 때까지 기다렸다.
중앙에서 관리되지 않던 TPS를 한곳에 관리하기
문제
- 실시간 발송과 정기 발송이 동시에 발생할 경우 TPS(초당 처리량) 초과 문제가 발생하였다.
- 정기 발송(빨간선)은 TPS를 맞춰 전송되지만, 실시간 발송은 별도 제어 없이 발송하여 정기 발송 중 실시간 발송이 추가로 발생하면 TPS 초과되었다.
원인

실시간 발송과 정기 발송의 TPS가 한곳에 관리되지 않았기 때문이다.
해결방안

- 외부 API의 TPS를 안정적으로 제어하기 위해 DB에 queue 테이블을 생성해 발송 요청을 통합 관리하는 구조로 개선하였다.
- 실시간 및 정기 발송 이벤트가 발생하면, 해당 요청을 queue 테이블에 저장하도록 구성하였다.
- 이후 queue에서 priority와 is_sent 값을 기준으로 정렬한 뒤, 외부 API의 TPS에 맞춰 순차적으로 발송하도록 처리하였다.
- 정기 발송의 priority는 발송 스케줄 시각을 분 단위로 환산한 값을 사용하였다.
- 실시간 발송의 priority는 항상 0으로 설정하여, 우선 발송되도록 하였다.

알림톡 발송 비동기 구조

중점적으로 살펴본 부분
- 외부 서비스의 부하나 장애로 인해 응답이 지연될 경우, 대기 큐가 터지면서 발송 완료 처리 쿼리가 롤백되고, 그로 인해 중복 알림톡이 발생할 수 있는 위험이 있다. 이를 방지하기 위해, 메시지를 발송하기 전 is_sent 값을 true로 먼저 업데이트하는 구조로 변경했다.
이때 해당 업데이트 쿼리가 외부 API 호출과 같은 트랜잭션에 묶여 있으면 롤백 시 다시 중복 발송될 수 있기 때문에, 트랜잭션이 분리하였다.
- 실시간 발송과 정기 발송이 동시에 다수 발생할 경우, 발송 누락 또는 중복 가능성이 존재하므로, 하루 예상 발송량을 기준으로 부하 테스트를 진행해 안정성을 검증했다.
- 중복 발송에 대한 대응이 가능하도록, 메시지를 생성할 때 페이지 단위로 처리하며, 각 페이지 전환 시마다 해당 메시지의 발송 완료 여부를 체크하는 구조를 도입했다.
- 외부 API의 평균 응답 시간이 실제로 우리가 기대하는 수준(p95, p99 등)에 부합하는지 확인하기 위해, 요청 시각(request_sent_at)과 응답 시각(response_received_at)을 함께 저장하여 운영 환경에서 레이턴시를 직접 측정할 수 있도록 로그를 구성했다.