새소식

Spring

[Spring] Apache AB를 활용한 서버 성능테스트와 톰캣 쓰레드(Thread) 확장으로 성능 최적화

  • -

웹 서버들은 어떻게 동시에 여러개의 요청을 처리할까?

일반적으로 멀티 쓰레드를 이용한 방법을 채택한다.

 

그런데 동시에 10만개의 요청이 들어왔을때, 쓰레드가 10만개가 생성될까?

 

멀티 쓰레드는 다음과 같은 단점이 있다.

  • 쓰레드를 생성하는 비용이 크다
  • CPU level에서 context switching 이 더 자주 일어나게 되면서, 성능 저하가 발생한다.
  • limit 을 걸어놓지 않으면 쓰레드가 무한정 생성되고, 물리적 성능 한계를 넘어서면 서버가 다운될 수 있다.

 

그래서 Tomcat에서는 Tread Pool 이라는 것을 관리한다.

 

Tread Pool

 

Thread Pool 의 동작 과정은 다음과 같다.

  1. 일정 개수의 Thread를 미리 열어놓는다.
  2. 새로운 요청이 들어오면, 비어있는 Thread를 할당한다.
  3. 서블릿 객체를 통해 HttpRequest와 HttpResponse를 생성한다.
  4. HttpRequst에 담긴 내용을 바탕으로, 작성된 로직을 수행한다
  5. 로직 수행 후, HttpResponse에 담고, 요청에 대한 응답을 보낸다.
  6. Thread를 반납하고, 해당 Thread는 다음 요청을 기다린다.

 

 

그렇다면, 할당되어 있는 Thread를 모두 사용하면 어떤 일이 생길까?

이는 개발자가 다음과 같은 전략(strategy)을 선택할 수 있다.

  1. 요청을 queue에 담아놓고 대기
  2. 요청 거절
  3. Tread pool 초기화

1. 실제 서버 성능 테스트하기 - 환경 구축

 

@Controller
public class ThreadTest {

	//아무 로직 없는 테스트
    @GetMapping("/nothing")
    public ResponseEntity<Void> nothingController() {
        return ResponseEntity.status(HttpStatus.OK).build();
    }

	//3초짜리 테스트
    @GetMapping("/threeSeconds")
    public ResponseEntity<Void> threeSecondsController() throws InterruptedException {
        Thread.sleep(3000);
        return ResponseEntity.status(HttpStatus.OK).build();
    }

}

spring에서 간단한 api를 작성하자.

/nothing : 아무 로직 없이 200 응답을 주는 API

/threeSeconds : 요청 하나 처리에 3초가 걸리는 API

 

 

아파치에서 제공하는 AB 툴을 사용해서 테스트 해보자.

.\ab.exe -n <테스트요청개수> -c <동시요청개수> http://localhost:8080/test

<option>

-n 10000 : 테스트를 위해 10000개의 요청을 보낸다.

-c 300 : 동시에 300개의 요청을 던진다. (기본 값은 하나만 던짐)

 


3. 실제 서버 성능 테스트하기 - 요청 처리에 3초가 걸리는 API

@GetMapping("/threeSeconds")
public ResponseEntity<Void> threeSecondsController() throws InterruptedException {
    Thread.sleep(3000);
    return ResponseEntity.status(HttpStatus.OK).build();
}

요청 하나 처리에 3초가 걸리는 API를 테스트 해보자.

 

CASE 1

먼저 쓰레드를 하나만 열도록 설정해서, 정말 한 번에 하나의 작업만 처리하는지 확인해보자.

  • Thread Pool 설정 1개 (싱글스레드)
  • 총 요청 10개
  • 동시 요청 10개

Result : 30.2 sec

실제로 작업 하나를 3초간 처리했기에, 총 30초가 걸린 모습이다.

 

이제, 톰캣 기본 설정인 200개를 세팅해서 어떤식으로 데이터가 나오는지 살펴보자

 

 

CASE 2

  • Thread Pool 설정 200개 (default)
  • 총 요청 1000개
  • 동시 요청 1000개

Result :  6 sec

와우! 아주 정확하고 이상적인 결과가 나왔다.

200개씩 3초마다 처리 가능한 곳에, 1000개를 동시에 두 번 밀어넣었으니, 15초가 나왔다.

물론 로컬이 아니라, 실제 환경이라면 더 오래 걸릴 것이다.

 

그렇다면, 여기에서도 Thread Pool을 1000으로 늘린다면, 성능이 좋아질 것이다.

테스트해보자.

 

CASE 3

  • Thread Pool 설정 200개 (default)
  • 총 요청 1000개
  • 동시 요청 1000개

Result : 3.6 sec

Thread 개수를 200개에서 1000개로 늘렸을 뿐인데, 15초가 걸리던 요청이 3초대로 내려왔다.

 


3. 실제 서버 성능 테스트하기 - 아무 로직 없는 API

@GetMapping("/nothing")
public ResponseEntity<Void> nothingController() {
    return ResponseEntity.status(HttpStatus.OK).build();
}

위와 같이 아무 로직이 없는 정말 단순한 API를 구성해봤다.

쓰레드가 많으면 많을수록 성능이 좋을지에 대해서 한 번 살펴보자.

 

 

CASE 4

  • Thread Pool 설정 200개 (default)
  • 총 요청 15000개
  • 동시 요청 15000개

Result : 평균 3.3 sec

1.5만개의 요청을 모두 동시에 보냈는데, 3.3초밖에 걸리지 않았다.

그렇다면 톰캣 설정을 바꿔서, 쓰레드 풀의 최대치를 높이면, 더 단축되지 않을까?

 

 

CASE 5

  • Thread Pool 설정 500개
  • 총 요청 15000개
  • 동시 요청 15000개

Applicaiton.yml

server:
  tomcat:
    threads:
      max: 500 //기본값 200

 

 

Result : 평균  3.2 sec

500개로 늘려서 테스트를 진행했다.

생각보다 성능이 많이 개선되지는 않았지만, 확실히 시간이 단축되었음은 확인했다.

아마 너무 간단하고 빠르게 끝나는 로직이라서 효과는 미미한 것 같다.

 

그렇다면 쓰레드 개수를 좀 더 늘려서, 1000개로 테스트를 해보자.

 

 

CASE 6

  • Thread Pool 설정 1000개
  • 총 요청 15000개
  • 동시 요청 15000개

Result : 평균  3.5 sec

엇. 이상하다. 쓰레드 개수를 분명 몇배로 늘렸는데, 성능이 개선되기는 커녕 악화되버렸다.

 

앞에서 말했듯이, 쓰레드가 너무 많으면 cpu context switching이 자주 일어나서, 성능이 오히려 악화될 수 있다.

 

 


결론

실제로 Tomcat에서 Thread Pool 의 size를 조절해보면서 성능 테스트를 어떻게 하는지 살펴봤다.

 

물론 PC 사양에 따라 천차 만별이겠지만, 다음과 같은 결과를 얻을 수  있었다.

 

  • 쓰레드를 너무 적게 열면, 동시 처리 가능한 요청이 적어져서, 성능이 악화된다.
  • 쓰레드를 너무 지나치게 많이 열면, context switching 때문에 성능이 오히려 악화된다.
  • 서버 성능과, 해당 API 서버가 처리하는 로직의 복잡도에 따라서 적절한 Thread Pool 설정이 필요하다.
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.