데이터베이스 성능 최적화 및 쿼리 튜닝
1. 문제 상황: 느린 쿼리와 성능 저하
서비스가 성장하면서 데이터베이스의 응답 속도가 점점 느려지는 현상이 발생한다. 초기에 개발할 때는 문제가 없었지만, 데이터가 증가하면서 다음과 같은 문제가 나타난다.
- 특정 쿼리 실행 속도가 매우 느려진다.
- 트래픽이 많아지면 데이터베이스의 CPU 사용률이 급격히 상승한다.
- 여러 개의 요청이 동시에 실행되면 성능이 급격히 저하된다.
- 데이터베이스 락(Lock) 문제가 발생하여 동시성이 떨어진다.
이러한 문제를 해결하기 위해서는 데이터베이스 성능 최적화와 쿼리 튜닝이 필수적이다.
2. 해결 과정: 성능 최적화 단계별 접근
2.1 느린 쿼리 탐색
느린 쿼리를 찾는 가장 쉬운 방법은 MySQL의 slow_query_log
기능을 활성화하는 것이다.
SET GLOBAL slow_query_log = 1;
SET GLOBAL long_query_time = 1; -- 1초 이상 걸리는 쿼리를 기록
이후 /var/log/mysql/slow.log
파일에서 실행 속도가 느린 쿼리를 확인할 수 있다.
2.2 실행 계획 분석
느린 쿼리를 발견한 후에는 실행 계획(EXPLAIN)을 통해 최적화가 필요한 부분을 분석해야 한다.
EXPLAIN SELECT * FROM orders WHERE user_id = 1234;
실행 계획을 확인하면 다음과 같은 정보가 표시된다.
컬럼 | 설명 |
---|---|
type | 쿼리의 조인 방식 (ALL, INDEX, RANGE, REF, EQ_REF 등) |
possible_keys | 사용 가능한 인덱스 |
key | 실제로 사용된 인덱스 |
rows | 쿼리가 스캔하는 행(row) 수 |
만약 type
값이 ALL
이라면 전체 테이블을 조회하는 것이므로 성능 저하의 원인이 될 수 있다.
2.3 인덱스 최적화
조회 속도를 개선하는 가장 효과적인 방법 중 하나는 적절한 인덱스를 추가하는 것이다.
예를 들어, user_id
컬럼에 대한 인덱스가 없으면 모든 데이터를 검색해야 하지만, 인덱스를 추가하면 성능이 향상된다.
CREATE INDEX idx_user_id ON orders(user_id);
이후 실행 계획을 다시 확인하여 인덱스가 제대로 사용되고 있는지 검토해야 한다.
2.4 JOIN 최적화
테이블 간 조인(JOIN) 시 불필요한 데이터가 조회되거나, 인덱스가 없으면 성능이 저하될 수 있다.
예를 들어, 다음과 같은 조인은 성능이 저하될 가능성이 있다.
SELECT users.name, orders.amount
FROM users
JOIN orders ON users.id = orders.user_id
WHERE users.age > 30;
이를 최적화하려면 조인에 사용되는 컬럼에 인덱스를 추가하는 것이 효과적이다.
CREATE INDEX idx_users_id ON users(id);
CREATE INDEX idx_orders_user_id ON orders(user_id);
2.5 데이터베이스 캐싱
같은 데이터를 반복해서 조회하는 경우, 캐싱을 활용하면 데이터베이스 부하를 줄일 수 있다.
- MySQL Query Cache (MySQL 5.7 이하에서 지원)
- Redis 또는 Memcached를 활용한 캐싱
예를 들어, 자주 조회되는 데이터를 Redis에 저장하여 캐싱하면 데이터베이스 부하를 줄일 수 있다.
SET user:1234 '{"name": "John", "age": 30}';
이후 같은 데이터를 조회할 때 Redis에서 가져오도록 하면 데이터베이스 부하를 줄일 수 있다.
2.6 데이터 정규화 vs. 비정규화
일반적으로 데이터 정규화는 중복을 줄이고 데이터 무결성을 유지하는 데 유리하지만, 성능이 중요한 경우 일부 비정규화를 고려할 수 있다.
예를 들어, 사용자 정보와 주문 정보를 정규화하면 다음과 같이 두 개의 테이블로 나뉘게 된다.
users (id, name, age)
orders (id, user_id, amount)
그러나 주문 목록을 자주 조회해야 하는 서비스라면, 중복을 감수하고 비정규화하여 한 테이블에 저장하는 방식도 고려할 수 있다.
orders (id, user_name, user_age, amount)
이렇게 하면 조인을 수행할 필요가 없어 성능이 향상될 수 있다.
2.7 파티셔닝을 통한 데이터 분산
데이터가 많아질수록 한 개의 테이블에서 모든 데이터를 조회하는 것은 비효율적일 수 있다. 테이블을 파티셔닝(partitioning)하면 데이터를 분산 저장하여 성능을 개선할 수 있다.
CREATE TABLE orders (
id INT NOT NULL,
user_id INT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
order_date DATE NOT NULL
) PARTITION BY RANGE (YEAR(order_date)) (
PARTITION p2019 VALUES LESS THAN (2020),
PARTITION p2020 VALUES LESS THAN (2021),
PARTITION p2021 VALUES LESS THAN (2022)
);
이 방식은 연도별로 데이터를 나누어 저장하므로 특정 연도의 데이터만 조회할 때 성능이 향상된다.
3. 최종 정리
데이터베이스 성능 저하는 서비스의 응답 속도를 느리게 하고, 사용자 경험을 저하시킬 수 있다. 이번 글에서는 주요 성능 저하 원인과 이를 해결하기 위한 튜닝 방법을 살펴보았다.
핵심 요약:
- 느린 쿼리를 탐색하기 위해
slow_query_log
를 활성화하고, 실행 계획(EXPLAIN
)을 분석해야 한다. - 적절한 인덱스를 추가하면 검색 성능을 향상시킬 수 있다.
- 조인(JOIN) 성능을 높이기 위해 조인 키에 인덱스를 추가해야 한다.
- Redis 등을 활용한 캐싱을 통해 데이터베이스 부하를 줄일 수 있다.
- 데이터 정규화와 비정규화의 균형을 고려해야 한다.
- 테이블 파티셔닝을 통해 대량 데이터를 효율적으로 관리할 수 있다.
데이터베이스 성능 최적화는 한 번에 끝나는 작업이 아니라 지속적인 모니터링과 튜닝이 필요한 과정이다. 실시간으로 성능을 분석하고 개선하는 습관을 들이는 것이 중요하다.