MySQL 스레드 풀 적용 및 성능 비교
서론
이번 포스팅에서는 MySQL에 스레드 풀을 적용시키고 여러 변수에 대해 스레드 풀 적용 전 후 성능을 비교해보겠습니다.
MySQL에 스레드 풀 기능 적용
MySQL 서버 엔터프라이즈 에디션은 스레드 풀 기능을 제공하지만 MySQL 커뮤니티 에디션은 스레드 풀 기능을 지원하지 않습니다. MySQL 커뮤니티 에디션으로 스레드 풀을 사용하기 위해 알려진 방법은 Percona Server에서 스레드 풀 플러그인 라이브러리를 MySQL 커뮤니티 에디션 서버에 설치해서 사용하는 것입니다.
Percona가 무엇인지 생소하실텐데 Percona 공식 사이트에 따르면 "Percona는 온프레미스(on-premises) 및 클라우드 환경에서 MySQL, MongoDB 그리고 PostgreSQL을 위한 세계 수준의 오픈 소스 데이터베이스 소프트웨어, 지원 그리고 서비스 회사로 널리 알려져있습니다. Percona는 전문성과 오픈 소스 소프트웨어의 특별한 조합을 통해 우리의 데이터베이스와 애플리케이션이 더 잘 실행되도록 돕는 데 전념합니다." 라고 설명하고 있습니다. 간단하게 요약하자면 오픈 소스 데이터베이스 소프트웨어에 추가적인 기능이나 개선을 제공하는 서비스 회사로 추측됩니다.
Percona를 알게된 건 'Real MySQL'이란 도서로 알게 되었는데 책에서 명시하기로는 플러그인 방식으로 MySQL 커뮤니티 에디션에 스레드풀 기능을 넣을 수 있음을 설명하고 있습니다. 하지만 percona에서 제공하는 공식문서에는 기존 MySQL에 플러그인 방식으로 기능을 넣는 방식을 설명하고 있지않아 다른 방법을 찾았습니다.
공식 문서에 따르면 "Percona Server for MySQL은 무료로 사용할 수 있으며, MySQL과 완전하게 호환되고 기능이 향상된 오픈소스 소프트웨어로, 어떤 MySQL 데이터베이스도 그대로 대체할 수 있는 제품입니다."라고 설명합니다. 여기서 그대로 대체할 수 있는 (drop-in replacement)는 어떤 MySQL 데이터베이스 환경에도 별다른 수정 없이 그대로 대체할 수 있는 제품을 의미하며 기존 MySQL에 플러그인을 설치하는 것이 아닌 기존 MySQL 자체를 Percona Server로 대체하는 방법을 권장하는 것 같습니다.
https://docs.percona.com/percona-server/8.4/index.html
Percona Server for MySQL
Percona Server for MySQL 8.4 - Documentation This documentation is for the latest release: Percona Server for MySQL 8.4.4-4 (Release Notes). Percona Server for MySQL is a freely available, fully compatible, enhanced, and open source drop-in replacement for
docs.percona.com
필자는 성능 비교가 목적이기 때문에 Percona에서 제공하는 도커 컨테이너 이미지로 Percona Server를 실행하도록 하겠습니다.
성능 그래프 작성 준비
이번 절에서는 성능 그래프 작성을 위한 과정에 대해 정리해보겠습니다.
테스트 도구는 데이터베이스 벤치마크에 자주 쓰이는 sysbench를 이용하고 도커를 이용해 MySQL과 Perconan Server를 테스트할 것입니다. 전체 과정을 도식화하면 아래와 같습니다.
테스트 진행
테스트는 동시 요청 쓰레드 개수 별로 MySQL과 Percona Server의 평균 TPS, QPS, Latency 차이를 비교해 봅니다.
테스트는 아래의 과정으로 진행됩니다.
MySQL, Percona 도커 컨테이너 실행
# MySQL Community Edition
docker run -d \
--cpus="4" \
--name mysql-community \
-e MYSQL_ROOT_PASSWORD=root \
-e MYSQL_DATABASE=test \
-p 3306:3306 \
mysql:8.0
# sysbench 접근을 위한 mysql-community 인증 플러그인 변경
docker exec -it mysql-community mysql -uroot -proot -e \
"ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'root'; FLUSH PRIVILEGES;"
# Percona Server (Thread Pool 포함)
docker run -d \
--cpus="4" \
--name percona-server \
-e MYSQL_ROOT_PASSWORD=root \
-e MYSQL_DATABASE=test \
-p 3307:3306 \
percona/percona-server:8.0
# sysbench 접근을 위한 percona-server 인증 플러그인 변경
docker exec -it percona-server mysql -uroot -proot -e \
"ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'root'; FLUSH PRIVILEGES;"
MySQL과 Percona Server 모두 논리 CPU는 4개로 제한합니다. 특별한 의미가 있는 것은 아니고 현재 필자의 컴퓨터의 논리 CPU가 8개이기 때문에 동시 실행을 위해 CPU 자원을 반으로 나눴습니다. MySQL은 3306 포트, Percona는 3307 포트에서 실행됩니다. 각 데이터베이스 실행 후 sysbench에서 데이터 준비를 위해 인증 플러그인을 변경해줍니다. 인증 플러그인 변경하는 이유는 sysbench가 데이터베이스에 접근하는 방식은 현재 버전의 데이터베이스에서 사용하지 않기 때문입니다.
sysbench 컨테이너 실행
docker run -it --name sysbench --network host severalnines/sysbench bash
sysbench를 호스트 네트워크 모드로 실행하여 컨테이너가 호스트의 네트워크 스택을 그대로 사용하도록 합니다. 주로 벤치마크 테스트, 로컬 개발, 고성능 통신이 필요한 경우 사용합니다.
sysbench 데이터 준비
sysbench를 통해 MySQL과 Percona 서버에 벤치마크에 사용할 테스트 데이터를 준비합니다.
# MySQL Community Edition용 (포트 3306)
sysbench sysbench /usr/share/sysbench/oltp_read_write.lua \
--mysql-host=127.0.0.1 \
--mysql-port=3306 \
--mysql-user=root \
--mysql-password=root \
--mysql-db=test \
--tables=1 \
--table-size=100000 \
prepare
# Percona Server용 (포트 3307)
sysbench /usr/share/sysbench/oltp_read_write.lua \
--mysql-host=127.0.0.1 \
--mysql-port=3307 \
--mysql-user=root \
--mysql-password=root \
--mysql-db=test \
--tables=1 \
--table-size=100000 \
prepare
벤치마크 스크립트 생성 및 실행
cat << 'EOF' > run_comparison.sh
#!/bin/bash
THREADS_LIST=(8 16 32 64 128)
DURATION=30
DBS=("mysql-community:3306" "percona-server:3307")
RESULT_FILE="compare_result.csv"
echo "threads,db,tps,qps,latency_ms" > $RESULT_FILE
for DB in "${DBS[@]}"; do
NAME=$(echo $DB | cut -d':' -f1)
PORT=$(echo $DB | cut -d':' -f2)
for t in "${THREADS_LIST[@]}"; do
echo "[*] $NAME - threads=$t"
docker exec sysbench sysbench /usr/share/sysbench/oltp_read_write.lua \
--mysql-host=127.0.0.1 \
--mysql-port=$PORT \
--mysql-user=root \
--mysql-password=root \
--mysql-db=test \
--tables=1 \
--table-size=100000 \
--threads=$t \
--time=$DURATION \
--report-interval=1 \
--events=0 run > tmp.log
TPS=$(grep "transactions:" tmp.log | head -1 | awk '{print $2}')
QPS=$(grep "queries:" tmp.log | head -1 | awk '{print $2}')
LAT=$(grep "avg:" tmp.log | awk '{print $2}' | head -1)
echo "$t,$NAME,$TPS,$QPS,$LAT" >> $RESULT_FILE
done
done
EOF
chmod +x run_comparison.sh
./run_comparison.sh
쓰레드 개수는 8 > 16 > 32 > 64 > 128 순으로 MySQL과 Percona 에 테스트하며 각 테스트마다 평균 TPS, QPS, Latency 를 추출합니다. 여기서 쓰레드 개수는 동시 사용자 정도로 생각해주시면 됩니다.
결과 그래프
테스트 전 스레드 풀 적용이 미적용보다 성능상 이점이 있을 것으로 판단했지만 벤치마크 결과 TPS, QPS, Latency 모두 요청 쓰레드가 많아질 수 록 스레드 풀 미적용이 더 좋은 성능을 보이고 있습니다.
원인에 대해 분석 중 Percona 서버의 스레드 풀의 변수를 확인해보았고 다음이 문제가 될 수 있겠다는 생각을 했습니다.
mysql> SHOW VARIABLES LIKE 'thread_pool%';
+-------------------------------+--------------+
| Variable_name | Value |
+-------------------------------+--------------+
| thread_pool_high_prio_mode | transactions |
| thread_pool_high_prio_tickets | 4294967295 |
| thread_pool_idle_timeout | 60 |
| thread_pool_max_threads | 100000 |
| thread_pool_oversubscribe | 3 |
| thread_pool_size | 8 |
| thread_pool_stall_limit | 500 |
+-------------------------------+--------------+
7 rows in set (0.08 sec)
thread_pool_size는 CPU를 동시에 사용할 수 있는 스레드 수를 정의합니다. 현재 Percona 서버 도커 컨테이너에 논리 CPU를 4개 할당했으므로 Percona가 자원을 원할히 사용할 수 없었을 것이라 판단했습니다.
mysql> SET GLOBAL thread_pool_size = 4;
Query OK, 0 rows affected (0.01 sec)
따라서, thread_pool_size 값을 4로 설정한 뒤 다시 벤치마크를 실행했습니다.
Percona 서버의 thread_pool_size를 조정한 이후 실행한 벤치마크에서는 스레드 풀 미적용보다 적용이 요청 스레드 개수가 많아질 수록 유의미한 성능차이를 보입니다. 하지만 요청 스레드가 적을 때는 MySQL이 더 좋은 성능을 보입니다.
이는 MySQL이 각 클라이언트 연결마다 전용 스레드를 할당하지만 스레드 수가 적으면 컨텍스트 비용이 작고 락 경합도 적기 때문에 오히려 오버헤드가 적어 스레드 풀 사용보다 더 좋은 성능이 나오는 것으로 유추할 수 있습니다.
결론
스레드 풀 적용과 미적용에 대한 성능 테스트를 실습해보았습니다. 이번 실습 전에는 "스레드 풀 적용을 하면 항상 이점만 있지 않을까"라는 생각을 가지고 있었지만 컴퓨터의 환경과 동작하는 작업의 유형에 따라 성능이 향상될 수 있고 저하될 수 있음을 알게 되었습니다.
스레드 풀을 사용할 계획이라면 작업의 특성을 먼저 보는 것이 좋습니다. 예를 들어, 만일 동시 접속 수가 많고 요청당 처리 시간이 긴 작업일 경우 스레드 풀을 사용하는 것이 적절할 수 있습니다. 하지만 반대의 경우에 스레드 풀을 사용하면 오히려 스레드 풀이 더 오버헤드가 될 수 있습니다.
그리고 스레드 풀을 사용한다면 컴퓨터 환경과 맞는 적절한 스레드 풀 관련 시스템 변수를 설정해야 효율적인 스레드 풀 사용이 가능합니다.