본문 바로가기
카테고리 없음

데이터베이스 성능 최적화 및 쿼리 튜닝

by 백수A 2025. 3. 29. 16:32

데이터베이스 성능 최적화 및 쿼리 튜닝

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 등을 활용한 캐싱을 통해 데이터베이스 부하를 줄일 수 있다.
  • 데이터 정규화와 비정규화의 균형을 고려해야 한다.
  • 테이블 파티셔닝을 통해 대량 데이터를 효율적으로 관리할 수 있다.

데이터베이스 성능 최적화는 한 번에 끝나는 작업이 아니라 지속적인 모니터링과 튜닝이 필요한 과정이다. 실시간으로 성능을 분석하고 개선하는 습관을 들이는 것이 중요하다.