Spring Boot 성능 개선 1-2. Redis 동시 접속 성능 개선(Feat. @Async)

최민길(Gale)·2023년 7월 18일
1

Spring Boot 적용기

목록 보기
40/46

안녕하세요 이번 시간에는 여러 요청을 동시에 처리할 때 Redis의 성능을 향상시킬 수 있는 방법에 대해서 알아보도록 하겠습니다.

결론적으로 말씀드리자면 비동기 방식으로 Redis에 접속하면 여러 요청을 동시에 처리할 때 Redis의 성능을 크게 향상시킬 수 있습니다. 즉 별도의 ThreadPoolTaskExecutor를 @Bean 주입한 후 @Async 어노테이션을 추가하는 방식으로 비동기로 스레드 풀에서 요청을 처리할 수 있습니다.

여기서 주의할 점이 하나 있습니다. @Async 어노테이션의 경우 리턴값이 void 또는 Future 타입만 받기 때문에 리턴값을 필요로 하는 경우 Future 타입을 사용해서 값을 참조해야 합니다.

Future란 비동기 계산의 결과를 나타내는 인터페이스로 게산을 비동기식으로 수행하고 메인 스레드에서 실행을 분리할 수 있는 특징이 있습니다. Future 인터페이스의 get() 메서드는 별도 스레드에서 비동기로 처리가 완료될 때까지 메인 스레드를 wait하며, 처리가 완료될 경우 결과값을 반환하여 wait을 해제합니다.

Java8부터는 CompletableFuture라는 Future 구현체를 지원합니다. 이 클래스의 경우 Future에 비해 더 유연하고 다양한 기능을 제공하며, 복잡한 비동기 로직을 간결하게 표현할 수 있습니다.

아래는 Redis에 저장된 값을 가져오는 로직을 비동기로 처리한 예제입니다. 프로토콜 버퍼를 사용하여 Redis에서 값을 가져오는 로직을 CompletableFuture 클래스로 리턴한 후 @Async 어노테이션을 추가하여 비동기로 처리하도록 합니다. 이어서 클라이언트에서 Redis에 접근 시 ComplatebleFuture.get() 메소드를 통해 결과값을 가져옵니다. 이 때 발생 가능한 예외를 try-catch 내에서 처리해줍니다.

    private UserInfoAllDao getRedis(String key) {
        try {
            return getRedisFuture(key).get();
        }
        catch (ExecutionException | InterruptedException e){
            throw new WrongAccessException(WrongAccessException.of.REDIS_CONNECTION_EXCEPTION);
        }
    }

    @Async
    private Future<UserInfoAllDao> getRedisFuture(String key) {
        try {
            byte[] bytes = redisTemplate.opsForValue().get(key);
            if(bytes == null) return null;

            UserInfoProto.UserInfo userProto = UserInfoProto.UserInfo.parseFrom(bytes);
            UserInfoAllDao userInfoAllDao = UserInfoAllDao.builder()
                    .userID(userProto.getUserID())
                    .userCode(userProto.getUserCode())
                    .email(userProto.getEmail())
                    .password(userProto.getPassword())
                    .userName(userProto.getUserName())
                    .appPassword(userProto.getAppPassword())
                    .build();
            return CompletableFuture.completedFuture(userInfoAllDao);
        }
        catch (InvalidProtocolBufferException e){
            throw new WrongAccessException(WrongAccessException.of.REDIS_CONNECTION_EXCEPTION);
        }
    }

이를 검증하기 위해 Jmeter로 부하 테스트를 진행했습니다. 스레드 100개로 Loop Count를 2, 총 200개의 동시 요청을 받았을 때의 latency를 비교해보았습니다.

아래는 @Async를 적용하기 전 평균 latency 값입니다. 왼쪽부터 차례대로 Sample Time(ms), Latency, Connect Time(ms) 입니다. 평균 latency가 150.695 만큼 발생한 것을 확인하실 수 있습니다.

다음은 @Async를 적용한 후 평균 latency 값입니다. 열 이름과 순서는 위와 동일하며 평균 latency가 24.865로 적용 이전에 비해 약 6배 가량 latency가 줄어든 것을 확인하실 수 있습니다.

결론적으로 비동기적으로 Redis에 접속함으로 인해 동시 요청 시 latency를 약 6배 가량 감소시킨 결과를 얻을 수 있었습니다. 이를 이용하여 다중 요청 처리를 해야 하는 작업에서 비동기적으로 진행하는 것을 고려하여 성능을 향상시킬 수 있다는 것을 알 수 있습니다.

profile
저는 상황에 맞는 최적의 솔루션을 깊고 정확한 개념의 이해를 통한 다양한 방식으로 해결해오면서 지난 3년 동안 신규 서비스를 20만 회원 서비스로 성장시킨 Software Developer 최민길입니다.

2개의 댓글

comment-user-thumbnail
2023년 7월 18일

덕분에 좋은 정보 얻어갑니다, 감사합니다.

1개의 답글