서버 최적화 & 데이터 마이그레이션 작업 - 3 : DB 최적화

최민길(Gale)·2023년 1월 3일
1

안녕하세요 오늘은 DB 최적화 작업을 진행하면서 배운 내용들에 대해 포스팅해보려고 합니다ㅎㅎ

저번에 DB 마이그레이션 작업과 API 서버 최적화 작업 이후 여전히 쿼리 실행 속도가 안정적이지 않고 10시에 많은 동접자들이 접속함에 따라 쿼리 로딩이 심한 현상이 발생하셨습니다.

문제에 대한 원인을 찾던 중 PHP의 connection pool 부재로 인해 성능이 저하될 수 있다는 사실을 알게 되었습니다. Connection Pool이란 DB와 연결된 커넥션을 만들어 저장하고, 커넥션이 필요할 때마다 저장된 커넥션을 꺼내 사용하면서 DB에 연결하기 위한 커넥션 비용을 감소시켜 성능을 향상시킬 수 있는 기법입니다. 대표적으로 Spring의 JDBC의 경우 Connection Pool을 지원하지만 PHP나 Ruby 등의 언어에서는 Connection Pool을 지원하지 않아 사용자들이 몰릴 경우 웹 서버가 죽지 않았음에도 DB에 과부하가 와서 성능 저하 이슈가 발생할 수 있습니다.

이를 해결하기 위해 AWS에서 제공하는 RDS Proxy를 사용했습니다. RDS Proxy는 DB의 연결에 대한 Connection Pool 기능을 제공하여 PHP의 단점을 보완할 수 있습니다. RDS Proxy를 사용하실 때 유의하실 점은 MariaDB의 경우 10.5 버전까지만 지원되기 때문에(2023.1.3. 기준) 그 이상 버전에서는 사용하실 수 없습니다. 또한 Proxy의 엔드포인트는 같은 VPC 내에서만 접속이 가능하기 때문에 외부에서는 Proxy 엔드포인트로 접속할 수 없습니다.

RDS Proxy 설정은 https://aws.amazon.com/ko/getting-started/hands-on/set-up-shared-database-connection-amazon-rds-proxy/ 링크에서 진행하실 수 있습니다. 여기서 주의하실 점은 IAM 정책 생성 시 위의 링크대로 진행하시면 DB와 정상적으로 연결이 되지 않아 하단의 코드로 입력하시면 되겠습니다.

IAM Policy 생성 JSON
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"secretsmanager:GetResourcePolicy",
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret",
"secretsmanager:ListSecretVersionIds"
],
"Resource": "Secrets Manager에서 발급받은 ARN"
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": [
"secretsmanager:GetRandomPassword",
"secretsmanager:ListSecrets"
],
"Resource": "*"
}
]
}


성공적으로 Proxy 적용을 완료하였고 가장 사용자가 많이 접속하는 오후 10시에 DB와 투다 앱을 모니터링하였습니다. 위에 보이시는 그래프는 각각 Proxy에서 모니터링한 지표와 RDS DB 자체적으로 모니터링한 지표입니다. Proxy가 성공적으로 동작하는 것을 확인할 수 있었지만 성능 측면에서 드라마틱한 차이가 발생하지 않았습니다.

문제를 확인하기 위해 사용 중인 DB인 MariaDB의 작동 방식에 대해 알아보았습니다. MariaDB의 경우 MySQL을 기반으로 만들어진 DB이며 MySQL과 많은 유사성을 띄고 있습니다. MySQL의 경우 클라이언트가 DB에 접속할 때마다 스레드를 생성하고 요청 작업이 종료되면 스레드를 제거하는 방식으로 작동합니다. 하지만 이 작업은 많은 요청이 들어올 경우 스레드를 계속 생성하고 제거하면서 많은 리소스를 낭비하게 되어 결과적으로 성능 저하를 발생시키게 됩니다.

이를 해결하기 위해 특정 개수의 스레드를 메모리에 캐싱하는 Thread Pool을 사용합니다. 클라이언트가 DB에 접속 시 Thread Pool에 존재하는 캐싱된 스레드를 통해 요청을 처리하게 되며, 요청이 끝난 후 해당 스레드는 캐시로 반환되거나 삭제됩니다.

RDS에서 Thread Pool을 설정하는 방법은 사용 중인 파라미터 그룹에서 thread_handling 옵션을 pool-of-threads 로 변경해주시면 됩니다. (기본값 : One-thread-per-connection) 파라미터 그룹이 제대로 적용될 수 있도록 재부팅도 잊지 말고 해주시면 됩니다.

Thread Pool이 성능 향상에 큰 도움이 되는지 확인하기 위해 sysbench를 이용하여 테스트를 진행하였습니다. 현재 투다 DB의 데이터 수를 고려하여 5개의 테이블에 각각 150만개의 데이터를 추가하여 테스트를 진행하였습니다. 테스트는 가장 트래픽이 많았을 때 동작한 12개의 스레드를 기준으로 10개와 20개의 스레드로 진행하였습니다.


위의 실행 결과는 Thread Pool을 적용하기 전 각각 스레드 10개와 20개로 테스트한 결과입니다. 각각 47.24, 89.64의 tps와 944.89,1792.75의 qps 값을 보여줍니다. tps(Transactions Per Second)는 단위 시간 내에 처리하는 요청의 개수이며 qps(Queries Per Second)는 단위 시간 내에 처리하는 쿼리량입니다.


위의 실행 결과는 Thread Pool을 적용한 후 각각 스레드 10개와 20개로 테스트한 결과입니다. 각각 46.94, 83.22의 tps와 938.81,1664.43의 qps 값을 보여줍니다. 지표 상으로 확인했을 땐 Thread Pool을 설정한 지표가 이전에 비해 적은 값을 나타내는데, 다른 블로그나 자료 등을 찾아봤을 때에도 Thread Pool을 설정한 실험값의 지표가 낮은 것으로 나타났고, 일부 글에서는 이를 통해 성능이 더 좋다는 결론을 도출하였습니다. 하지만 이 부분에 대해서 아직 완전한 학습이 되지 않아 이 부분을 좀 더 학습 후 결과에 대해 분석을 시도해야 할 것 같습니다.

Thread Pool 설정도 성공적으로 마쳤고 당일 10시에 사용자들이 몰릴 때의 모니터링을 통해 Thread Pool 적용이 성공적이었는지에 대해 살펴보려고 합니다. 긴 글 읽어주셔서 감사합니다!

참고 자료
https://sungwookkang.com/1230
https://loosie.tistory.com/366
https://code-lab1.tistory.com/209
https://sungwookkang.com/1492
https://prohannah.tistory.com/168
https://intrepidgeeks.com/tutorial/significance-and-calculation-method-of-qps-and-tps-in-mysql-database
https://mariadb.com/kb/en/thread-pool-system-status-variables/

profile
저는 상황에 맞는 최적의 솔루션을 깊고 정확한 개념의 이해를 통한 다양한 방식으로 해결해오면서 지난 3년 동안 신규 서비스를 20만 회원 서비스로 성장시킨 Software Developer 최민길입니다.

0개의 댓글