springboot_async
Spring Async and Lettuce Async
Redis Developer Course | Redis Technical Support | Redis Enterprise Server |
---|
Spring Async
Spring Boot에서 제공하는 @EnableAsync, @Async 어노테이션을 이용해서 비동기(asynchronous)로
메소드 실행 방법입니다.
소스 코드에 대한 일반적인 내용은 위 문서 [Connection Pool & Thread]를 보시기 바랍니다.
Thread 설정
Thread 개수를 입력 받아 실행합니다. 각 thread 당 1000번 save(set 명령)을 실행합니다.
StringController.java
Repository 설정
Class에 @EnableAsync 어노테이션을 추가하고, 메소드에 @Async 어노테이션을 추가합니다.
@EnableAsync 어노테이션은 아래와 같이 main 메소드가 있는 class에 추가해도 됩니다.
StringRepository.java
Lettuce Async
Spring Boot에서 제공하는 @Async 어노테이션을 이용하지 않고 Lettuce에서 제공하는 비동기(asynchronous)로
메소드 실행 방법입니다.
@GetMapping("/save/sync/{size}")는 동기로 입력할 때 사용합니다.
@GetMapping("/save/async/{size}")는 비동기로 입력할 때 사용합니다.
StringController2.java
StringRepository2.java
RedisConfig.java
Lettuce로 레디스 서버에 직접 연결하기 위해 RedisClient를 리턴하는 메소드를 추가해습니다.
[Spring 비동기(@Async)]와 [Lettuce 비동기(Async)] 성능 비교
Spring @Async 어노테이션을 이용한 방법[Spring 비동기(@Async)]와
[Lettuce 비동기(Async)] 성능 비교
레디스 서버와의 연결은 1개를 사용했다. setShareNativeConnection(true)
Spring 비동기(@Async)
- @Async 작동 방식에 대한 오해: 명령을 최대한 빨리 레디스 서버에 보내고 응답을 비동기로 받는 것으로 생각했다.
스레드 1개에서는 생각과 같이 작동해서 동기입력일 때 8초였는데 비동기에서 3초로 줄었다.
그런데 스레드를 10개로 늘려서 실행해보니 소요시간이 동기입력(9초)보다 늘어서 12초가 나왔다.
스레드 100개 일 때는 102초로 동기입력(14초)보다 훨씬 느려졌다.
Java에서 스레드별로 for loop 종료 후 소요시간을 찍어본 결과 위 경우 모두 1초 이내로 나온다.
StringController.java레디스 서버에서 redis-cli --stat로 명령 실행 상태를 확인해보니 초당 약 900~1000개 명령을 실행한다.
for (int i=0; i<count; i++) {
stringRepository.saveAsync(threadName+"-"+i, "value-"+threadName+"-"+i);
}
log.info("{} 소요시간: {}회 -> {} ms (시작:{} ~ 종료: {})", threadName, count, ...
- 정리: @Async를 사용하면 스레드 개수와 관계없이 해당 메서드를 초당 약 900~1000번 실행한다. 즉, 스레드 개수를 늘려도 전체 성능이 나아지지 않는다.
Lettuce 비동기(Async)
- 레터스의 비동기 실행은 최대한 빨리 서버로 명령을 보내고 응답을 비동기로 기다린다.
그러므로 전체 처리 시간에서 동기입력에 비해서 훨씬 빠르다.
StringRepository2.javaprivate RedisClient redisClient;
private StatefulRedisConnection<String, String> connection;
private RedisCommands<String, String> syncCmd;
private RedisAsyncCommands<String, String> asyncCmd;
public StringRepository2(RedisClient redisClient) {
this.redisClient = redisClient;
this.connection = redisClient.connect();
this.syncCmd = this.connection.sync();
this.asyncCmd = this.connection.async();
} - 스레드 1개: 동기입력 8초 -> 비동기입력 1초
스레드 10개: 동기입력 9초 -> 비동기입력 2초
스레드 100개: 동기입력 14초 -> 비동기입력 3초
스레드 1000개: 동기입력 62초 -> 비동기입력 10초 - 당연히 Spring 비동기(@Async) 입력에 비교해도 훨씬 빠르다.
- 결론: 대량의 데이터를 레디스 서버에 입력할 경우에는 Lettuce 비동기 방식을 사용할 것을 추천합니다.
[Spring 비동기(@Async)]와 [Lettuce 비동기(Async)] 테스트 결과
동기입력은 이전 테스트 결과를 사용했다.
스레드 | 입력건수 | 소요시간(sec) | ||
---|---|---|---|---|
동기입력 | Spring 비동기(@Async) | Lettuce 비동기(Async) | ||
1 | 1,000 | 8 | 3 | 1 |
10 | 10,000 | 9 | 12 | 2 |
100 | 100,000 | 14 | 102 | 3 |
500 | 500,000 | 34 | 테스트 생략 | 7 |
1000 | 1,000,000 | 62 | 10 | |
5000 | 5,000,000 | 288 | 37 |
- Spring 비동기(@Async)의 스레드 500 이상은 결과가 예상되므로 테스트를 생략했다.
[Lettuce 비동기(Async)] 스레드 5000
명령 실행 횟수가 초당 20만에 가까이 도달했다. 서버 CPU는 60% 정도 사용했다.
해당 서버를 redis-benchmark로 테스트 시 초당 11만 정도 처리했다.
redis-benchmark는 스레드를 50개 처리한다. 스레드 개수를 늘려도 성능이 나아지지 않는다.
정리 일반적으로 표준으로 삼고 있는 redis-benchmark 테스트보다 Java Lettuce 비동기 테스트가
성능이 더 좋게 나왔다.
Redis-benchmark 테스트
SET 명령 100만회 실행
$ redis-benchmark -n 1000000 -r 1000000000 set keyA___rand_int__ valueA___rand_int__
Spring 비동기(@Async)
다음은 각 테스트 동안 redis-cli --stat를 이용해서 서버 처리 상태를 모니터링 한 것이다.
Spring 비동기(@Async), 스레드 1: 처리시간 3초
requests ()괄호 안에 있는 숫자가 레디스 서버에서 명령을 실행한 횟수이다.
Spring 비동기(@Async), 스레드 10: 처리시간 12초
Spring 비동기(@Async), 스레드 100: 처리시간: 102초
Lettuce 비동기(Async)
Lettuce 비동기(Async), 스레드 1: 처리시간 1초
requests ()괄호 안에 있는 숫자가 레디스 서버에서 명령을 실행한 횟수이다.
Lettuce 비동기(Async), 스레드 10: 처리시간 2초
Lettuce 비동기(Async), 스레드 100: 처리시간 3초
Lettuce 비동기(Async), 스레드 500: 처리시간 7초
Lettuce 비동기(Async), 스레드 1000: 처리시간 10초
Lettuce 비동기(Async), 스레드 5000: 처리시간 37초
<< Connection Pool | Async | DB select >> |
---|