실무에서 활용되는 데이터베이스 성능 최적화 기법

목차

인덱스를 활용한 쿼리 성능 향상

인덱스는 데이터베이스에서 쿼리의 성능을 향상시키는 데 중요한 역할을 합니다. 인덱스를 적절하게 활용하면 데이터 검색 속도를 향상시킬 수 있습니다.

1. 인덱스의 개념

인덱스는 특정 열(또는 여러 열)에 대해 정렬된 데이터 구조입니다. 이를 통해 데이터베이스 엔진은 데이터를 효율적으로 검색할 수 있습니다.

2. 인덱스의 종류

가장 일반적인 인덱스 종류는 B-tree 인덱스입니다. 하지만 컴포지트 인덱스와 비트맵 인덱스도 특정 상황에서 유용하게 활용될 수 있습니다.


-- B-tree 인덱스 생성
CREATE INDEX idx_name ON customers (name);

-- 컴포지트 인덱스 생성
CREATE INDEX idx_name_age ON customers (name, age);

-- 비트맵 인덱스 생성
CREATE BITMAP INDEX idx_gender ON customers (gender);

3. 인덱스 사용의 장단점

인덱스를 사용하면 데이터 검색 속도는 향상되지만, 인덱스를 생성하고 유지하는 데에도 비용이 발생합니다. 또한 인덱스는 데이터 수정 작업에 대한 부가적인 오버헤드를 가질 수도 있습니다.


쿼리 튜닝을 위한 실행 계획 분석

쿼리 튜닝은 쿼리의 성능 개선을 위해 실행 계획을 분석하고 최적화하는 과정입니다. 실행 계획은 데이터베이스 엔진이 쿼리를 어떻게 실행할지에 대한 계획을 나타냅니다.

1. 실행 계획 확인 방법

실행 계획은 대부분의 데이터베이스 시스템에서 다양한 명령어를 통해 확인할 수 있습니다.


-- MySQL 실행 계획 확인
EXPLAIN SELECT * FROM customers WHERE age > 30;

-- Oracle 실행 계획 확인
EXPLAIN PLAN FOR SELECT * FROM customers WHERE age > 30;

2. 실행 계획 분석 항목

실행 계획은 다음과 같은 항목을 포함할 수 있습니다.

– 테이블 스캔 방식: 풀 테이블 스캔(Full Table Scan), 인덱스 스캔(Index Scan), 범위 스캔(Range Scan) 등
– 조인 방식: Nested Loop Join, Hash Join, Sort-Merge Join 등
– 인덱스 사용 여부: 인덱스를 효과적으로 활용하는지 여부
– 필터링과 정렬: WHERE 조건과 ORDER BY 절에서의 처리 방식

3. 실행 계획 분석 대상

실행 계획 분석은 자주 실행되는 쿼리와 성능이 문제가 있는 쿼리를 대상으로 수행합니다. 성능 개선이 필요한 쿼리의 실행 계획을 분석하여 문제를 파악하고 최적화 방안을 도출할 수 있습니다.


테이블 파티셔닝의 이해와 활용

테이블 파티셔닝은 대용량의 테이블을 작은 단위로 분할하는 기술로, 데이터의 관리 및 성능을 향상시킬 수 있습니다.

1. 테이블 파티셔닝의 이점

– 데이터 접근 성능 향상: 파티셔닝된 테이블은 작은 단위로 분할되어 있기 때문에 원하는 데이터에 빠르게 접근할 수 있습니다.
– 관리 용이성: 파티션 단위로 데이터를 관리할 수 있어서 데이터의 로딩, 삭제, 이관 등의 작업이 용이합니다.
– 가용성 향상: 테이블 파티셔닝을 통해 시스템의 가용성을 높일 수 있습니다. 일부 파티션의 오류로 인해 전체 시스템에 영향을 받지 않습니다.

2. 테이블 파티셔닝의 종류

– 범위 파티셔닝(Range Partitioning): 특정 열의 값 범위로 파티션을 나눕니다. 예를 들어 날짜 또는 가격 범위로 파티션을 나눌 수 있습니다.
– 목록 파티셔닝(List Partitioning): 특정 열의 값을 기준으로 파티션을 나눕니다. 다른 파티션에 속하지 않는 값을 가진 행은 기본 파티션에 저장됩니다.
– 해시 파티셔닝(Hash Partitioning): 해시 함수를 사용하여 테이블을 여러 파티션으로 분할합니다. 데이터를 고르게 분산시킬 수 있습니다.
– 컴포지트 파티셔닝(Composite Partitioning): 여러 개의 파티션 키를 사용하여 파티션을 나눕니다. 범위, 목록 또는 해시 파티셔닝을 조합하여 사용할 수 있습니다.

3. 테이블 파티셔닝의 예시


-- 범위 파티셔닝 예시
CREATE TABLE orders (
    order_id INT,
    order_date DATE,
    ...
)
PARTITION BY RANGE (order_date) (
    PARTITION p0 VALUES LESS THAN ('2021-01-01'),
    PARTITION p1 VALUES LESS THAN ('2022-01-01'),
    PARTITION p2 VALUES LESS THAN (MAXVALUE)
);

-- 해시 파티셔닝 예시
CREATE TABLE payments (
    payment_id INT,
    customer_id INT,
    ...
)
PARTITION BY HASH (customer_id) PARTITIONS 4;

-- 컴포지트 파티셔닝 예시
CREATE TABLE sales (
    sale_id INT,
    sale_date DATE,
    ...
)
PARTITION BY RANGE (YEAR(sale_date), MONTH(sale_date))
SUBPARTITION BY HASH(DAY(sale_date))
SUBPARTITIONS 4 (
    PARTITION p0 VALUES LESS THAN (2022, 1),
    PARTITION p1 VALUES LESS THAN (2022, 2),
    PARTITION p2 VALUES LESS THAN (MAXVALUE, MAXVALUE)
);

데이터베이스 캐싱 전략

데이터베이스 캐싱은 데이터베이스 작업의 성능을 향상시키기 위해 데이터를 캐시에 저장하는 전략입니다. 캐시는 데이터를 미리 읽어서 메모리에 저장해두므로, 데이터베이스에 반복적으로 접근하는 작업에서 큰 성능 향상을 제공할 수 있습니다.

1. 데이터베이스 캐싱의 이점

– 빠른 데이터 액세스: 캐시에 저장된 데이터는 메모리에 있으므로 디스크에 액세스하는 것보다 훨씬 빠르게 접근할 수 있습니다.
– 부하 분산: 데이터베이스 서버의 부하를 분산시켜주어 데이터베이스 서버의 성능을 향상시킵니다.
– 스케일 아웃 가능: 캐시 서버를 추가하여 확장이 가능하므로, 대량의 사용자에 대한 확장이 용이합니다.

2. 데이터베이스 캐싱 전략의 종류

– 애플리케이션 레벨 캐싱: 애플리케이션에서 데이터를 캐싱하는 방식으로, 자체 캐시 인스턴스를 생성하고 데이터베이스에서 데이터를 조회하여 캐시에 저장합니다.
– 데이터베이스 레벨 캐싱: 데이터베이스 시스템에서 제공하는 캐시 기능을 활용하는 방식으로, 예를 들어 MySQL에서는 Query Cache를 사용하여 결과셋을 캐싱할 수 있습니다.

3. 애플리케이션 레벨 캐싱 예시 (Redis 캐시)


import redis

# Redis 연결
r = redis.Redis(host='localhost', port=6379)

# 캐시에 데이터 저장
key = 'user:1'
data = {'name': 'John', 'age': 25}
r.set(key, data)

# 캐시에서 데이터 조회
result = r.get(key)
print(result)  # b'{"name": "John", "age": 25}'

4. 데이터베이스 레벨 캐싱 예시 (MySQL Query Cache)


-- MySQL Query Cache 활성화
SET GLOBAL query_cache_size = 1000000;
SET GLOBAL query_cache_type = 1;

-- 결과 캐싱
SELECT SQL_CACHE * FROM customers WHERE age > 30;

쿼리 옵티마이저 힌트의 활용

쿼리 옵티마이저 힌트는 데이터베이스에서 쿼리 실행 계획을 조정하기 위해 사용되는 명령어입니다. 힌트를 사용하여 쿼리 옵티마이저에게 원하는 실행 계획을 알려줄 수 있고, 성능 향상을 기대할 수 있습니다.

1. 쿼리 옵티마이저 힌트의 사용

쿼리 옵티마이저 힌트는 주석으로 작성되며, 쿼리의 SELECT 구문 뒤에 작성됩니다. 주석으로 작성된 힌트는 쿼리 실행 시 옵티마이저에 의해 해석되어 실행 계획을 수립합니다.

2. 쿼리 옵티마이저 힌트의 활용 사례

– 인덱스 힌트: 특정 인덱스를 사용하도록 옵티마이저에게 알려줄 수 있습니다. 인덱스 힌트는 특정 테이블과 인덱스 명을 지정하여 작성합니다.
– 조인 힌트: 조인 순서나 조인 방법 등을 조정하여 옵티마이저에게 알려줄 수 있습니다. 조인 힌트는 JOIN 구문 뒤에 작성합니다.
– 풀 테이블 스캔 힌트: 테이블을 풀 스캔하도록 옵티마이저에게 알려줄 수 있습니다. 풀 테이블 스캔 힌트는 테이블 명을 지정하여 작성합니다.

3. 쿼리 옵티마이저 힌트 예시 (Oracle)


-- 인덱스 힌트
SELECT /*+ INDEX(employee emp_salary_idx) */ * FROM employee WHERE salary > 5000;

-- 조인 힌트
SELECT /*+ ORDERED */ * FROM orders o, order_details od WHERE o.order_id = od.order_id;

-- 풀 테이블 스캔 힌트
SELECT /*+ FULL(employee) */ * FROM employee;

4. 쿼리 옵티마이저 힌트 예시 (MySQL)


-- 인덱스 힌트
SELECT * FROM employee USE INDEX (emp_salary_idx) WHERE salary > 5000;

-- 조인 힌트
SELECT * FROM orders STRAIGHT_JOIN order_details ON orders.order_id = order_details.order_id;

-- 풀 테이블 스캔 힌트
SELECT * FROM employee FORCE INDEX () /* USE PRIMARY KEY */ WHERE salary > 5000;

트랜잭션 관리와 동시성 제어

트랜잭션 관리와 동시성 제어는 데이터베이스에서 데이터의 정확성과 일관성을 유지하기 위해 중요한 개념입니다. 트랜잭션은 하나의 논리적 작업 단위를 나타내며, 동시성 제어는 동시에 여러 개의 트랜잭션이 동작할 때 데이터의 정확성을 유지하기 위한 기법입니다.

1. 트랜잭션 관리

– ACID 원칙: 트랜잭션의 원자성(Atomicity), 일관성(Consistency), 격리성(Isolation), 지속성(Durability)을 보장하기 위한 원칙입니다. ACID 원칙을 준수하여 트랜잭션이 정확하게 수행되도록 합니다.
– 트랜잭션 제어문: 트랜잭션을 시작하고 종료하는 제어문을 사용하여 트랜잭션을 관리합니다. 일반적으로 BEGIN, COMMIT, ROLLBACK과 같은 제어문을 사용합니다.

2. 동시성 제어

– 락(Lock) 기반 동시성 제어: 트랜잭션이 데이터를 읽거나 변경할 때, 다른 트랜잭션이 해당 데이터에 접근하지 못하도록 락을 설정하여 동시성을 제어합니다. 공유 락(Shared Lock)과 배타적 락(Exclusive Lock)을 사용하여 읽기와 쓰기 연산을 제어합니다.
– 격리 수준(Isolation Level): 트랜잭션이 동작하는 동안 다른 트랜잭션과의 격리 수준을 설정하여 데이터의 일관성을 유지합니다. 격리 수준은 READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE 등의 수준을 제공합니다.

3. 동시성 제어 예시 (MySQL)

락 기반 동시성 제어


-- 배타적 락 설정 (쓰기 락)
START TRANSACTION;
SELECT * FROM orders WHERE order_id = 1 FOR UPDATE;
-- 쓰기 작업 수행
UPDATE orders SET status = 'completed' WHERE order_id = 1;
COMMIT;

-- 공유 락 설정 (읽기 락)
START TRANSACTION;
SELECT * FROM orders WHERE status = 'processing' LOCK IN SHARE MODE;
-- 읽기 작업 수행
SELECT COUNT(*) FROM orders WHERE status = 'processing';
COMMIT;

격리 수준 설정


-- READ COMMITTED 격리 수준 설정
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

START TRANSACTION;
-- 트랜잭션 내에서 다른 트랜잭션에서 COMMIT되는 데이터에 대한 변경 내용을 볼 수 없음 (반복 가능한 읽기)
SELECT * FROM orders WHERE status = 'processing';
COMMIT;

데이터베이스 인덱스 구조와 대용량 데이터 처리

7.1 B-tree 인덱스의 효율적인 사용 방법

B-tree 인덱스는 가장 일반적으로 사용되는 인덱스 구조입니다. B-tree 인덱스의 효율적인 사용을 위해 다음과 같은 방법을 적용할 수 있습니다:

– 인덱스 열 선택: 인덱스를 생성할 열을 신중하게 선택하여 불필요한 인덱스 생성을 피합니다. 인덱스가 자주 사용되는 열이나 검색 조건에 자주 사용되는 열을 선택합니다.
– 인덱스 순서: 인덱스의 순서를 올바르게 지정하여 검색 성능을 향상시킵니다. 자주 사용되는 열을 먼저 지정하는 것이 좋습니다.
– 인덱스 컬럼 크기: 인덱스 키의 크기를 가능한 작게 설정하여 메모리 사용량을 줄이고 인덱스의 성능을 향상시킵니다.

7.2 컴포지트 인덱스의 활용

컴포지트 인덱스는 두 개 이상의 열을 조합하여 인덱스를 생성하는 것을 말합니다. 컴포지트 인덱스를 활용하여 다중 열에 대한 검색 성능을 향상시킬 수 있습니다.

– 열 선택: 컴포지트 인덱스를 생성할 때, 자주 사용되는 열을 앞에 위치시키는 것이 좋습니다.
– 검색 조건: 컴포지트 인덱스의 열 순서에 맞게 검색 조건을 작성하여 인덱스를 효율적으로 사용할 수 있습니다.
– 중복 값 제거: 중복 값이 많은 열을 컴포지트 인덱스에 포함시키면 인덱스의 공간 효율성이 떨어질 수 있으므로 중복 값을 가지는 열은 추가하지 않는 것이 좋습니다.

7.3 비트맵 인덱스의 장단점과 적용 사례

비트맵 인덱스는 비트맵 형태로 인덱싱되는 방식으로, 특정 값을 가지는 레코드를 찾을 때 유용합니다. 비트맵 인덱스의 장단점과 적용 사례는 다음과 같습니다:

– 장점:
– 저렴한 공간 사용: 비트맵 인덱스는 각 비트가 참 또는 거짓 값을 나타내므로 공간 효율적입니다.
– 빠른 검색 성능: 비트 연산을 통해 다중 비교 조건을 처리하기 때문에 검색 속도가 빠릅니다.

– 단점:
– 업데이트 작업에 대한 성능 저하: 비트맵 인덱스는 업데이트 시 해당 비트를 수정해야하므로 업데이트 작업에는 성능 저하가 발생할 수 있습니다.
– 칼럼의 기준 값 차이: 칼럼의 기준 값이 비트 형식으로 인덱싱되므로 기준 값이 매우 다양한 경우에는 비트맵 인덱스의 효율이 떨어질 수 있습니다.


-- 비트맵 인덱스 생성
CREATE BITMAP INDEX idx ON table(column);

-- 비트맵 인덱스 사용한 쿼리
SELECT * FROM table WHERE column = 'value';

쿼리 최적화를 위한 통계 정보 관리

쿼리 최적화를 위해 데이터베이스에서 효과적으로 통계 정보를 관리해야 합니다. 통계 정보는 쿼리 실행 계획을 결정하는 데 중요한 역할을 하며, 다음과 같은 방법으로 통계 정보를 관리합니다.

8.1 통계 정보 수집

– 자동 통계 수집: 대부분의 데이터베이스는 통계 정보를 자동으로 수집하는 기능을 제공합니다. 테이블 또는 열에 대해 변경된 데이터량 등을 기준으로 주기적으로 통계 정보를 업데이트합니다.
– 수동 통계 수집: 특정 시점에서 데이터의 통계 정보를 수동으로 수집할 수도 있습니다. 예를 들어, 데이터가 대량으로 변경된 경우에는 수동으로 통계 정보를 업데이트할 수 있습니다.

8.2 통계 정보 업데이트

– 테이블 통계 정보: 테이블의 행 수, 칼럼의 카디널리티(고유한 값의 수) 등을 포함한 통계 정보를 업데이트합니다. 이를 통해 쿼리 옵티마이저는 쿼리 실행 계획을 결정할 때 통계 정보를 참고하여 최적의 경로를 선택할 수 있습니다.
– 열 통계 정보: 각 열의 데이터 분포, 최소/최대 값 등을 포함한 통계 정보를 업데이트합니다. 열 통계 정보를 업데이트하는 것은 쿼리 옵티마이저가 인덱스 사용 여부와 같은 결정을 내리는 데 도움이 됩니다.

8.3 통계 정보 관리 예시 (Oracle)

자동 통계 수집


-- 자동 통계 수집 활성화
ALTER TABLE table_name AUTOTRACE ON;

-- 통계 정보 갱신 주기 설정
EXEC DBMS_STATS.SET_TABLE_STATS(
  ownname => 'schema_name',
  tabname => 'table_name',
  method_opt => 'FOR ALL COLUMNS SIZE AUTO'
);

수동 통계 수집


-- 수동 통계 수집
EXEC DBMS_STATS.GATHER_TABLE_STATS(
  ownname => 'schema_name',
  tabname => 'table_name',
  method_opt => 'FOR ALL COLUMNS SIZE AUTO'
);

잠금과 교착 상태 처리 방법

데이터베이스에서 여러 트랜잭션이 동시에 실행될 때 정확성과 일관성을 유지하기 위해 잠금(Lock)을 사용합니다. 하지만 잘못된 잠금 사용은 교착 상태(Deadlock)를 유발할 수 있으므로 이를 방지하고 처리하기 위한 방법을 알아야 합니다.

9.1 잠금의 종류

– 공유 잠금(Shared Lock): 읽기 작업을 수행하는 트랜잭션 간에 공유할 수 있는 잠금입니다. 다른 트랜잭션이 데이터를 수정하는 것을 방지합니다.
– 배타 잠금(Exclusive Lock): 쓰기 작업을 수행하는 트랜잭션이 해당 데이터를 배타적으로 소유하는 잠금입니다. 다른 트랜잭션의 읽기나 쓰기 작업을 방지합니다.

9.2 교착 상태 처리

– 대기 그래프(Wait-for graph) 기반 교착 상태 탐지: 대기 그래프를 생성하여 교착 상태를 감지하고, 감지된 교착 상태를 해결하기 위해 트랜잭션 중 하나를 롤백시킵니다.
– 시간 초과(Time-out) 기반 교착 상태 탐지: 특정 시간 동안 대기한 트랜잭션이 있는 경우 교착 상태로 간주하고 롤백시킵니다.
– 교착 상태 예방을 위한 트랜잭션 스케줄링: 트랜잭션을 잘 조정하여 교착 상태를 예방하는 방법입니다. 이를 위해 우선순위 기반 스케줄링 등을 사용할 수 있습니다.

9.3 교착 상태 처리 예시 (MySQL)


-- 교착 상태 진단
SHOW ENGINE INNODB STATUS;

-- 교착 상태 해결을 위한 트랜잭션 롤백
ROLLBACK;

데이터베이스 복제와 샤딩을 통한 확장성 개선

데이터베이스의 확장성을 개선하기 위해 데이터베이스 복제와 샤딩을 사용할 수 있습니다. 이를 통해 더 많은 트래픽과 데이터를 처리하고 높은 가용성을 제공할 수 있습니다.

10.1 데이터베이스 복제

데이터베이스 복제는 원본 데이터베이스에서 데이터의 사본을 여러 대의 서버에 복제하는 기술입니다. 원본 데이터베이스와 복제된 데이터베이스 사이의 연결이 비동기적으로 이루어지며, 다음과 같은 방법으로 사용됩니다.

– 읽기 전용 복제: 원본 데이터베이스의 읽기 작업을 여러 복제본에서 수행하여 읽기 처리량을 증가시킵니다.
– 고가용성을 위한 복제: 원본 데이터베이스의 장애 시에도 복제본으로 작업을 전환하여 서비스 중단을 최소화합니다.

10.2 데이터베이스 샤딩

데이터베이스 샤딩은 데이터를 분산 저장하여 트래픽과 데이터의 부하를 분산하는 기술입니다. 데이터를 여러 개의 샤드로 나누고, 각 샤드마다 별도의 데이터베이스를 운영합니다.

– 수평 샤딩: 테이블의 특정 기준(예: 사용자 ID)에 따라 데이터를 여러 개의 샤드에 분산 저장합니다.
– 수직 샤딩: 테이블의 열을 기준으로 데이터를 분리하여 여러 개의 샤드에 저장합니다.

10.3 데이터베이스 복제 및 샤딩 예시 (MongoDB)

데이터베이스 복제


// 원본 데이터베이스에 대한 복제 설정
rs.initiate();
rs.add("replica1.example.com");
rs.add("replica2.example.com");

데이터베이스 샤딩


// 데이터베이스 샤딩 구성
sh.status();
sh.enableSharding("myDB");
sh.shardCollection("myDB.myCollection", { "userID": "hashed" });

Leave a Comment