본문 바로가기
Lecture

Jmeter를 이용한 Spring MVC Vs. Webflux 성능 비교

by Renechoi 2023. 10. 21.

간단한 예제 코드

MVC


@SpringBootApplication
@RestController
@RequiredArgsConstructor
public class MvcApplication implements ApplicationListener<ApplicationReadyEvent> {
    private final RedisTemplate<String, String> redisTemplate;
    private final UserRepository userRepository;

    public static void main(String[] args) {
        SpringApplication.run(MvcApplication.class, args);
    }

    @GetMapping("/health")
    public Map<String, String> heatlh() {
        return Map.of("health", "ok");
    }

    @GetMapping("/users/1/cache")
    public Map<String, String> getCachedUser() {
        var name = redisTemplate.opsForValue().get("users:1:name");
        var email = redisTemplate.opsForValue().get("users:1:email");
        return Map.of("name", name == null ? "" : name, "email", email == null ? "" : email);
    }

    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        return userRepository.findById(id).orElse(new User());
    }

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        redisTemplate.opsForValue().set("users:1:name", "greg");
        redisTemplate.opsForValue().set("users:1:email", "greg@fastcampus.co.kr");

        Optional<User> user = userRepository.findById(1L);
        if (user.isEmpty()) {
            userRepository.save(User.builder().name("greg").email("greg@fastcampus.co.kr").build());
        }
    }
}

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Data
@Table(name = "users")
class User {
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Id
    private Long id;
    private String name;
    private String email;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
}

interface UserRepository extends JpaRepository<User, Long> {
}

기본 스레드 최소 설정은 1000개로 한다.

server:
  port: 9000
  tomcat:
    max-connections: 10000
    accept-count: 1000
    threads:
      max: 3000
      min-spare: 1000

logging:
  level:
    root: INFO

spring:
  datasource:
    url: jdbc:mysql://localhost:3307/mvc_webflux_jmeter_test
    username: root
    password: xxxx
  data:
    redis:
      host: localhost
      port: 6379
  jpa:
    generate-ddl: on
    hibernate:
      ddl-auto: create

Webflux


@SpringBootApplication
@RestController
@RequiredArgsConstructor
public class WebfluxApplication {
    private final ReactiveRedisTemplate<String, String> reactiveRedisTemplate;
    private final UserRepository userRepository;

    public static void main(String[] args) {
        SpringApplication.run(WebfluxApplication.class, args);
    }

    @GetMapping("/health")
    public Mono<Map<String, String>> health() {
        return Mono.just(Map.of("health", "ok"));
    }

    @GetMapping("/users/1/cache")
    public Mono<Map<String, String>> getCachedUser() {
        var name = reactiveRedisTemplate.opsForValue().get("users:1:name");
        var email = reactiveRedisTemplate.opsForValue().get("users:1:email");

        return Mono.zip(name, email)
                .map(i -> Map.of("name", i.getT1(), "email", i.getT2()));
    }

    @GetMapping("/users/{id}")
    public Mono<User> getUser(@PathVariable Long id) {
        return userRepository.findById(id).defaultIfEmpty(new User());
    }
}

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table("users")
class User {
    @Id
    private Long id;
    private String name;
    private String email;
    @CreatedDate
    private LocalDateTime createdAt;
    @LastModifiedDate
    private LocalDateTime updatedAt;
}

interface UserRepository extends ReactiveCrudRepository<User, Long> {
}
server:
  port: 9010

spring:
  r2dbc:
    url: r2dbc:mysql://localhost:3307/mvc_webflux_jmeter_test
    username: root
    password: xxxx
  data:
    redis:
      host: 127.0.0.1
      port: 6379
  jpa:
    generate-ddl: on
    hibernate:
      ddl-auto: create

Jmeter 설치

Jmeter는 brew 명령어로 간단히 설치할 수 있다.

Jmeter는 java로 만들어진 오픈 소스 -> https://jmeter.apache.org/

brew install jmeter

몇가지 플러그인을 더 설치한다

  • json-lib
  • jmeter-plugins-cmn-jmeter
  • cmdrunner
  • jpgc-graphs-basic 2.0
  • jpgc-graphs-additional 2.0

Jmeter 실행

listner를 다음과 같이 추가한다.

  • summary: 요약 내용 보여줌
  • reponse times over time: 응답 시간이 얼마나 걸리는
  • transaction per second: tps가 얼마나 되는지

각각 실행하기 위해 한쪽 실행시 한 쪽은 disable 해주었다.

기본 요청 응답 -> MVC test

warm up으로 유저 1명으로 실행해보자.

summary와 tps

이제 threds(users)를 200으로 올리고 루프 카운트를 infinite로 해서 계속 요청을 해보자

summary, 응답시간, tps


  • throughput: 42000.0/sec
  • 응답시간: 8~9ms
  • tps: 35000~40000

기본 요청 응답 -> Webflux test

동일하게 200명의 유저로 loop infinite로 실행해보자.

summary, 응답시간, tps

  • throughput: 50375/sec
  • 응답시간: 4~5ms
  • tps: 48000 ~ 50000

기본 요청 응답 -> 비교 결과

비교해보면 약 throughput, tps의 경우 20~30% 성능 향상이 있는 것을 볼 수 있다.
응답시간의 경우 2배에 가까이 차이가 난다.

엄청 빠르진 않지만 그래도 Webflux가 빠르다.

db IO 포함 -> MVC test

  • throughput: 1701/sec
  • 응답시간: 100~270ms
  • tps: 1200 ~ 2000

db IO 포함 -> Webflux test

  • throughput: 4738/sec
  • 응답시간: 36~50ms
  • tps: 4000 ~ 5500

db IO 포함 -> 비교 결과

비교해보면 전체적으로 최소 2배 ~ 3배 가량의 차이가 발생하는 것을 볼 수 있다.

확실히 Webflux가 빠르다.

총평

일반 응답과 CRUD를 사용한 응답 요청의 성능에서 Webflux가 MVC가 좋은 성능을 보여준다.

참고로 테스트 결과를 제시하진 않았지만 redis 캐시를 사용할 때 MVC와 Webflux 모두 큰 성능 효과가 있다.



reference :

반응형