목차
- 지원하는 Index 타입 확인
- Sample Table 생성
- 테스트 케이스 1 (인덱스 X, 병렬처리 X)
- 테스트 케이스 2 (인덱스 X, 병렬처리 O)
- 테스트 케이스 3 (B-Tree 인덱스, 병렬처리 O)
- 테스트 케이스 4 (BRIN 인덱스, 병렬처리 O)
- 테스트 케이스 5 (BRIN 인덱스, 병렬처리 O, pages_per_range 수정)
지원하는 Index 타입 확인
설치된 PostgreSQL에서 지원하는 Index 타입 확인하는 방법은 아래와 같다.
Sample Table 생성
아래의 쿼리로 테스트를 진행할 샘플 테이블을 생성했다.
테스트용으로 대용량 데이터 쓰기를 위해서 Unlogged Table을 사용했다.
1
2
3
4
5
6
| CREATE UNLOGGED TABLE test_demo (
test_id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
test_name char(32),
test_value float NOT NULL,
test_date timestamptz NOT NULL
);
|
generate_series
을 사용해서 데이터를 생성했다.
1
2
3
| INSERT INTO test_demo (test_name, test_value, test_date)
SELECT md5(random()::text), hashtext(random()::text), test_date
FROM generate_series('2023-01-01 0:00'::timestamptz, '2024-12-31 23:59:59'::timestamptz,'1 seconds'::interval) as test_date;
|
테스트 케이스 1 (인덱스 X, 병렬처리 X)
1
2
3
4
5
6
7
8
9
10
| SET max_parallel_workers = 0;
SET max_parallel_maintenance_workers = 0;
EXPLAIN ANALYZE
SELECT test_date, test_name, sum(test_value)
FROM test_demo
WHERE test_date >= '2024-01-01'
and test_date <= '2024-01-31'
GROUP BY test_date, test_name
ORDER BY test_date, test_name;
|
인덱스도 없고, 병렬처리를 하지 않는 경우 24552.210 ms가 소요된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| Finalize GroupAggregate (cost=1149589.30..1172948.59 rows=187908 width=49) (actual time=24371.412..24535.105 rows=172800 loops=1)
" Group Key: (date_trunc('day'::text, test_date)), test_name"
-> Gather Merge (cost=1149589.30..1169425.31 rows=156590 width=49) (actual time=24371.408..24487.198 rows=172800 loops=1)
Workers Planned: 2
Workers Launched: 0
-> Partial GroupAggregate (cost=1148589.28..1150350.92 rows=78295 width=49) (actual time=24371.244..24477.733 rows=172800 loops=1)
" Group Key: (date_trunc('day'::text, test_date)), test_name"
-> Sort (cost=1148589.28..1148785.02 rows=78295 width=49) (actual time=24371.236..24424.043 rows=172800 loops=1)
" Sort Key: (date_trunc('day'::text, test_date)), test_name"
Sort Method: external merge Disk: 11160kB
-> Parallel Seq Scan on test_demo (cost=0.00..1139547.71 rows=78295 width=49) (actual time=494.892..24230.032 rows=172800 loops=1)
Filter: ((test_date >= '2024-01-30 00:00:00+00'::timestamp with time zone) AND (test_date <= '2024-01-31 23:59:59+00'::timestamp with time zone))
Rows Removed by Filter: 62985600
Planning Time: 0.129 ms
Execution Time: 24552.210 ms
|
테스트 케이스 2 (인덱스 X, 병렬처리 O)
1
2
3
4
5
6
7
8
9
| SET max_parallel_workers = 8;
EXPLAIN ANALYZE
SELECT date_trunc('day', test_date), test_name, sum(test_value)
FROM test_demo
WHERE test_date >= '2024-01-30 00:00:00'
and test_date <= '2024-01-31 23:59:59'
GROUP BY 1, 2
ORDER BY 1, 2;
|
인덱스는 없지만, 병렬처리를 하는 경우 7278.143 ms가 소요된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| Finalize GroupAggregate (cost=1149589.30..1172948.59 rows=187908 width=49) (actual time=7123.454..7263.586 rows=172800 loops=1)
" Group Key: (date_trunc('day'::text, test_date)), test_name"
-> Gather Merge (cost=1149589.30..1169425.31 rows=156590 width=49) (actual time=7123.449..7221.229 rows=172800 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial GroupAggregate (cost=1148589.28..1150350.92 rows=78295 width=49) (actual time=7009.714..7040.742 rows=57600 loops=3)
" Group Key: (date_trunc('day'::text, test_date)), test_name"
-> Sort (cost=1148589.28..1148785.02 rows=78295 width=49) (actual time=7009.705..7023.835 rows=57600 loops=3)
" Sort Key: (date_trunc('day'::text, test_date)), test_name"
Sort Method: external merge Disk: 3216kB
Worker 0: Sort Method: external merge Disk: 3216kB
Worker 1: Sort Method: external merge Disk: 4752kB
-> Parallel Seq Scan on test_demo (cost=0.00..1139547.71 rows=78295 width=49) (actual time=229.448..6945.629 rows=57600 loops=3)
Filter: ((test_date >= '2024-01-30 00:00:00+00'::timestamp with time zone) AND (test_date <= '2024-01-31 23:59:59+00'::timestamp with time zone))
Rows Removed by Filter: 20995200
Planning Time: 0.135 ms
Execution Time: 7278.143 ms
|
테스트 케이스 3 (B-Tree 인덱스, 병렬처리 O)
1
| CREATE INDEX test_b_tree_idx ON test_demo (test_date, test_name);
|
인덱스를 생성하는데 completed in 1 m 20 s 254 ms
만큼 소요되었다.
1
2
| VACUUM FREEZE ANALYZE;
SELECT pg_size_pretty(pg_relation_size('test_b_tree_idx'));
|
인덱스 용량은 4059 MB
정도 된다.
1
2
3
4
5
6
7
| EXPLAIN ANALYZE
SELECT date_trunc('day', test_date), test_name, sum(test_value)
FROM test_demo
WHERE test_date >= '2024-01-30 00:00:00'
and test_date <= '2024-01-31 23:59:59'
GROUP BY 1, 2
ORDER BY 1, 2;
|
B-Tree 인덱스를 추가하면 324.670 ms 정도 소요된다.
1
2
3
4
5
6
7
8
9
| GroupAggregate (cost=310448.23..314446.90 rows=177719 width=49) (actual time=229.743..318.625 rows=172800 loops=1)
" Group Key: (date_trunc('day'::text, test_date)), test_name"
-> Sort (cost=310448.23..310892.52 rows=177719 width=49) (actual time=229.736..262.584 rows=172800 loops=1)
" Sort Key: (date_trunc('day'::text, test_date)), test_name"
Sort Method: external merge Disk: 11160kB
-> Index Scan using test_b_tree_idx on test_demo (cost=0.56..288875.80 rows=177719 width=49) (actual time=2.495..83.365 rows=172800 loops=1)
Index Cond: ((test_date >= '2024-01-30 00:00:00+00'::timestamp with time zone) AND (test_date <= '2024-01-31 23:59:59+00'::timestamp with time zone))
Planning Time: 12.301 ms
Execution Time: 324.670 ms
|
테스트 케이스 4 (BRIN 인덱스, 병렬처리 O)
1
| CREATE INDEX test_brin_idx ON test_demo USING brin(test_date, test_name);
|
인덱스를 생성하는데 completed in 22 s 709 ms
만큼 소요되었다.
1
2
| VACUUM FREEZE ANALYZE;
SELECT pg_size_pretty(pg_relation_size('test_brin_idx'));
|
인덱스 용량은 560 kB
정도 된다.
1
2
3
4
5
6
7
| EXPLAIN ANALYZE
SELECT date_trunc('day', test_date), test_name, sum(test_value)
FROM test_demo
WHERE test_date >= '2024-01-30 00:00:00'
and test_date <= '2024-01-31 23:59:59'
GROUP BY 1, 2
ORDER BY 1, 2;
|
BRIN 인덱스를 추가하면 293.568 ms 정도 소요된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| Finalize GroupAggregate (cost=729620.32..750195.10 rows=165508 width=49) (actual time=154.738..287.291 rows=172800 loops=1)
" Group Key: (date_trunc('day'::text, test_date)), test_name"
-> Gather Merge (cost=729620.32..747091.82 rows=137924 width=49) (actual time=154.731..241.458 rows=172800 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial GroupAggregate (cost=728620.30..730171.94 rows=68962 width=49) (actual time=73.483..99.565 rows=57600 loops=3)
" Group Key: (date_trunc('day'::text, test_date)), test_name"
-> Sort (cost=728620.30..728792.70 rows=68962 width=49) (actual time=73.474..82.143 rows=57600 loops=3)
" Sort Key: (date_trunc('day'::text, test_date)), test_name"
Sort Method: external merge Disk: 7624kB
Worker 0: Sort Method: quicksort Memory: 587kB
Worker 1: Sort Method: external merge Disk: 3224kB
-> Parallel Bitmap Heap Scan on test_demo (cost=309.89..720718.99 rows=68962 width=49) (actual time=1.077..18.485 rows=57600 loops=3)
Recheck Cond: ((test_date >= '2024-01-30 00:00:00+00'::timestamp with time zone) AND (test_date <= '2024-01-31 23:59:59+00'::timestamp with time zone))
Rows Removed by Index Recheck: 4480
Heap Blocks: lossy=1309
-> Bitmap Index Scan on test_brin_idx (cost=0.00..268.51 rows=173620 width=0) (actual time=1.661..1.661 rows=19200 loops=1)
Index Cond: ((test_date >= '2024-01-30 00:00:00+00'::timestamp with time zone) AND (test_date <= '2024-01-31 23:59:59+00'::timestamp with time zone))
Planning Time: 0.159 ms
Execution Time: 293.568 ms
|
테스트 케이스 5 (BRIN 인덱스, 병렬처리 O, pages_per_range 수정)
pages_per_range
은 기본값이 128이다.
이 값을 다양한 값으로 변경하여 테스트를 해보려고 한다.
처음에는 64로 변경을 해보았다.
1
| CREATE INDEX test_brin_idx ON test_demo USING brin(test_date, test_name) WITH (pages_per_range=64);
|
1
2
| VACUUM FREEZE ANALYZE;
SELECT pg_size_pretty(pg_relation_size('test_brin_idx'));
|
pages_per_range
을 64로 변경할 경우 인덱스 용량은 1104 kB
정도 된다.
64로 변경하였을때는 261.137 ms 정도 소요되었다. 요약된 데이터가 좀 더 정확해지고, 인덱스 스캔 중에 더 많은 데이터 블록을 건너 뛰어서 소폭 조회 속도가 빨라졌다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| Finalize GroupAggregate (cost=772713.86..796606.30 rows=192197 width=49) (actual time=114.417..255.227 rows=172800 loops=1)
" Group Key: (date_trunc('day'::text, test_date)), test_name"
-> Gather Merge (cost=772713.86..793002.61 rows=160164 width=49) (actual time=114.409..209.847 rows=172800 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial GroupAggregate (cost=771713.84..773515.68 rows=80082 width=49) (actual time=66.951..91.961 rows=57600 loops=3)
" Group Key: (date_trunc('day'::text, test_date)), test_name"
-> Sort (cost=771713.84..771914.04 rows=80082 width=49) (actual time=66.944..74.350 rows=57600 loops=3)
" Sort Key: (date_trunc('day'::text, test_date)), test_name"
Sort Method: external merge Disk: 5952kB
Worker 0: Sort Method: external merge Disk: 2616kB
Worker 1: Sort Method: external merge Disk: 2616kB
-> Parallel Bitmap Heap Scan on test_demo (cost=576.63..762450.98 rows=80082 width=49) (actual time=0.853..17.499 rows=57600 loops=3)
Recheck Cond: ((test_date >= '2024-01-30 00:00:00+00'::timestamp with time zone) AND (test_date <= '2024-01-31 23:59:59+00'::timestamp with time zone))
Rows Removed by Index Recheck: 2411
Heap Blocks: lossy=1024
-> Bitmap Index Scan on test_brin_idx (cost=0.00..528.58 rows=195539 width=0) (actual time=2.220..2.221 rows=18560 loops=1)
Index Cond: ((test_date >= '2024-01-30 00:00:00+00'::timestamp with time zone) AND (test_date <= '2024-01-31 23:59:59+00'::timestamp with time zone))
Planning Time: 0.108 ms
Execution Time: 261.137 ms
|
그 다음은 32로 변경을 해보았다.
1
| CREATE INDEX test_brin_idx ON test_demo USING brin(test_date, test_name) WITH (pages_per_range=32);
|
1
2
| VACUUM FREEZE ANALYZE;
SELECT pg_size_pretty(pg_relation_size('test_brin_idx'));
|
pages_per_range
을 32 변경할 경우 인덱스 용량은 2200 kB
정도 된다.
32로 변경하였을때는 382.216 ms 정도 소요되었다.
기본값보다 안좋은 결과가 나왔고, B-Tree보다 안좋은 결과가 나왔다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| Finalize GroupAggregate (cost=762248.43..785662.02 rows=188345 width=49) (actual time=221.268..382.792 rows=172800 loops=1)
" Group Key: (date_trunc('day'::text, test_date)), test_name"
-> Gather Merge (cost=762248.43..782130.55 rows=156954 width=49) (actual time=221.256..329.125 rows=172800 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial GroupAggregate (cost=761248.41..763014.14 rows=78477 width=49) (actual time=86.887..117.966 rows=57600 loops=3)
" Group Key: (date_trunc('day'::text, test_date)), test_name"
-> Sort (cost=761248.41..761444.60 rows=78477 width=49) (actual time=86.880..98.141 rows=57600 loops=3)
" Sort Key: (date_trunc('day'::text, test_date)), test_name"
Sort Method: external merge Disk: 7624kB
Worker 0: Sort Method: external merge Disk: 3552kB
Worker 1: Sort Method: quicksort Memory: 25kB
-> Parallel Bitmap Heap Scan on test_demo (cost=1096.65..752183.73 rows=78477 width=49) (actual time=1.662..22.401 rows=57600 loops=3)
Recheck Cond: ((test_date >= '2024-01-30 00:00:00+00'::timestamp with time zone) AND (test_date <= '2024-01-31 23:59:59+00'::timestamp with time zone))
Rows Removed by Index Recheck: 1376
Heap Blocks: lossy=1245
-> Bitmap Index Scan on test_brin_idx (cost=0.00..1049.56 rows=189810 width=0) (actual time=4.645..4.645 rows=18240 loops=1)
Index Cond: ((test_date >= '2024-01-30 00:00:00+00'::timestamp with time zone) AND (test_date <= '2024-01-31 23:59:59+00'::timestamp with time zone))
Planning Time: 13.381 ms
Execution Time: 389.999 ms
|
마지막으로 16으로 변경을 해보았다.
1
| CREATE INDEX test_brin_idx ON test_demo USING brin(test_date, test_name) WITH (pages_per_range=16);
|
1
2
| VACUUM FREEZE ANALYZE;
SELECT pg_size_pretty(pg_relation_size('test_brin_idx'));
|
pages_per_range
을 64로 변경할 경우 인덱스 용량은 4368 kB
정도 된다.
B-Tree 인덱스 용량보다 커졌다.
16으로 변경하였을때는 387.908 ms 정도 소요되었다.
역시나 32보다 안좋은 결과를 확인할 수 있었다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| Finalize GroupAggregate (cost=705059.18..724934.03 rows=159878 width=49) (actual time=212.402..379.386 rows=172800 loops=1)
" Group Key: (date_trunc('day'::text, test_date)), test_name"
-> Gather Merge (cost=705059.18..721936.32 rows=133232 width=49) (actual time=212.398..324.537 rows=172800 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial GroupAggregate (cost=704059.16..705558.02 rows=66616 width=49) (actual time=112.612..145.726 rows=57600 loops=3)
" Group Key: (date_trunc('day'::text, test_date)), test_name"
-> Sort (cost=704059.16..704225.70 rows=66616 width=49) (actual time=112.600..122.132 rows=57600 loops=3)
" Sort Key: (date_trunc('day'::text, test_date)), test_name"
Sort Method: external merge Disk: 6784kB
Worker 0: Sort Method: quicksort Memory: 1286kB
Worker 1: Sort Method: external merge Disk: 3640kB
-> Parallel Bitmap Heap Scan on test_demo (cost=2122.45..696443.52 rows=66616 width=49) (actual time=4.240..30.279 rows=57600 loops=3)
Recheck Cond: ((test_date >= '2024-01-30 00:00:00+00'::timestamp with time zone) AND (test_date <= '2024-01-31 23:59:59+00'::timestamp with time zone))
Rows Removed by Index Recheck: 859
Heap Blocks: lossy=1103
-> Bitmap Index Scan on test_brin_idx (cost=0.00..2082.48 rows=161176 width=0) (actual time=12.424..12.424 rows=18080 loops=1)
Index Cond: ((test_date >= '2024-01-30 00:00:00+00'::timestamp with time zone) AND (test_date <= '2024-01-31 23:59:59+00'::timestamp with time zone))
Planning Time: 0.184 ms
Execution Time: 387.908 ms
|
pages_per_range
의 값을 작게 하는것은 인덱스 스캔 중에 더 많은 데이터 블록을 건너 뛰는것보다 페이지를 계속 읽는 과정에서 오버헤드가 발생해서 느려진것 같다.
Test 결과로는 BRIN 인덱스를 사용하는 것이 합리적이다.
B-Tree 인덱스보다 쿼리 조회 응답시간이 빠르고, 인덱스 용량이 훨씬 적기 때문이다.
하지만 실제 테이블 구성과, 환경 구성에 따라서 결과가 다를 수 있기 때문에 직접 테스트해보고 결정하는것이 좋을 것 같다.