배경
우리 서비스에서는 주기적으로 앱 사용자들에게 큐레이팅 정보로 랜딩되는 푸시알림을 보낸다. 얼마 전부터 앱푸시 열람수가 많은 경우에 서버장애가 발생하여, 원인을 파악하고 개선했던 경험을 정리해보았다.
우선 AWS RDS 성능개선도우미에서 AAS 가 높은 SQL을 조회해보았다. 이상한 점은 기존에 레디스 캐싱이 ttl: N분으로 적용되어있음에도 호출 수가 수백여 건인 것이었다. 캐싱이 만료된 시점에 거의 동시에 요청이 몰려 캐싱이 되기 전에 캐시 값을 계산하기 위한 로직이 요청 수 만큼 실행된 것으로 예상되었다.
본론
이 문제를 방지하기 위한 두 가지 전략을 사용하였다.
첫 번째는 확률적 재계산으로, ttl이 N분으로 설정한 경우 캐시가 만료되기 M(M<N)분 전부터 임의의 확률로
- 기존이 캐싱값을 가져가거나
- 재계산 후 해당 값을 캐싱하는 것이다.
이렇게 하면 M분 이후에 요청이 몰리더라도 (확률에 따라) 캐시값을 계산하기 위한 로직의 실행 횟수를 요청수보다 적게 가져갈 수 있다.
단점은 백그라운드에서 실행되는 게 아니라 클라이언트의 요청이 발생해야 적용되는 것인데, 이번 작업의 경우 아주 빈번하게 요청되는 API였기에 문제가 되지 않았다.
두 번째는 레디스 분산락이다. 캐시값을 계산하기 전 레디스로부터 락 획득을 시도하여,
- 성공한 경우에만 계산 후 캐싱을 하고, 락을 해제한다.
- 실패한 경우에는 일정 시간동안 대기하여 캐시값을 다시 가져간다.
이번 작업의 경우에는 일정 시간 후에도 캐시값이 없는 경우에 오류를 발생시키거나 유효하지 않은 데이터를 응답할 수 없었기에, 일정 시간 후에는 결국 재계산을 하도록 구현했다.
위 작업을 통해 앱 푸시가 발송된 후에도 안정적으로 운영되는 것을 확인했다.
'배운 것들 > 그 외' 카테고리의 다른 글
| Nest.js의 인터셉터를 이용하여 인풋 필드에 권한 체크를 추가하기 (2) | 2025.12.24 |
|---|---|
| Elasticsearch 로 기업 검색 구현하기 (2) | 2025.11.06 |
| GraphQL 요청당 필드 캐싱으로 인한 이슈 해결 (1) | 2025.06.27 |
| Elasticsearch 를 활용한 프로젝트를 경험하면서 느낀 점 (2) | 2025.06.26 |
| TypeScript 프로젝트에서 외부 파일을 이용할 때 경로 처리 (3) | 2025.04.12 |