1. 성능 개선 방법
이전 포스팅에서 문제점을 파악해 다음과 같은 방법으로 성능을 개선하기로 했다. 그리고 이 포스팅에서 Connection Pool의 크기를 조절해 성능을 개선해보겠다.
- connection pool 크기 조절 하기
- scale out을 통해 트래픽을 분산해 TPS 올리기
2. Connection Pool
먼저 병목이 발생한 getConnection()이 무엇인지 알아보자. getConnection()은 자바 프로그램과 데이터베이스를 네트워크 상에서 연결해주는 메소드이다. 연결에 성공하면 DB와의 연결 상태를 Connection 객체로 표현해 반환한다.
데이터베이스 커넥션을 획득할 때는 다음과 같은 과정을 거친다.
- 어플리케이션 로직은 DB 드라이버를 통해 커넥션을 조회한다.
- DB 드라이버는 DB와 TCP/IP 커넥션을 연결한다. 이 과정에서 3 way handshake가 발생한다.
- DB 드라이버는 TCP/IP 커넥션이 연결되면, ID와 PASSWORD를 통해 기타 정보를 DB에 전달한다.
- DB는 ID,PASSWORD를 통해 내부 인증을 완료하고 내부에 DB 세션을 생성한다.
- DB는 커넥션 생성이 완료되었다는 응답을 보낸다.
- DB 드라이버는 커넥션 객체를 생성해서 클라이언트에 반환한다.
이렇게 커넥션을 만드는 과정은 복잡하고 시간도 많이 걸린다. 따라서 유저가 어플리케이션을 사용할때 SQL을 실행하는 시간뿐만 아니라 커넥션을 새로 만드는 시간이 추가되기 때문에 응답속도에 영향을 준다. 이러한 문제를 해결한 방법이 바로 커넥션을 미리 생성해두고 사용하는 Connection pool(커넥션 풀)이라는 방법이다.
바로 어플리케이션을 시작하는 시점에 커넥션 풀을 필요한 만큼 커넥션을 미리 확보해서 풀을 보관한다. 기본값은 보통 10개이다. 따라서 커넥션 풀에 있는 커넥션은 DB와 연결되어 있기 때문에 언제든지 SQL을 즉시 DB에 전달할 수 있다. 그리고 커넥션을 모두 사용하고 나면 커넥션을 종료하는 것이 아니라 커넥션이 살아있는 상태로 커넥션 풀에 반환해야한다.
즉, 유저가 늘어날수록 getConnection()이 느려진 이유는, 제한된 커넥션 수로 인해 동시에 커넥션을 요청하는 유저들로 인해 대기 시간이 증가하고 병목 현상이 발생하기 때문이다.
3. Connection Pool 크기 조절 후 테스트
이제 첫번째 해결방법으로 Connection Pool 크기를 조정해보겠다. 그 전에 얼마나 늘려아할까? 많이 늘린다고 좋을까? 하는 의문점이 생겼다. 분명 trade off가 있을텐데 먼저 궁금증을 해결하기로 했다.
Q. connection pool를 많이 늘리면 좋을까?
connection pool을 무작정 많이 늘리는 것은 좋지 않다. 왜냐하면 WAS의 Connection을 사용하는 주체는 Thread인데 Thread pool 보다 Connection pool 의 크기가 크다면 Thread가 사용하는 Connection 외에 Connection이 남아 실질적으로 메모리 공간이 남아 낭비되기 때문이다.
그럼 Connection pool 과 Thread pool 을 동시에 늘려주면 안 될까? 하는 생각이 든다. 하지만 Thread의 증가는 동시에 처리할 수 있는 작업 수가 증가를 의미하므로 더 많은 CPU 및 메모리 자원을 사용하게 된다. 즉 Context Switching이 발생해 성능이 낮아지고, 병목 현상이 발생할 수 있다.
Q. connection pool를 얼마나 늘려야 할까?
Hikari CP는 JDBC의 Connection을 효율적으로 관리하는 Connection Pool의 구현체이다. 또한 Spring Boot가 기본적으로 사용하는 connection-pool Framework다.
그리고 Hikari CP 공식 문서에서 최적화된 Pool Size를 구하는 공식을 알 수 있다.
connections = ((core_count * 2) + effective_spindle_count)
- core_count : CPU 코어 수
- effective_spindle_count: 하드 디스크의 개수. 하드 디스크 하나는 spindle 하나를 가진다. 따라서 spindle의 수는 기본적으로 DB 서버가 동시 관리할 수 있는 IO 개수를 말한다.
There hasn't been any analysis so far regarding how well the formula works with SSDs.
하지만 SSD를 사용하는 경우는 특별히 명확한 결론이 없다고 한다.
현재 WAS 서버를 [Compact] 2vCPU, 2GB Mem, 50GB Disk [g1], SSD를 사용하고 있기 때문에 Connection Pool 개수를 늘리며 TPS가 가장 높을때를 찾기로 했다!
기존과 똑같은 nGrinder 조건으로 VUser 300명, agent 1개, 실행시간 1분을 고정시키고, warm up을 설정해 Connection Pool size만 바꿔가며 테스트 해보았다.
1. Connection Pool size가 5일때
해당 테스트의 TPS는 1382 ,Mean Test Time 는 93.65ms이다. 즉, 초당 1382번의 API 요청을 처리할 수 있다.
Pinpoint의 Server map으로 확인해 본 결과, 응답시간은 평균 48ms, 최대 1.18sec이다.
2. Connection Pool size가 10일때 (default)
TPS는 1,429, Mean Test Time 는 93.68ms이다. 즉, 초당 1429번의 API 요청을 처리할 수 있다.
응답시간은 평균 31ms, 최대 607ms 이다.
3. Connection Pool size가 20일때
TPS는 692, Mean Test Time 는 212.86ms이다. 즉, 초당 692번의 API 요청을 처리할 수 있다.
응답시간 평균 147ms, 최대 3.14sec이다.
4. 결론
결과를 표로 정리해보면 다음과 같다.
Size 5 | Size 10 | Size 20 | |
TPS | 1382 | 1429 | 692 |
최대 응답시간 | 1.18sec | 607ms | 3.14sec |
평균 응답시간 | 48ms | 31ms | 147ms |
conneciton pool size 의 크기가 10일때 TPS 와 평균, 최대 응답시간 모두 가장 좋다는 것을 알 수 있었다. 따라서 connection pool zise의 크기는 10 이 적당하다고 판단했다.
성능 테스트로 정확히 확인하기 전까지는 connection pool size을 조절해서 TPS를 높일 수 있을 것이라고 생각했지만, 실제로 테스트를 해보니 성능이 개선되지 않았다. 아쉽지만 단순히 connection pool을 무작정 늘린다고 성능이 개선되는 것도 아니라는 것을 눈으로 확인할 수 있어서 재밌었다!
다른 방법으로 성능 개선을 도전해봐야겠다 !😃
'Spring' 카테고리의 다른 글
nGrinder와 Pinpoint로 성능테스트 및 개선하기 (5) - 캐싱 (0) | 2024.05.30 |
---|---|
nGrinder와 Pinpoint로 성능테스트 및 개선하기 (4) - Scale out (0) | 2024.05.20 |
nGrinder와 Pinpoint로 성능테스트 및 개선하기 (2) - 병목지점 파악 (0) | 2024.05.14 |
nGrinder와 Pinpoint로 성능테스트 및 개선하기 (1) - 환경 구축 (0) | 2024.05.10 |
Redis Lua Script 실제로 Atomic할까? (0) | 2024.04.18 |