12. SPOQA conference 2021 : Always Evolving!
간단한 Index Scan 분석
EXPLAIN ANALYZE
SELECT *
FROM big_table
WHERE id = 123456789;
2억 row 이상
Index Scan using big_table_pkey on big_table (cost=...)
Index Cond: (id = 123456789)
Planning time: 0.802 ms
Execution time: 2.660 ms
13. SPOQA conference 2021 : Always Evolving!
인덱스 구조 요약
• 인덱스란 단어 뜻 그대로, 책의 색인, 목차와 같은 역할
• 테이블에는 각 row의 물리적(또는 논리적) 위치를 나타내는 내부 컬럼이 있다
• 인덱스의 각 노드에는 인덱스를 건 컬럼의 값과 해당 row의 위치를 나타내는 값을 가지고 있다
14. SPOQA conference 2021 : Always Evolving!
인덱스 응용 맛보기
Multi-column index와 Index on expression
15. SPOQA conference 2021 : Always Evolving!
Multi-column 인덱스
1 2 3
2 7 1
3 9 1
42 3 3
.
.
.
ID A B C D
18. SPOQA conference 2021 : Always Evolving!
Multi-column 인덱스 사용 예
CREATE TABLE purchase (
id int PRIMARY KEY,
store_id int,
created_at timestamp,
...
);
매장에서 상품을 구매한 내역을 저장.
(store_id) 매장에서
(created_at)에 물건을 구매함.
19. SPOQA conference 2021 : Always Evolving!
Multi-column 인덱스 사용 예
CREATE INDEX ix_purchase_store_id_created_at
ON purchase (store_id, created_at);
02/06
13:03
02/06
15:42
02/07
09:22
store_id
created_at
1 번 매장
02/06
12:11
02/07
08:59
02/07
10:30
2 번 매장
02/06
19:41
02/06
21:02
3번 매장
< < < < <
20. SPOQA conference 2021 : Always Evolving!
Multi-column 인덱스 사용 예
02/06
13:03
02/06
15:42
02/07
09:22
store_id
created_at
1 번 매장
02/06
12:11
02/07
08:59
02/07
10:30
2 번 매장
02/06
19:41
02/06
21:02
3번 매장
2번 매장의 최근 구매내역 10개를
가져오고 싶어요
SELECT purchase.*
FROM purchase
WHERE store_id = 2
ORDER BY created_at DESC
LIMIT 10
< < < < <
21. SPOQA conference 2021 : Always Evolving!
Multi-column 인덱스 분석
Limit (cost=...)
-> Index Scan Backward using ix_purchase_store_id_created_at on purchase (cost=...)
Index Cond: (store_id = 2)
Planning time: 0.782 ms
Execution time: 1.460 ms
EXPLAIN ANALYZE
SELECT purchase.*
FROM purchase
WHERE store_id = 2
ORDER BY created_at DESC
LIMIT 10
22. SPOQA conference 2021 : Always Evolving!
Multi-column 인덱스 요약
• 앞에서 부터 정렬되기 때문에 순서에 매우 유의!
• (A, B) 인덱스가 (A) 인덱스를 대체할 수 있음 (인덱스 수 절약)
• 뒤쪽 컬럼에서 부등식이나 order by를 사용하면 효율적
23. SPOQA conference 2021 : Always Evolving!
Index on expression
1 2 3 -1
2 7 1 6
3 9 1 8
42 3 3 0
.
.
.
ID A B C D A - B
24. SPOQA conference 2021 : Always Evolving!
Index on expression
CREATE INDEX ix_table_a_b ON table ((A - B));
1
1
0
< < = = < < = <
3
1
2
1
2
-1
4
2
2
5
2
3
2
3
-1
2
4
-2
1
5
-4
4
5
-1
A
B
A - B
25. SPOQA conference 2021 : Always Evolving!
Index on expression 사용 예
CREATE TABLE store (
id int PRIMARY KEY,
...
updated_at timestamp,
synced_at timestamp,
);
외부 서비스에 동기화하는 테이블
updated_at > synced_at인
row만 골라서 동기화한다.
26. SPOQA conference 2021 : Always Evolving!
Index on expression 사용 예
CREATE INDEX ix_store_update_at_minus_synced_at
ON store ((updated_at - synced_at))
updated_at
synced_at
updated_at
- synced_at
02/06
12:00
02/06
9:00
3시간
02/06
15:20
02/06
11:10
4시간 10분
02/06
12:30
02/06
12:00
30분
02/05
12:10
02/05
12:10
0분
02/04
17:42
02/06
17:42
0분
02/06
09:30
02/06
08:30
1시간
<
< < <
=
27. SPOQA conference 2021 : Always Evolving!
Index on expression 사용 예
SELECT store.*
FROM store
WHERE updated_at - synced_at > INTERVAL ‘0 day’
LIMIT 100
updated_at
synced_at
updated_at
- synced_at
02/06
12:00
02/06
9:00
3시간
02/06
15:20
02/06
11:10
4시간 10분
02/06
12:30
02/06
12:00
30분
02/05
12:10
02/05
12:10
0분
02/04
17:42
02/06
17:42
0분
02/06
09:30
02/06
08:30
1시간
<
< < <
=
28. SPOQA conference 2021 : Always Evolving!
Index on expression 분석
EXPLAIN ANALYZE
SELECT store.*
FROM store
WHERE updated_at - synced_at > INTERVAL ‘0 day’
LIMIT 100
Limit (cost=...)
-> Index Scan using ix_store_updated_at_minus_synced_at on store (cost=...)
Index Cond: ((updated_at - synced_at) > '00:00:00'::interval)
Planning time: 0.598 ms
Execution time: 4.711 ms
29. SPOQA conference 2021 : Always Evolving!
Index on expression 요약
• 컬럼 그 자체의 값이 아닌, 함수를 적용한 값,
여러 컬럼으로 계산된 값에도 인덱스를 걸 수 있다
31. SPOQA conference 2021 : Always Evolving!
두 테이블의 JOIN
a_id b_id
1 2
2
3 1
4
A
b_id b_attr
1 spoqa
2 con
3 2021
4 !
B
1 : 0, 1
32. SPOQA conference 2021 : Always Evolving!
두 테이블의 JOIN
SELECT * FROM A INNER JOIN B ON A.b_id = B.b_id
SELECT * FROM A LEFT OUTER JOIN B ON A.b_id = B.b_id
33. SPOQA conference 2021 : Always Evolving!
INNER JOIN
• 양쪽에 모두 존재하는 데이터만 보여 줌
• 대충 쿼리해도 null 값 없이 원하는 값이 나옴
a_id b_id b_attr
1 2 con
3 1 spoqa
SELECT * FROM A INNER JOIN B ON A.b_id = B.b_id
34. SPOQA conference 2021 : Always Evolving!
OUTER JOIN
• 기준이 되는 A 테이블은 모든 데이터가 나옴
• B 테이블은 있으면 붙고 없으면 null로 대체 됨
• B의 존재 유무가 A의 결과에 영향을 끼치지 않기 때문에
데이터 로딩에 많이 사용 됨 (sqlalchemy joinedload)
a_id b_id b_attr
1 2 con
2
3 1 spoqa
4
SELECT * FROM A LEFT OUTER JOIN B ON A.b_id = B.b_id
36. SPOQA conference 2021 : Always Evolving!
매우 느린 백오피스 쿼리
SELECT COUNT(*)
FROM coupon_promotion cp
JOIN coupon ON cp.coupon_id = coupon.id
• 백오피스 페이지네이션에 쓰이는 쿼리
• coupon_promotion과 coupon은 1:1 관계
• JOIN 없으면 1초 정도 걸림
37. SPOQA conference 2021 : Always Evolving!
백오피스 쿼리 분석
EXPLAIN ANALYZE
SELECT COUNT(*)
FROM coupon_promotion cp
JOIN coupon ON cp.coupon_id = coupon.id
...(생략)
-> Parallel Seq Scan on coupon_promotion (…)
...
-> Index Only Scan using coupon_pkey on coupon (...)
...
Planning time: 0.697 ms
Execution time: 94362.596 ms
1:1 관계임에도 불구하고
각 coupon_promotion 마다
coupon의 존재를 확인함
* 타 DBMS에서는 다를지도..?
foreign key + not null
38. SPOQA conference 2021 : Always Evolving!
OUTER JOIN으로 변경
EXPLAIN ANALYZE
SELECT COUNT(*)
FROM coupon_promotion cp
LEFT OUTER JOIN coupon ON cp.coupon_id = coupon.id
...(생략)
-> Parallel Seq Scan on coupon_promotion (cost=...)
Planning time: 0.410 ms
Execution time: 1047.600 ms
coupon의 존재여부를 확인하지 않음
39. SPOQA conference 2021 : Always Evolving!
INNER / OUTER JOIN 요약
• 데이터 로딩에는 OUTER JOIN이 유용하게 쓰인다
• OUTER JOIN이 쿼리 플래너에게 큰 힌트가 될 수 있다
41. SPOQA conference 2021 : Always Evolving!
참고자료들
• ctid: postgresql - system-columns
• Btree 등 self-balancing balanced search tree
• Wiki - Self-balancing binary search tree
• Wiki - B-tree
• postgresql - btree
• 시간복잡도: Wiki - Time complexity
• Multi-column index (composite index): postgresql - indexes-multicolumn
• Index on expression: postgresql - indexes-expressional
• mysql 에서는 virtual column + index / function based index
• 쿼리 플랜 유도
• postgresql - planner-optimizer
• postgresql - explicit-joins