TIL: Java SpringBoot 대 Asp.Net REST API 성능

TIL: Java SpringBoot 대 Asp.Net REST API 성능

2022-10-13 last update

11 minutes reading java dotnet spring

TIL: Java Spring 부트와 Asp.Net REST API 성능 비교



오늘 저는 Java SpringBoot 및 Asp.Net 성능에 대해 배웠습니다(#til).

곧 출시될 .Net 7 릴리스와 최소 API를 테스트하고 있었는데 속도에 놀랐습니다. 그래서 Java 18 SpringBoot 프로젝트와 비교하기로 결정했습니다.

결과는 Asp.Net 7 솔루션이 Java 18의 SpringBoot 애플리케이션보다 거의 2배 빠릅니다!

결과는 다음과 같습니다.



Asp.Net 7 Minimal API는 SpringBoot Java 18 애플리케이션의 경우 30초당 2,152,070개 요청에 비해 30초 동안 4,084,163개의 요청을 전달합니다!

자바 18 스프링부트

$ k6 run --vus 16 --duration 30s config.json

          /\      |‾‾| /‾‾/   /‾‾/
     /\  /  \     |  |/  /   /  /
    /  \/    \    |     (   /   ‾‾\
   /          \   |  |\  \ |  (‾)  |
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: config.json
     output: -

  scenarios: (100.00%) 1 scenario, 16 max VUs, 1m0s max duration (incl. graceful stop):
           * default: 16 looping VUs for 30s (gracefulStop: 30s)


running (0m30.0s), 00/16 VUs, 2152070 complete and 0 interrupted iterations
default ✓ [======================================] 16 VUs  30s

     data_received..................: 373 MB  12 MB/s
     data_sent......................: 392 MB  13 MB/s
     http_req_blocked...............: avg=1.61µs   min=441ns   med=942ns    max=2.39ms p(90)=1.53µs   p(95)=1.75µs
     http_req_connecting............: avg=308ns    min=0s      med=0s       max=1.55ms p(90)=0s       p(95)=0s
     http_req_duration..............: avg=180.65µs min=114.4µs med=170.42µs max=4.11ms p(90)=202.21µs p(95)=216.99µs
       { expected_response:true }...: avg=180.65µs min=114.4µs med=170.42µs max=4.11ms p(90)=202.21µs p(95)=216.99µs
     http_req_failed................: 0.00%   ✓ 0            ✗ 2152070
     http_req_receiving.............: avg=22.51µs  min=4.59µs  med=20.95µs  max=3.95ms p(90)=32.44µs  p(95)=39.5µs
     http_req_sending...............: avg=6.3µs    min=2.63µs  med=5.61µs   max=3.03ms p(90)=8.87µs   p(95)=9.59µs
     http_req_tls_handshaking.......: avg=0s       min=0s      med=0s       max=0s     p(90)=0s       p(95)=0s
     http_req_waiting...............: avg=151.83µs min=92.24µs med=142.39µs max=3.43ms p(90)=172.73µs p(95)=187.11µs
     http_reqs......................: 2152070 71733.676043/s
     iteration_duration.............: avg=218.91µs min=138.9µs med=207.65µs max=4.54ms p(90)=244.87µs p(95)=263.15µs
     iterations.....................: 2152070 71733.676043/s
     vus............................: 16      min=16         max=16
     vus_max........................: 16      min=16         max=16


Asp.Net 7

$ k6 run --vus 16 --duration 30s config.json

          /\      |‾‾| /‾‾/   /‾‾/
     /\  /  \     |  |/  /   /  /
    /  \/    \    |     (   /   ‾‾\
   /          \   |  |\  \ |  (‾)  |
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: config.json
     output: -

  scenarios: (100.00%) 1 scenario, 16 max VUs, 1m0s max duration (incl. graceful stop):
           * default: 16 looping VUs for 30s (gracefulStop: 30s)


running (0m30.0s), 00/16 VUs, 4084163 complete and 0 interrupted iterations
default ✓ [======================================] 16 VUs  30s

     data_received..................: 845 MB  28 MB/s
     data_sent......................: 743 MB  25 MB/s
     http_req_blocked...............: avg=1.06µs   min=431ns   med=972ns    max=2.48ms p(90)=1.41µs   p(95)=1.62µs
     http_req_connecting............: avg=0ns      min=0s      med=0s       max=66.6µs p(90)=0s       p(95)=0s
     http_req_duration..............: avg=74.37µs  min=30.59µs med=67.84µs  max=7.33ms p(90)=91.37µs  p(95)=106.97µs
       { expected_response:true }...: avg=74.37µs  min=30.59µs med=67.84µs  max=7.33ms p(90)=91.37µs  p(95)=106.97µs
     http_req_failed................: 0.00%   ✓ 0             ✗ 4084163
     http_req_receiving.............: avg=13.64µs  min=4.43µs  med=12.72µs  max=7.05ms p(90)=17.75µs  p(95)=19.44µs
     http_req_sending...............: avg=6.36µs   min=2.77µs  med=5.96µs   max=5.83ms p(90)=8.24µs   p(95)=8.96µs
     http_req_tls_handshaking.......: avg=0s       min=0s      med=0s       max=0s     p(90)=0s       p(95)=0s
     http_req_waiting...............: avg=54.35µs  min=19.55µs med=48.45µs  max=5.34ms p(90)=70.1µs   p(95)=83.96µs
     http_reqs......................: 4084163 136135.973692/s
     iteration_duration.............: avg=113.11µs min=56.58µs med=105.71µs max=7.9ms  p(90)=133.41µs p(95)=153.52µs
     iterations.....................: 4084163 136135.973692/s
     vus............................: 16      min=16          max=16
     vus_max........................: 16      min=16          max=16


세부



목표는 JSON 본문을 가져와 역직렬화하고 값을 수정하고 직렬화된 JSON 응답을 반환하는 간단한 HTTP POST API를 테스트하는 것이었습니다.

요청 및 응답 개체는 동일하지만 다른 클래스로 표시됩니다. 데이터 수정은 단순히 문자열을 연결하고 정수 값에 추가하는 것입니다.

Asp.Net 프로젝트의 경우 .Net 7의 미리보기 7이 사용되었으며 Java의 경우 Oracle OpenJDK 18.0.2.1과 결합된 종속성으로 spring-boot-starter-weblombok가 있는 새로운 SpringBoot 프로젝트가 사용되었습니다.

코드는 다음과 같습니다.

자바 18 스프링 부트



pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>RestApiDemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>RestApiDemo</name>
    <description>RestApiDemo</description>
    <properties>
        <java.version>18</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>


RestApiDemoApplication.java

package com.example.restapidemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RestApiDemoApplication {

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

}


입력 모델.자바

package com.example.restapidemo;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
public final class InputModel {
    String FirstName;
    String LastName;
    Integer Age;
}


출력모델.자바

package com.example.restapidemo;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
@AllArgsConstructor
public final class OutputModel {
    String FirstName;
    String LastName;
    Integer Age;
}


데모컨트롤러.자바

package com.example.restapidemo;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {

    @PostMapping("/get")
    @ResponseBody
    public OutputModel GetData(@RequestBody InputModel input) {
        return new OutputModel(input.FirstName + "x", input.LastName + "x", input.Age + 10);
    }
}


Asp.Net 7



프로그램.cs

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/get", ([FromBody] InputModel input) => new OutputModel
    {
        FirstName = input.FirstName + "x",
        LastName = input.LastName + "x",
        Age = input.Age + 10
    }
);

app.Run();

internal sealed class InputModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

internal sealed class OutputModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}


K6.io config.json

import http from 'k6/http';

export default function () {

  const url = 'http://localhost:8080/get';

  const payload = JSON.stringify({
    FirstName: 'John',
    LastName: 'Doe',
    Age: 33
  });

  const params = {
    headers: {
      'Content-Type': 'application/json',
    },
  };

  http.post(url, payload, params);
}


응답 유효성을 위해 CURL로 먼저 테스트:
자바 18 스프링부트

$ curl -X POST http://localhost:8080/get -H "content-type: application/json" -d "{\"FirstName\":\"John\",\"LastName\":\"Doe\",\"Age\":33}"

{"firstName":"Johnx","lastName":"Doex","age":43}%


Asp.Net 7

$ curl -X POST http://localhost:8080/get -H "content-type: application/json" -d "{\"FirstName\":\"John\",\"LastName\":\"Doe\",\"Age\":33}"

{"firstName":"Johnx","lastName":"Doex","age":43}%


.Net 7이 있는 Kestrel 서버는 Java 18 기반 SpringBoot 애플리케이션이 있는 기본 Tomcat 서버와 비교하여 간단한 API를 처리하는 꽤 괜찮은 작업을 수행하는 것 같습니다.

ORM 프레임워크나 로깅을 사용하기로 결정하기 전까지는 베어 언어가 매우 유사하기 때문에 간단한 REST API를 구현하기로 결정할 때 누군가 이것을 사용하는 것을 고려할 수 있습니다.