반응형

들어가며

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

+ Recent posts