Redis를 통한 API 중복요청 방지

Y_Sevin·2023년 12월 6일
0

현재 프로젝트의 AI서버는 굉장히 큰 모델을 구동하기에 단일 스레드로 구성되어있다.. 뿐만 아니라 요청 하나를 처리하는데 3~4초의 시간이 소모되는데 다시 말해 10명이 동시에 요청을 보내면 마지막 사람은 30초정도 기다려야하는 상황이 발생한다.
하지만 요청 하나의 비용을 줄이기에는 비용 문제와 모델링 문제가 엮어져 있어 어려운 상황이다. 때문에 요청 비용을 줄일 수 없다면, 요청에서 발생할 수 있는 다양한 성능 저하 상황이라도 막아보자는 마음으로 Redis를 통해 API 중복요청 방지를 적용한 내용을 공유해보고자 한다.

왜 Redis?

Redis는 Key-Value 구조의 비정형 데이터를 저장하고 관리하는 인메모리 저장소이다. 다시 말해 모든 Redis 데이터가 메모리에 저장됨을 의미하며, 따라서 대기 시간이 낮고 처리량이 높아 빠른 입출력이 가능하다.

또한, Redis는 핵심 처리 부분이 Single Thread로 구성되어 있어 원자적(Atomic)이고 Thread-safe한 특징을 지닌다. 때문에 동시 접근 시 Race Condition이 발생할 가능성을 줄여준다.

이러한 특성으로 인해 Redis는 실시간 데이터 처리 및 빠른 응답이 필요한 작업에 매우 적합하다. 특히, 중복 요청 제거와 같은 작업에서 Redis의 성능은 두드러지는데, 이는 Redis가 빠르게 데이터를 저장하고 조회할 수 있기 때문이다.

과정

Spring Boot 프로젝트 설정

Redis 의존성을 추가하고 및 연결 정보를 설정

implementation 'org.springframework.boot:spring-boot-starter-data-redis'
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        final RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        template.setValueSerializer(new GenericToStringSerializer<>(Object.class));
        return template;
    }
}

Redis를 이용한 중복 요청 필터 구현
Spring의 OncePerRequestFilter를 확장해 중복 요청을 필터링하는 코드를 작성

OncePerRequestFilter는 하나의 요청에서 한번만 Filtering 하는 filter


@Component
public class DeduplicationFilter extends OncePerRequestFilter {

    private static final String REQUEST_KEY_PREFIX = "request:";
    private static final long EXPIRATION_TIME_SECONDS = 3;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String userId = request.getHeader("Authorization");

        if (userId != null) {
            String requestKey = REQUEST_KEY_PREFIX + userId + ":" + request.getRequestURI();
            boolean isDuplicate = !redisTemplate.opsForValue().setIfAbsent(requestKey, "true", EXPIRATION_TIME_SECONDS, TimeUnit.SECONDS);

            if (isDuplicate) {
                response.setStatus(HttpServletResponse.SC_CONFLICT);
                return;
            }
        }

        filterChain.doFilter(request, response);
    }
}

Spring 애플리케이션 설정

필터를 등록하고 적용할 URL 패턴을 설정

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<DeduplicationFilter> requestFilter() {
        FilterRegistrationBean<DeduplicationFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new DuplicateRequestFilter());
        registrationBean.addUrlPatterns("/api/editor/*");
        registrationBean.addUrlPatterns("/api/추가할엔드포인트/*");
        return registrationBean;
    }
}
profile
매일은 아니더라도 꾸준히 올리자는 마음으로 시작하는 개발블로그😎

0개의 댓글