개요
현재 진행중인 개인 프로젝트에선 Rate Limiter 기능이 적용되어있지 않습니다.
이 경우 만약, 악성 사용자가 서버를 다운시키기 위해 짧은 시간 내에 다량의 영상을 업로드할 경우, 서버에 심각한 부하가 발생할 수 있고, 한 영상에 매크로성 댓글이 여러 개 달리면 사용자 경험에 부정적인 영향을 줄 수 있습니다.
이러한 문제들을 예방하고자 Rate Limiter를 적용하기로 결정했습니다.
Rate Limiter를 적용하기 전 고민이 있었는데요, Resilience4j, Bucket4j와 같이 기존에 존재하는 라이브러리를 사용해 구현할지, 아니면 Redis를 이용해 직접 구현할지 고민되었습니다.
라이브러리를 사용하면 구현이 간편하다는 장점이 있습니다. 하지만 스프링 애플리케이션 메모리를 직접 사용하기 때문에 많은 사용자의 요청 횟수를 애플리케이션 메모리에 저장하면 Out Of Memory Error가 발생해 애플리케이션 전체가 다운될 수 있다는 문제점이 있었습니다.
Redis를 사용하면 사용자 요청 횟수 정보들은 Redis 메모리에 저장되기 때문에 Max Memory를 초과해서 Out Of Memory 에러로 Redis가 다운되어도 스프링 애플리케이션에는 문제가 없기 때문에 전체 서버가 다운되는 문제는 발생하지 않습니다.
그래서 보다 안정적인 서버 운영을 위해 Redis에 직접 구현하는 방식을 선택했습니다.
구현
영상 업로드 기능에 RateLimiter를 적용한 코드입니다.
RateLimiter 기능을 직접 비즈니스단에 구현할 수 있지만, annotation을 직접 만들어 비즈니스 로직과 RateLimiter 기능을 분리시켰습니다.
RateLimiter 구현체가 하는 역할은 간단합니다.
1. 사용자 이메일 정보를 가져와서 key 값으로 설정한다.
2. rateLimiterService의 isAllowed 메서드를 호출해 가능한 요청인지 검증한다.
3. 가능한 요청이라면 메서드를 실행시킨다.
그렇다면 rateLimiter의 isAllowed 메서드는 어떻게 동작하는지 알아보겠습니다.
isAllowed 메서드는 userKey와 duration 정보를 기반으로 redis에 명령어를 던져서 결과값이 1이면 true를 반환합니다.
스크립트는 아래와 같은 방식으로 동작합니다.
1. key 값에 따른 value를 가져옵니다.
2. 만약 value가 1이라면 duration으로 설정한 기간동안 이미 요청을 보낸 것이기 때문에 0을 리턴합니다.
3. 만약 value가 1이 아니라면 duration으로 설정한 기간동안 요청을 보내지 않은 것이므로 key값에 value로 1을 설정하고 ttl을 duration으로 잡아준 뒤, 1을 리턴합니다.
이를 통해서 duration으로 설정된 기간동안 한번의 요청만 수행 가능하도록 rateLimiter가 동작하는 것입니다.
해당 명령어는 Lua 스크립트로 작성되었기 때문에 스크립트의 원자적 실행 또한 보장됩니다.
테스트
1분동안 두번의 요청을 보내서 rate limiter가 정상적으로 동작하는지 확인해보겠습니다.
첫번째 요청을 34초에 보내고, 두번째 요청을 50초에 보냈을 때 rate limiter alert가 발생한걸 보면, Rate Limiter가 정상적으로 동작한다는걸 알 수 있습니다.
결론
Redis와 Spring AOP를 이용해 Rate Limiter를 구현해보았습니다.
Lua Script로 Redis에 명령어를 전송하는 것은 처음 사용해봤는데, 원자성 보장, 동적인 코드 실행 등 생각보다 사용 범위가 넓은 것 같습니다.
하지만 원자성을 보장하는 만큼 lua script가 redis에서 실행되는동안 다른 명령어들은 실행되지 못하기 때문에 성능을 모니터링 하며 사용하는 것이 중요할 것 같습니다.
References
https://redis.io/docs/latest/develop/interact/programmability/eval-intro/
Scripting with Lua
Executing Lua in Redis
redis.io
Redis Lua Script를 이용해서 API Rate Limiter개발
안녕하세요. Item Engineering 팀 박상윤입니다. 이번 글에서는 상품 엑셀 등록 서비스 개발하고 컨슈머에서 사용할 API Rate Limiter 개발하면서 발생한 이슈를 정리한 글입니다. 이 글은 아래와 같이 구
dev.gmarket.com
'개발 > SpringBoot' 카테고리의 다른 글
Spring MVC, Spring Webflux SSE 성능 비교 (0) | 2024.12.17 |
---|---|
날짜별 조회 수 분석 기능 구현 (0) | 2024.11.04 |
S3 presigned Url 적용을 통한 영상 업로드 성능 향상 (0) | 2024.06.19 |
SpringDoc Swagger Https 설정법 (0) | 2024.04.11 |
네이티브 쿼리문 + @SqlResultSetmapping 을 이용한 조회 (0) | 2024.03.02 |