> For the complete documentation index, see [llms.txt](https://wonjoon.gitbook.io/joons-til/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wonjoon.gitbook.io/joons-til/trustay/documentation/resolving-duplicate-notification-bursts-with-redis-based-rate-limiting.md).

# Resolving Duplicate Notification Bursts with Redis-Based Rate Limiting

(사내 보안 규정에 따라 실제 서비스·테이블·코드 식별자는 모두 일반화되어 있습니다)

## 배경:

기존 구조에서는 도메인 이벤트가 발생하면, \
**알림탭에 쌓이는 알림**과 **유저 기기로 발송되는 푸시**가 자동으로 생성되는 구조였습니다.

**이벤트 발생 빈도가 높은 특정 케이스**에서는 중복 알림 발송 현상이 드러나기 시작했습니다.

* 팔로잉한 유저가 짧은 시간 안에 여러 글을 연속으로 썼을 경우
* 팔로우/언팔로우가 반복되는 경우

이로 인해 사용자 입장에서는

* 알림 센터에 유사한 알림이 연속으로 쌓이고,
* 푸시 알림이 짧은 시간 동안 여러 번 도착하는

**알림 폭탄 문제**가 발생했습니다.

## 원인 파악 - '중복'이 아니라 '빈번한 발생'의 상황.

이벤트는 **'정상적으로'** 여러 번 발생하고 있었고, 시스템 관점에서는 잘못된 이벤트가 아니라는 것을 확인했습니다.

기능적 오류는 아니라는 것을 확인하였고, "정책 문제"에 가까웠습니다.

## 해결 방법

해결 전략은 **"중복을 막자"**&#xBCF4;다는, \
**"같은 유형의 알림은 일정 시간에 한 번만 보내자"** 였습니다.

이를 위해 고민한 방법들은 아래와 같습니다.

### DB Unique 제약으로 중복 차단(X)

알림 테이블에 Unique 제약을 두어 중복 생성을 원칙적으로 막는 방식.

* 중복의 기준을 컬럼 조합으로 판단하기가 어려웠고,
* 정책 변경시 스키마 변경이 발생할 수 있으며,
* 충돌 발생시 예외처리나 재시도 로직이 복잡해질 것,

정책 문제에 비해 **오버엔지니어링라고 판단되고, 정책 변경에 대한 유연성이 떨어진다고** 판단했습니다.

### 알림 생성 시, 최근 알림 조회(X)

알림Row 생성 시간을 확인하는 방법으로, 최근 비슷한 알림을 DB에서 조회 후 판단할 수 있습니다.&#x20;

* 알림 생성 시마다 추가 RTT와DB 부하 증가. 대량 트래픽 상황에서는&#x20;
* 쿼리 성능을 위해 테이블에인덱스가 추가되어야 했고, 데이터삽입시 부담 증가.

중복을 막기 위해 DB를 더 사용하는 구조는,\
문제를 해결하기보다 **새로운 병목을 만들 가능성**이 높다고 판단했습니다.

### 메시지 큐 기반 처리(X)

정확히 한 번만 알림을 발송하도록 보장하는 방식으로, 중복 제거에 많이 사용됩니다.

* 같은 이벤트가 중복으로 처리되어 같은 알림이 두 번 생성되는 상황에 사용합니다.
* 식별 가능한 ID를 통해 처리된 작업인지 구별합니다.

이 방식은 ‘같은 이벤트가 여러 번 처리되는 문제’를 해결하는 방법이지,\
‘**정상 이벤트가 자주 발생해 알림이 많이 가는 문제**’를 해결하는 방식은 아닙니다.

### Redis 기반 Rate Limit(O)

키 존재 여부만 빠르게 판단할 수 있으면 충분했기 때문에,&#x20;복잡한 데이터 구조 대신 단순한 키 기반 접근을 사용했습니다.

* **빠르고 가벼운 제어**: Redis는 인메모리 NoSQL 저장소이고, 초당 수천 건 조회에도 부담이 적고,
* **자동 만료**:TTL 기반 자동 만료를 통해 관리가 쉬웠고,
* **장애 영향 범위 분리**: DB 트랜잭션과 분리가 가능했으며,
* **추가 인프라 비용 없음:** 새로운 기술 도입이 아닌, 기존에 Redis를 사용하고 있었고,
* 정책 변경이 쉬웠습니다.

가장 단순하고 해당 문제에 적합한 해결책이었습니다.

## 핵심 설계

알림 생성 전에 Redis에 발송 이력 키를 기록 (  `rate_limit: {notification_type}:{target_id}:{date}` )

키가 존재하면 알림 생성을 스킵하고, 키가 없으면 알림 생성 후 TTL(내부적으로 논의)과 함께 저장했습니다.

* 새 글 알림: 1일
* 팔로우 알림: 1분

#### 적용 위치

Event Subscriber 레벨에서 적용하였습니다.&#x20;

* 이벤트는 발생했지만, 알림을 생성할지 말지 결정.
* **추후 사용처가 많을 것 같아서, 별도의 클래스로 정의**하였습니다.
* 정책 변경에 유연한 대처를 위해 **타입별로(ENUM) TTL을 한곳에 모아 관리할 수 있게 설계**했습니다.

## 결과

* 동일 유형 알림의 중복 발송이 제거됐습니다.
* 정책 계층만 추가하여 문제를 해결하였고,
* 유틸리티 메서드를 활용해서 특정 알림 유형에만 선택적으로 적용할 수 있게 되었습니다.
