Linux VPS 또는 클라우드 서버에서 MySQL 8.0을 튜닝하는 실전 단계별 가이드입니다. 느린 쿼리 로그로 먼저 측정하고, InnoDB 버퍼 풀 크기를 올바르게 설정하며, EXPLAIN 출력을 읽고, 적절한 인덱스를 추가하는 방법을 다룹니다.
MySQL 데이터베이스 성능 튜닝 기초
느린 데이터베이스는 애플리케이션이 느려지는 가장 흔한 원인입니다. 서버를 증설하거나 코드를 다시 작성하기 전에, 몇 시간의 체계적인 MySQL 튜닝만으로도 잃어버린 성능의 상당 부분을 되찾는 경우가 많습니다. 이 가이드는 MySQL 8.0을 실행하는 일반적인 Linux VPS 또는 클라우드 서버를 기준으로 기본기를 다룹니다. 측정하고, 메모리 크기를 설정하고, 쿼리 실행 계획을 읽고, 적절한 인덱스를 추가하는 과정입니다.
이 단계들은 모든 표준 MySQL 8.0 설치에 적용됩니다. 여기에는 Skyline Cloud VPS 또는 클라우드 서버에서 사우디아라비아 국내에 호스팅되는 데이터베이스도 포함되며, 데이터를 사우디아라비아 내에 상주시키면 PDPL 및 NCA 규정 준수에 도움이 됩니다.
사전 준비 사항
- MySQL 8.0이 설치된 Linux 서버(Ubuntu 22.04/24.04, Debian, 또는 RHEL 계열).
sudo권한을 가진 사용자와 관리 권한을 가진 MySQL 계정.- 실제 또는 대표성 있는 워크로드 — 유휴 상태의 데이터베이스를 대상으로 튜닝하면 아무것도 알 수 없습니다.
항상 한 번에 하나의 변수만 변경하고 다시 측정하세요. 측정 없는 튜닝은 추측에 불과합니다.
1단계 — 느린 쿼리 로그로 먼저 측정하기
측정하지 않는 것은 튜닝할 수 없습니다. 느린 쿼리 로그는 시간 임계값을 초과하는 모든 구문을 기록하며, 이는 가장 심각한 문제 쿼리를 찾는 가장 빠른 방법입니다.
런타임에 활성화하기(재시작 불필요):
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1; -- log queries slower than 1 second
SET GLOBAL log_queries_not_using_indexes = 'ON';
영구적으로 적용하려면 MySQL 설정 파일(Ubuntu/Debian에서는 /etc/mysql/mysql.conf.d/mysqld.cnf, RHEL에서는 /etc/my.cnf)에 다음을 추가하세요:
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 1
log_queries_not_using_indexes = 1
대표성 있는 워크로드를 실행한 다음 로그를 분석하세요. Percona Toolkit의 pt-query-digest는 동일한 쿼리를 핑거프린트별로 그룹화하고 소비된 총 시간 기준으로 순위를 매깁니다:
sudo apt install percona-toolkit # Debian/Ubuntu
pt-query-digest /var/log/mysql/mysql-slow.log | less
보고서 상단의 쿼리에 집중하세요. 단일 실행 시간이 가장 긴 쿼리가 아니라 총 실행 시간이 가장 긴 쿼리입니다. 분당 10,000번 실행되는 쿼리를 최적화하는 것이 야간에 한 번 실행되는 쿼리를 최적화하는 것보다 낫습니다.
2단계 — InnoDB 버퍼 풀 크기 설정하기
InnoDB는 기본 스토리지 엔진이며, 그 버퍼 풀은 테이블과 인덱스 데이터를 RAM에 캐싱합니다. 작업 집합(working set)이 버퍼 풀에 들어가면 MySQL은 대부분의 읽기를 디스크 대신 메모리에서 처리하는데, 이것이 보통 가장 큰 단일 성능 향상 요인입니다.
현재 크기와 적중률(hit ratio)을 확인하세요:
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
SELECT
(1 - (SELECT VARIABLE_VALUE FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads')
/ NULLIF((SELECT VARIABLE_VALUE FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests'), 0)) * 100
AS hit_ratio_pct;
적중률은 99% 이상을 목표로 하세요. Oracle 문서에 따르면, 전용 데이터베이스 서버에서는 버퍼 풀을 **전체 RAM의 50~75%**로 설정하여 OS와 MySQL의 다른 버퍼를 위한 공간을 남겨 두어야 합니다(InnoDB 자체는 설정된 크기보다 약 10% 더 많은 메모리를 예약합니다). 소규모 공유 서버에서는 50%에 가깝게 유지하고 OS를 위해 최소 1~2 GB를 남겨 두세요.
8 GB 전용 서버라면 약 5~6 GB가 합리적인 시작점입니다:
[mysqld]
innodb_buffer_pool_size = 5G
MySQL을 재시작하고 워크로드가 캐시를 워밍업한 후 적중률을 다시 확인하세요. 너무 크게 설정하지 마세요. 버퍼 풀에 다른 프로세스를 더한 값이 RAM을 초과하면 OS가 디스크로 스왑(swap)하면서 성능이 무너집니다.
3단계 — EXPLAIN으로 쿼리 실행 계획 읽기
어떤 쿼리가 느린지 파악했다면, 이제 그 _이유_를 알아내세요. EXPLAIN은 MySQL이 구문을 어떻게 실행하려는지 보여줍니다:
EXPLAIN SELECT * FROM orders WHERE customer_id = 42 AND status = 'shipped';
가장 중요한 컬럼:
| 컬럼 | 주의해서 볼 점 |
|---|---|
type |
ALL은 풀 테이블 스캔을 의미하며 보통 좋지 않습니다. ref, range, 또는 const가 더 낫습니다. |
key |
사용된 인덱스. NULL은 사용된 인덱스가 없음을 의미합니다. |
rows |
검사할 것으로 추정되는 행 수. 낮을수록 좋습니다. |
Extra |
Using filesort 또는 Using temporary는 비용이 큰 정렬 단계를 나타냅니다. |
MySQL 8.0에서는 EXPLAIN ANALYZE로 한 걸음 더 나아갈 수 있습니다. 이 명령은 실제로 쿼리를 실행하여 추정치와 함께 실제 소요 시간을 보고합니다:
EXPLAIN ANALYZE SELECT * FROM orders WHERE customer_id = 42 AND status = 'shipped';
이는 인덱스 변경이 실제로 도움이 되었는지 확인하는 가장 신뢰할 수 있는 방법입니다.
4단계 — 적절한 인덱스 추가하기
대용량 테이블에서의 풀 테이블 스캔(type: ALL)은 느린 쿼리의 전형적인 원인입니다. WHERE, JOIN, ORDER BY 절에서 사용되는 컬럼에 인덱스를 추가하세요.
위 쿼리의 경우, 필터링되는 두 컬럼을 모두 포함하는 복합 인덱스를 사용하면 MySQL이 일치하는 행으로 곧장 이동할 수 있습니다:
CREATE INDEX idx_orders_customer_status
ON orders (customer_id, status);
컬럼 순서가 중요합니다. 가장 선택도가 높은 컬럼 — 또는 동등(equality) 매칭에 사용되는 컬럼 — 을 먼저 두세요. 복합 인덱스는 선행 컬럼(customer_id)만으로 필터링하는 쿼리에도 사용되므로, 이를 위한 별도의 단일 컬럼 인덱스는 거의 필요하지 않습니다.
EXPLAIN ANALYZE를 다시 실행하여 type이 개선되었는지(예: ALL에서 ref로) 그리고 rows가 크게 줄었는지 확인하세요.
인덱스는 공짜가 아닙니다. 디스크 공간을 소비하고 각 인덱스를 유지 관리해야 하므로 쓰기(INSERT/UPDATE/DELETE)를 느리게 만듭니다. 신중하게 인덱스를 설계하고, sys.schema_unused_indexes 뷰를 통해 식별할 수 있는 사용되지 않는 인덱스는 제거하세요:
SELECT * FROM sys.schema_unused_indexes;
5단계 — 검증한 다음 반복하기
튜닝은 일회성 작업이 아니라 반복 과정입니다. 각 변경 후에:
- 영향을 받은 쿼리를
EXPLAIN ANALYZE로 다시 실행하고 소요 시간을 비교하세요. - 느린 쿼리 로그를 다시 실행시키고 다시 다이제스트(digest)하세요.
- 버퍼 풀 적중률이 계속 높게 유지되는지 확인하세요.
도움이 되지 않은 변경은 모두 되돌리세요. 무엇을 왜 변경했는지 메모해 두세요. 미래의 당신이 현재의 당신에게 고마워할 것입니다.
흔한 함정
- 단일 쿼리만 따로 떼어 튜닝하기. 느린 로그 다이제스트가 드러내는 총 영향(빈도 × 비용)을 기준으로 최적화하세요.
- 블로그에서
my.cnf값 그대로 복사하기. 버퍼 풀 크기는 당신의 RAM과 데이터셋에 따라 달라집니다. 복사하지 말고 측정하세요. - 인덱스된 컬럼을 함수로 감싸기.
WHERE DATE(created_at) = '2026-06-08'는created_at의 인덱스를 사용할 수 없습니다. 범위 조건으로 다시 작성하세요:WHERE created_at >= '2026-06-08' AND created_at < '2026-06-09'. - 넓은 테이블에서
SELECT *사용하기. 필요한 컬럼만 가져와야 MySQL이 커버링 인덱스(covering index)를 사용할 수 있습니다.
결론
MySQL 성능 튜닝은 마법이 아니라 체계적인 작업입니다. 느린 쿼리 로그로 측정하고, InnoDB에 충분한 RAM을 주고, EXPLAIN ANALYZE로 쿼리 실행 계획을 읽고, 신중하게 인덱스를 추가하세요. 이 네 가지 기본기만으로도 하드웨어를 업그레이드하기 전에 실제 세계의 성능 저하 대부분이 해결됩니다.
확장성, 예측 가능한 지연 시간, 또는 아랍어 현지 지원이 필요한 워크로드라면 데이터베이스를 사우디아라비아 국내에 호스팅하세요. Skyline Cloud VPS 및 클라우드 서버를 살펴보고, 비즈니스 이메일 호스팅과 결합하여 완전한 스택을 구성한 다음, Skyline Cloud 계정을 생성하여 몇 분 만에 튜닝된 MySQL 서버를 배포하세요.
Comments
0 total · 0 threads