반응형
들어가며
MVC
- MVC는 Synchronous Blocking I/O 방식으로 동작한다.
- Blocking I/O는 Kernel에 I/O 처리 요청을 하고, 처리가 완료될 때까지 Block하여 대기하는 방식이다.
- 이는 I/O 처리가 완료될 때까지 다른 작업을 먼저 처리할 수 없으므로 자원을 효율적으로 사용할 수 없다.
WebFlux
- WebFlux는 Asynchronous Non-Blocking I/O 방식으로 동작한다.
- Non-Blocking I/O는 Kernel에 I/O 처리 요청(Event Loop에 등록)을 하고, 처리 완료시 이벤트를 콜백으로 받아 처리하는 방식.
- 이는 I/O 처리를 요청한 후 다른 작업을 처리하다가 콜백 발생시 해당 작업을 처리하는 방식이므로 자원을 효율적으로 사용할 수 있다.
- API나 데이터베이스 같은 여러 수단을 통해 데이터를 요청한 후 결과를 조합하여 응답으로 내려줄 경우에 사용하면 좋다.
- 적은 양의 스레드와 최소한의 하드웨어 자원으로 동시성을 핸들링하기 위해 만들어졌다.
RestTemplate(Blocking Sync) vs WebClient(Non-Blocking Async)
설명
- 외부 API 2개를 호출하여 조합 후 응답으로 내려주는 API를 MVC(RestTemplate)와 WebFlux(WebClient) 방식으로 구성하여 테스트한다.
- 100명의 사용자가 30초 동안 동시에 API를 호출할 경우 각 방식의 성능 차이를 비교한다.
외부 API
- 코드
@RestController public class OtherController { @SneakyThrows @GetMapping("/api/messages1") public String messages1() { Thread.sleep(10); return "Hello"; } @SneakyThrows @GetMapping("/api/messages2") public String messages2() { Thread.sleep(10); return "World"; } }
MVC
- 코드
@RestController public class MyController { private RestTemplate restTemplate = new RestTemplate(); @GetMapping("/api/messages") public String messages() { String message1 = restTemplate.getForObject("http://localhost:8081/api/messages1", String.class); String message2 = restTemplate.getForObject("http://localhost:8081/api/messages2", String.class); return message1 + message2; } }
- 결과 이미지
- CPU Max : 30%
- Heap Max : 1200MB
- Thread Max : 132
- Throughput : 2110.2/sec
WebFlux
- 코드
@RestController public class MyController { private WebClient webClient = WebClient.builder().build(); @GetMapping("/api/messages") public Mono<String> messages() { Mono<String> messages1 = webClient.get().uri("http://localhost:8081/api/messages1").retrieve().bodyToMono(String.class); Mono<String> messages2 = webClient.get().uri("http://localhost:8081/api/messages2").retrieve().bodyToMono(String.class); return Mono.zip(messages1, messages2, (s, s2) -> s + s2); } }
- 결과 이미지
- CPU Max : 35%
- Heap Max : 900MB
- Thread Max : 38
- Throughput : 3126.4/sec
결론
- 두 방식 CPU 사용량, Heap 사용량은 큰 차이 없었다.
- MVC 방식에 비해 WebFlux는 Thread 사용량이 매우 낮았고(132 vs 38), 초당 처리량이 높았다.(2110.2/sec vs 3126.4sec)
MongoTemplate(Blocking Sync) vs ReactiveMongoTemplate(Non-Blocking Async)
설명
- MongoDB에서 데이터 6개를 조회하고 조합 후 응답으로 내려주는 API를 MVC(MongoTemplate)와 WebFlux(ReactiveMongoTemplate) 방식으로 구성하여 테스트한다.
- 100명의 사용자가 30초 동안 동시에 API를 호출할 경우 각 방식의 성능 차이를 비교한다.
MVC
- 코드
@RequiredArgsConstructor @RestController public class SampleController { private final MongoTemplate mongoTemplate; @GetMapping("/api/samples") public List<Sample> getSamples() { return Arrays.asList( mongoTemplate.findById("60ed1bbb03defc41787652b2", Sample.class), mongoTemplate.findById("60ed1bbb03defc41787652b3", Sample.class), mongoTemplate.findById("60ed1bbb03defc41787652b4", Sample.class), mongoTemplate.findById("60ed1c20da5c1274017c1958", Sample.class), mongoTemplate.findById("60ed1c20da5c1274017c1959", Sample.class), mongoTemplate.findById("60ed1c20da5c1274017c195a", Sample.class) ); } @AllArgsConstructor @NoArgsConstructor @Data @Document public static class Sample { @Id private String id; private String message; } }
- 결과 이미지
- CPU Max : 50%
- Heap Max : 750MB
- Thread Max : 120
- Throughput : 342.6/sec
WebFlux
- 코드
@RequiredArgsConstructor @RestController public class SampleController { private final ReactiveMongoTemplate mongoTemplate; @GetMapping("/api/samples") public Flux<Sample> getSamples() { return Flux.merge( mongoTemplate.findById("60ed1bbb03defc41787652b2", Sample.class), mongoTemplate.findById("60ed1bbb03defc41787652b3", Sample.class), mongoTemplate.findById("60ed1bbb03defc41787652b4", Sample.class), mongoTemplate.findById("60ed1c20da5c1274017c1958", Sample.class), mongoTemplate.findById("60ed1c20da5c1274017c1959", Sample.class), mongoTemplate.findById("60ed1c20da5c1274017c195a", Sample.class) ); } @AllArgsConstructor @NoArgsConstructor @Data @Document public static class Sample { @Id private String id; private String message; } }
- 결과 이미지
- CPU Max : 50 ~ 60%
- Heap Max : 500 ~ 1200MB
- Thread Max : 26
- Throughput : 364.4/sec
결론
- 두 방식 CPU 사용량, Heap 사용량은 큰 차이 없었다.
- MVC 방식에 비해 WebFlux는 Thread 사용량이 매우 낮았고(120 vs 26), 초당 처리량이 높았다.(342.6/sec vs 364.4/sec)
JDBC(Blocking Sync) vs JDBC(Blocking Async) vs R2DBC(Non-Blocking Async)
설명
- MySQL에서 데이터 4개를 조회하고 조합 후 응답으로 내려주는 API를 MVC(JDBC)와 WebFlux(JDBC, R2DBC) 방식으로 구성하여 테스트한다.
- 200명의 사용자가 30초 동안 동시에 API를 호출할 경우 각 방식의 성능 차이를 비교한다.
MVC
- 코드
@RequiredArgsConstructor @RestController public class UserController { private final UserRepository userRepository; @GetMapping("/api/users") public List<User> getUsers() { return Arrays.asList( userRepository.findById(1L).orElse(null), userRepository.findById(2L).orElse(null), userRepository.findById(3L).orElse(null), userRepository.findById(4L).orElse(null) ); } }
- 결과 이미지
- CPU Max : 10%
- Heap Max : 100 ~ 200MB
- Thread Max : 214
- Throughput : 478.8/sec
WebFlux(JDBC)
- 코드
@RequiredArgsConstructor @RestController public class UserController { private final UserRepository userRepository; @GetMapping("/api/users") public Flux<User> getUsers() { return Flux.mergeSequential( Mono.fromCallable(() -> getUser(1L)).subscribeOn(Schedulers.boundedElastic()), Mono.fromCallable(() -> getUser(2L)).subscribeOn(Schedulers.boundedElastic()), Mono.fromCallable(() -> getUser(3L)).subscribeOn(Schedulers.boundedElastic()), Mono.fromCallable(() -> getUser(4L)).subscribeOn(Schedulers.boundedElastic()) ); } private User getUser(Long userSeq) { return userRepository.findById(userSeq).orElseThrow(() -> new IllegalStateException("user not found")); } }
- 결과 이미지
- CPU Max : 10%
- Heap Max : 130 ~ 230MB
- Thread Max : 148
- Throughput : 489.9/sec
WebFlux(R2DBC)
- 코드
@RequiredArgsConstructor @RestController public class UserController { private final UserRepository userRepository; @GetMapping("/api/users") public Flux<User> getUsers() { return Flux.mergeSequential( userRepository.findById(1L), userRepository.findById(2L), userRepository.findById(3L), userRepository.findById(4L) ); } }
- 결과 이미지
- CPU Max : 10%
- Heap Max : 130 ~ 230MB
- Thread Max : 41
- Throughput : 657.3/sec
결론
- 세 방식 CPU 사용량, Heap 사용량은 큰 차이 없었다.
- 특정 요청자 수 이하까지는 MVC(JDBC) 방식보다 WebFlux(JDBC) 방식이 Thread 사용량이 더 많았다.
- 200명 기준으로는 MVC(JDBC)보다 WebFlux(JDBC)가 Thread 사용량이 적고(214 vs 148), 초당 처리량이 미세하게나마 높았다.(478.8/sec vs 489.9/sec)
- WebFlux(JDBC)보다 WebFlux(R2DBC)가 Thread 사용량이 훨씬 적고(148 vs 41), 초당 처리량이 훨씬 높았다.(489.9/sec vs 657.3/sec)
- Non-Blocking Async를 지원하지 않는 메소드를 부득이하게 사용해야할 경우에는 WebFlux(JDBC) 방식처럼 Mono.fromCallable()을 사용하는 방식으로 구현하면 좋다.
- Non-Blocking Async를 지원한다면 적극적으로 사용하는 것이 좋다.
반응형
'Development > WebFlux' 카테고리의 다른 글
[WebFlux] Spring Data Reactive (0) | 2021.07.17 |
---|---|
[WebFlux] Stream API (0) | 2021.07.17 |
[WebFlux] WebClient (0) | 2021.07.17 |
[WebFlux] Test (0) | 2021.07.17 |
[WebFlux] Controller (0) | 2021.07.17 |