상세 컨텐츠

본문 제목

Database Index 개념 및 분석

카테고리 없음

by 또또니엘 2024. 11. 14. 01:21

본문

🛫  서론

본 글은 데이터베이스 index에 대해 공부해보고자 한다. 

 

🧐  Index?

PostgreSQL 공식 문서에서는 인덱스를 아래와 같이 말하고 있다.

Indexes are a common way to enhance database performance. An index allows the database server to find and retrieve specific rows much faster than it could do without an index. But indexes also add overhead to the database system as a whole, so they should be used sensibly.

 

인덱스는 데이터베이스 성능을 향상시키는 일반적인 방법이다. 인덱스를 사용하면 데이터베이스 서버가 인덱스가 없는 경우보다 훨씬 더 빨리 특정 행을 찾고 검색할 수 있다. 하지만 인덱스는 데이터베이스 시스템 전체에 오버헤드를 추가하므로 이를 현명하게 사용해야 한다.

 

* 오버헤드 : 어떤 처리를 하기 위해 들어가는 간접적인 처리 시간 · 메모리 등을 말한다.

 

PostgreSQL은 B-Tree, Hash, GiST, SP-GiST, GIN, BRIN 등 총 6가지 인덱스 방식을 제공합니다. 이 중 B-Tree가 기본 설정이며 가장 일반적으로 사용된다.

 

😃  B-Tree

각 노드가 여러 개의 자식 노드를 가질 수 있어 트리의 높이를 낮추고, 빠른 데이터 접근이 가능하게 설계된 것이 특징이며, 주요한 B-Tree의 특성은 다음과 같다.



- 루트 노드, 브랜치 노드, 리프 노드로 구성
- 항상 균형 잡힌 트리로 구성되어 성능에 안정성을 가짐
- 각 노드는 여러 개의 키와 포인터를 가짐
- 리프 노드는 데이터 주소를 가리키는 포인터를 가짐

 

📈  Index 성능 분석

1️⃣  단일 인덱스에 대한 성능 분석 - 유저 포인트 조회

💬  유저의 포인트를 조회한다는 가정하에 성능을 테스트해보고자 한다.

 

포인트 테이블 생성

CREATE TABLE point (
    id SERIAL PRIMARY KEY,
    user_id BIGINT,
    amount BIGINT NOT NULL DEFAULT 0,
    created_date timestamp(6) NOT NULL DEFAULT now()
)

* 인덱스 추가
CREATE INDEX idx_user_id ON point(user_id);

 

더미 데티어 생성

INSERT INTO point (user_id, amount)
SELECT
    generate_series, 10000
FROM generate_series(1, 10000000);

 

* generate_series 특정 value에 따라 행의 개수를 늘리기 위해 사용

 

조회

SELECT * FROM point
WHERE user_id = 100000;


성능 비교

인덱스 X

Gather  (cost=1000.00..126613.85 rows=1 width=28)
  Workers Planned: 2
  ->  Parallel Seq Scan on point  (cost=0.00..125613.75 rows=1 width=28)
        Filter: (user_id = 100000)
JIT:
  Functions: 2
  Options: Inlining false, Optimization false, Expressions true, Deforming true

 

조회 속도 (ms)

- 369, 353, 337, 331, 331, 335, 326, 335, 313, 344 

평균 337.4


인덱스(user_id) O 


아래 결과와 같이 유저 아이디 Index를  사용하여 검색하는것을 알수있다

Index Scan using idx_user_id on point  (cost=0.43..8.45 rows=1 width=28)
  Index Cond: (user_id = 100000)

 

조회 속도(ms)
- 34, 33, 38, 38, 47, 36, 26, 31, 25, 29
평균 33.7

💎 결과 - 인덱스 유무에따라 10배 정도의 성능 차이가 나타난다

 

2️⃣  복합 인덱스에 대한 성능 분석 - 예약 가능 스케쥴 목록 조회

💬  콘서트 중 예약 가능한 스케쥴 목록을 조회한다는 가정하에 성능을 테스트해보고자 한다.

 

콘서트, 콘서트 스케쥴 테이블 생성

CREATE TABLE concert (
    id SERIAL PRIMARY KEY,
    created_date TIMESTAMP(6) NOT NULL DEFAULT NOW()
)

CREATE TABLE concert_schedule (
	id SERIAL PRIMARY KEY,
    concert_id BIGINT NOT NULL,
    performance_date TIMESTAMP(6) NOT NULL,
    booking_start_date TIMESTAMP(6) NOT NULL,
    booking_end_date TIMESTAMP(6) NOT NULL,
	created_date TIMESTAMP(6) NOT NULL DEFAULT NOW()
)

 

더미 데티어 생성

INSERT INTO concert
SELECT
FROM generate_series(1, 10000000);

WITH schedule_data AS (
  SELECT
    floor(random() * 100000) + 1 AS concert_id,
    CASE
      WHEN random() < 0.5 THEN
        CURRENT_TIMESTAMP - (random() * INTERVAL '30 days')
      ELSE
        CURRENT_TIMESTAMP + (random() * INTERVAL '30 days')
    END AS booking_start_date
  FROM generate_series(1, 10000000)
)

INSERT INTO concert_schedule (concert_id, booking_start_date, booking_end_date, performance_date)
SELECT
  concert_id,
  booking_start_date,
  booking_start_date + INTERVAL '50 days' AS booking_end_date,
  booking_start_date + INTERVAL '60 days' AS performance_date
FROM schedule_data;

 

조회

SELECT * FROM concert_schedules
LEFT JOIN concerts ON concert_id = concerts.id
WHERE
	concert_id = 100
	AND booking_start_at <= CURRENT_TIMESTAMP
  AND booking_end_at > CURRENT_TIMESTAMP;

 

성능 비교

인덱스 X

Nested Loop Left Join  (cost=1000.43..188222.14 rows=50 width=56)
  ->  Gather  (cost=1000.00..188213.06 rows=50 width=44)
        Workers Planned: 2
        ->  Parallel Seq Scan on concert_schedule  (cost=0.00..187208.06 rows=21 width=44)
              Filter: ((concert_id = 100) AND (booking_start_date <= CURRENT_TIMESTAMP) AND (booking_end_date > CURRENT_TIMESTAMP))
  ->  Materialize  (cost=0.43..8.46 rows=1 width=12)
        ->  Index Scan using concert_pkey on concert  (cost=0.43..8.45 rows=1 width=12)
              Index Cond: (id = 100)
JIT:
  Functions: 6
  Options: Inlining false, Optimization false, Expressions true, Deforming true

 

조회 속도(ms)

- 398, 424, 474, 501, 401, 395, 388, 465, 484, 428

평균 435.8

인덱스 O (concert_id)

Nested Loop Left Join  (cost=5.63..406.71 rows=50 width=56)
  ->  Bitmap Heap Scan on concert_schedule  (cost=5.20..397.63 rows=50 width=44)
        Recheck Cond: (concert_id = 100)
        Filter: ((booking_start_date <= CURRENT_TIMESTAMP) AND (booking_end_date > CURRENT_TIMESTAMP))
        ->  Bitmap Index Scan on idx_concert_id  (cost=0.00..5.18 rows=100 width=0)
              Index Cond: (concert_id = 100)
  ->  Materialize  (cost=0.43..8.46 rows=1 width=12)
        ->  Index Scan using concert_pkey on concert  (cost=0.43..8.45 rows=1 width=12)
              Index Cond: (id = 100)

 

조회 속도(ms)

- 47, 40, 39, 36, 31, 42, 27, 27, 31, 39
평균 35.9

 

인덱스 O (concert_id, booking_start_date)

Nested Loop Left Join  (cost=5.40..215.63 rows=51 width=56)
  ->  Bitmap Heap Scan on concert_schedule  (cost=4.96..206.53 rows=51 width=44)
        Recheck Cond: ((concert_id = 100) AND (booking_start_date <= CURRENT_TIMESTAMP))
        Filter: (booking_end_date > CURRENT_TIMESTAMP)
        ->  Bitmap Index Scan on idx_concert_booking_start_date  (cost=0.00..4.95 rows=51 width=0)
              Index Cond: ((concert_id = 100) AND (booking_start_date <= CURRENT_TIMESTAMP))
  ->  Materialize  (cost=0.43..8.46 rows=1 width=12)
        ->  Index Scan using concert_pkey on concert  (cost=0.43..8.45 rows=1 width=12)
              Index Cond: (id = 100)

 

조회 속도(ms)

- 30, 30, 42, 35, 25, 35, 30, 43, 36, 41

평균 34.7

 

인덱스 O (concert_id, booking_start_date, booking_end_date)

Nested Loop Left Join  (cost=5.65..215.88 rows=51 width=56)
  ->  Bitmap Heap Scan on concert_schedule  (cost=5.22..206.79 rows=51 width=44)
        Recheck Cond: ((concert_id = 100) AND (booking_start_date <= CURRENT_TIMESTAMP) AND (booking_end_date > CURRENT_TIMESTAMP))
        ->  Bitmap Index Scan on idx_concert_booking_start_date_booking_end_date  (cost=0.00..5.20 rows=51 width=0)
              Index Cond: ((concert_id = 100) AND (booking_start_date <= CURRENT_TIMESTAMP) AND (booking_end_date > CURRENT_TIMESTAMP))
  ->  Materialize  (cost=0.43..8.46 rows=1 width=12)
        ->  Index Scan using concert_pkey on concert  (cost=0.43..8.45 rows=1 width=12)
              Index Cond: (id = 100)

 

조회 속도(ms)

- 64, 39, 57, 42, 30, 40, 31, 34, 29, 33

평균 39.9

💎 결과 - 역시 인덱스 유무에따라 10배 이상의 성능 차이가 나타나지만 단일 인덱스와 복합인데스의 차이는 미미해서 차이가 거의 없다고 볼 수 있다.

 

🎉  결론 

  1. 높은 Cardinality를 가진 컬럼의 인덱스 효과가 큼
  2. 복합 인덱스는 신중하게 설계해야 하며, 항상 더 많은 컬럼을 포함하는 것이 좋은 것은 아님
  3. 실제 데이터 분포와 쿼리 패턴을 고려한 인덱스 설계가 중요하다