[DB] PK๊ฐ ์๋๋ฐ ์ ์ฟผ๋ฆฌ๊ฐ ๋๋ฆด๊น - ๋ณตํฉ์ธ๋ฑ์ค ์ ๋์ปฌ๋ผ
by Dongwoongkim์ธ๋ฑ์ค ์ค๊ณ๋ ๋จ์ํ "์ด๋ค ์ปฌ๋ผ์ ํฌํจ์ํฌ๊น"์ ๋ฌธ์ ๊ฐ ์๋๋ค. ์ด๋ค ์์๋ก ์ ๋ ฌํ ๊น๊ฐ ๋ ์ค์ํ๋ค. ๋ณตํฉ PK๊ฐ ์กํ์๋ค๊ณ ํด์ ๊ทธ ์์ ๋ค์ด์๋ ์ปฌ๋ผ์ผ๋ก ์กฐ๊ฑด์ ๊ฑธ๊ธฐ๋ง ํ๋ฉด ๋นจ๋ผ์ง ๊ฑฐ๋ผ๊ณ ์๊ฐํ๋๋ฐ, ๊ทธ๊ฒ ์๋์๋ค.
์ด๋ฒ ๊ธ์์๋ PK๊ฐ ์์์๋ ์ฟผ๋ฆฌ๊ฐ ํ์ค์บ์ ๊ฐ๊น๊ฒ ๋์ํ๋ ์ฌ๋ก๋ฅผ ์ ๋ฆฌํ๊ณ , ๋ณตํฉ B-tree ์ธ๋ฑ์ค์ ์ ๋ ์ปฌ๋ผ(leading column) ์๋ฆฌ๋ฅผ ์ง์ด๋ณธ๋ค.
0. ๋ฌธ์ ์ํฉ
๋ฆฌํฌํ ํ๋ฉด ํ๋๊ฐ ๋๋ ธ๋ค. ์์๋ก ์ง์ ๋ณ·์ฐ์๋ณ ๋งค์ถ ํต๊ณ๋ฅผ ๋ณด์ฌ์ฃผ๋ ํ์ด์ง์ธ๋ฐ, ํ ์ง์ ๋ฐ์ดํฐ๊ฐ 5์ฒ ๊ฑด ์์ค์ด ๋๋ฉด ์๋ต์ด 200ms ์ด์์ผ๋ก ๋์ด์ก๋ค.
ํ ์ด๋ธ ๊ตฌ์กฐ๋ ๋๋ต ์ด๋ ๋ค(์์).
CREATE TABLE analytics.sales_monthly_stats (
sales_id bigint NOT NULL,
year_mm varchar(6) NOT NULL, -- '202403' ํ์
branch_cd varchar(10) NOT NULL,
amount numeric(18,2),
-- ... ์ปฌ๋ผ ๋ค์
CONSTRAINT sales_monthly_stats_pk
PRIMARY KEY (sales_id, year_mm, branch_cd)
);
๊ฐ์ฅ ๋ง์ด ํธ์ถ๋๋ ์ฟผ๋ฆฌ๋ ๋ค์๊ณผ ๊ฐ๋ค.
SELECT count(*)
FROM analytics.sales_monthly_stats
WHERE branch_cd = 'B001'
AND year_mm = '202403';
branch_cd๋ year_mm๋ PK ์์ ํฌํจ๋์ด ์๋ค. ๊ทธ๋ฌ๋ ์ธ๋ฑ์ค๋ฅผ ์ ํ๊ณ ์์ ๊ฑฐ๋ผ๊ณ ์ฒ์์ ๊ทธ๋ ๊ฒ ์๊ฐํ๋ค.
1. 1์ฐจ ์ง๋จ - "PK๊ฐ ์์ผ๋๊น ๋น ๋ฅด๊ฒ ์ง"
์ฒ์์๋ ์ ํ๋ฆฌ์ผ์ด์ ์ชฝ์ ์์ฌํ๋ค. JOIN์ ์ค์ด๊ณ , N+1์ ํ๊ณ , ํด๋ผ์ด์ธํธ ๋ ๋๋ง ์ฝ๋๋ ์๋ดค๋ค. ๊ทธ๋ฐ๋ฐ๋ ์ฟผ๋ฆฌ ๋จ๋ ์ผ๋ก 200ms๊ฐ ๋์๋ค.
์ฟผ๋ฆฌ๋ฅผ ์ง์ ์์ฌํด๋ด๋ WHERE ์กฐ๊ฑด์ ๋ ์ปฌ๋ผ์ด ๋ชจ๋ PK ์์ ์์ผ๋ ํ์ค์บ์ ์๋ ๊ฒ ๊ฐ์๋ค. ํ์ธ ์ฐจ EXPLAIN์ ๋ ๋ดค๋ค.
EXPLAIN (ANALYZE, BUFFERS)
SELECT count(*)
FROM analytics.sales_monthly_stats
WHERE branch_cd = 'B001'
AND year_mm = '202403';
Aggregate (cost=64614.63..64614.64 rows=1 width=8)
(actual time=217.564..217.566 rows=1 loops=1)
Buffers: shared hit=19054
-> Index Only Scan using sales_monthly_stats_pk on sales_monthly_stats
(cost=0.43..64613.38 rows=497 width=0)
(actual time=176.794..217.527 rows=438 loops=1)
Index Cond: ((year_mm = '202403'::text) AND (branch_cd = 'B001'::text))
Heap Fetches: 438
Buffers: shared hit=19054
Planning Time: 0.248 ms
Execution Time: 217.621 ms
Index Only Scan using sales_monthly_stats_pk ๋ผ๋ ์ค์ด ๋ณด์ธ๋ค. ์ธ๋ฑ์ค๋ฅผ ํ๊ธด ํ๋ค. ๊ทธ๋ฐ๋ฐ ๋ช ๊ฐ์ง ์ซ์๊ฐ ์ด์ํ๋ค.
438ํ์ ๋๋ ค์ฃผ๋๋ฐ Buffers๊ฐ 19,054
์์ ๋น์ฉ๋ cost=64613
PostgreSQL์ ํ buffer๋ 8KB์ด๋ค. 19,054 × 8KB ≈ 149MB. ๊ฒฐ๊ตญ 438ํ์ ์ํด ์ฝ 149MB ๋ถ๋์ ์ธ๋ฑ์ค ํ์ด์ง๋ฅผ ์ฝ์๋ค๋ ๋ป์ด๋ค. ํ ํ๋น ์ฝ 43๊ฐ buffer์ด๋ค. ๊ฒฐ๊ณผ ํ์์ ๋นํด ์๋์ ์ผ๋ก ๋ง๋ค. ์ธ๋ฑ์ค๋ฅผ ํ๊ณ ๋ ์๋๋ฐ, ์ ์์ ์ผ๋ก ํ์ฉ๋๊ณ ์์ง๋ ์๋ค๋ ์ ํธ์ด๋ค.
2. 2์ฐจ ๋ถ์ - ์ PK๊ฐ ์ ํตํ๋๊ฐ
์ฌ๊ธฐ์ ์ ์ ๋งํ๋ค. "์ธ๋ฑ์ค๋ฅผ ํ๋ค๊ณ ๋์ค๋๋ฐ ์ ํ์ค์บ์ฒ๋ผ ๋์ํ์ง?"
ํต์ฌ์ B-tree ๋ณตํฉ ์ธ๋ฑ์ค๊ฐ ์ ๋ ์ปฌ๋ผ๋ถํฐ ์ ๋ ฌ๋์ด ์๋ค๋ ์ ์ด๋ค. ์๊ณ ์๋ ์ฌ์ค์ด์ง๋ง, "PK ์์ ์ปฌ๋ผ์ด ์์ผ๋ฉด ๊ทธ๋๋ ํ์ฉ๋๋ค" ๋ผ๋ ์ง๊ด๊ณผ ์ถฉ๋ํ๋ฉด์ ์ ๊น ํท๊ฐ๋ ธ๋ค.
โ ๋ณตํฉ ์ธ๋ฑ์ค๋ ๋ค๋จ ์ ๋ ฌ๋ ์ฑ
๋ณตํฉ ์ธ๋ฑ์ค (์ฑ, ์ด๋ฆ, ์ค๊ฐ์ด๋ฆ) ์ด๋ผ๋ฉด ์ฑ ์ ์๋ ์์๋ก ์ ๋ ฌ๋์ด ์๋ค.
| ํ์ด์ง | ์ฑ | ์ด๋ฆ | ์ค๊ฐ์ด๋ฆ |
| 1 | ๊ฐ | ์ฌ๋ | - |
| 2 | ๊ฐ | ํด๋จผ | - |
| 3 | ๊น | ํผํ | - |
| 4 | ๊น | ๋์ | - |
| 5 | ๊น | ๋์ | ์ |
| ... | ... | ... | ... |
- "๊น ๋์ " ๊ฒ์ → ์ฑ → ์ด๋ฆ ์์ผ๋ก ๋ฐ๋ผ๊ฐ๋ฉด ํ ๋ฒ์ ๋๋ฌํ๋ค.
- "๋์ " (์ด๋ฆ)๋ง์ผ๋ก ๊ฒ์ → ์ฑ ์ ์ฒด๋ฅผ 1ํ์ด์ง๋ถํฐ ๋๊น์ง ํ์ด์ผ ํ๋ค.
- "๋์ ์" (์ด๋ฆ+์ค๊ฐ์ด๋ฆ)๋ง์ผ๋ก ๊ฒ์ → ๋ง์ฐฌ๊ฐ์ง๋ก ํ์ค์บ์ด๋ค.
์ ๋ ์ปฌ๋ผ์ด WHERE ์ ์ ์์ผ๋ฉด ์ธ๋ฑ์ค์ "์ ๋ ฌ ์ด์ "์ ์ธ ์ ์๋ค. ๋๋จธ์ง ์ปฌ๋ผ์ด ์ธ๋ฑ์ค์ ํฌํจ๋์ด ์๋๋ผ๋ ๊ฒฐ๊ตญ ์ฑ ์ ์ฒด๋ฅผ ํผ์ณ๋๊ณ ํํฐ๋งํ๋ ๊ฒ๊ณผ ๊ฐ๋ค.
โก ์ฐ๋ฆฌ ์ฟผ๋ฆฌ์ ๋์
PK ์ ๋: sales_id ← WHERE ์ ์ ์์
WHERE : branch_cd, year_mm ← PK์ 2,3๋ฒ์งธ ์ปฌ๋ผ
sales_id ์กฐ๊ฑด์ด ์์ผ๋ ์ตํฐ๋ง์ด์ ๋ ์ธ๋ฑ์ค ์ ์ฒด๋ฅผ ํ์ผ๋ฉด์ year_mm/branch_cd๋ก ํํฐ๋ง๋ง ํ๋ค. "Index Only Scan" ์ด๋ผ๋ ์ด๋ฆ์ด ๋ถ๊ธด ํ์ง๋ง, ์ค์์ ์ธ๋ฑ์ค ํ์ค์บ + ํํฐ์ด๋ค. ์ด๊ฒ๋๋ฌธ์ 19,054 buffers๊ฐ ๋ฐ์ํ ๊ฒ์ด์๋ฐ.
3. ํด๊ฒฐ ๋ฐฉ์
์ ๋ ์ปฌ๋ผ์ด branch_cd์ธ ๋ณด์กฐ ์ธ๋ฑ์ค๋ฅผ ์ถ๊ฐํ๋ค.
CREATE INDEX ix_sales_monthly_stats__branch_yearmm
ON analytics.sales_monthly_stats (branch_cd, year_mm);
ANALYZE analytics.sales_monthly_stats; -- ์ตํฐ๋ง์ด์ ํต๊ณ ๊ฐฑ์
์ด์ ํ๊ฒฝ์ด๋ผ๋ฉด ๋ฝ์ ์ค์ด๊ธฐ ์ํด ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ๋ค.
CREATE INDEX CONCURRENTLY ix_sales_monthly_stats__branch_yearmm
ON analytics.sales_monthly_stats (branch_cd, year_mm);
CONCURRENTLY ์ต์ ์ ํ ์ด๋ธ ์ฐ๊ธฐ๋ฅผ ๋ง์ง ์๊ณ ์ธ๋ฑ์ค๋ฅผ ์์ฑํ๋ค. ๋์ ์ผ๋ฐ ์์ฑ๋ณด๋ค ์๊ฐ์ด ๋ ๊ฑธ๋ฆฌ๊ณ , ์ค๊ฐ์ ์คํจํ๋ฉด INVALID ์ํ์ ์ธ๋ฑ์ค๊ฐ ๋จ์ ์ ์์ด ํ์ฒ๋ฆฌ๊ฐ ํ์ํ๋ค.
โ Before / After ๋น๊ต
๋ค์ EXPLAIN์ ๋ ๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
Index Scan using ix_sales_monthly_stats__branch_yearmm on sales_monthly_stats
(cost=0.43..2.65 rows=1 width=225)
Index Cond: (((branch_cd)::text = 'B001'::text)
AND ((year_mm)::text = '202403'::text))
Execution Time: 1.4 ms
Index Only Scan using sales_monthly_stats_pk (cost 64,613) ์์ Index Scan using ix_sales_monthly_stats__branch_yearmm (cost 2.65) ๋ก ๋ฐ๋์๋ค. ์ตํฐ๋ง์ด์ ๊ฐ ์ ์ธ๋ฑ์ค๋ฅผ ๊ณจ๋๋ค๋ ๋ป์ด๋ค.
์งํ Before (PK๋ง) After (์ ๊ท ์ธ๋ฑ์ค)
| Plan | Index Only Scan using PK | Index Scan using ์ ๊ท ์ธ๋ฑ์ค |
| Cost | 64,613 | 2.65 |
| Buffers | 19,054 | 23 |
| Execution Time | 217 ms | 1.4 ms |
์๋ต์๊ฐ์ ์ฝ 150๋ฐฐ, ๋ฉ๋ชจ๋ฆฌ IO๋ 800๋ฐฐ ๊ฐ๊น์ด ์ค์๋ค. cost ์ถ์ ์น๋ ์ฝ 24,000๋ฐฐ ์ฐจ์ด๊ฐ ๋๋ค.
โก PK๋ ๊ทธ๋๋ก ๋์ด๋ ๋๋๊ฐ
๋๋ค. ๋์ ์ญํ ์ด ๋ค๋ฅด๋ค.
| ์ธ๋ฑ์ค | ์ ๋์ปฌ๋ผ | ์ฐ์์ |
| PK (sales_id, year_mm, branch_cd) | sales_id | WHERE sales_id = ? ๋จ๊ฑด ์กฐํ/UPDATE/DELETE |
| ์ ๊ท (branch_cd, year_mm) | branch_cd | WHERE branch_cd = ? AND year_mm = ? ๋ชฉ๋ก ์กฐํ |
PK๋ฅผ ๋ผ๋ ๊ฒ ์๋๋ผ ์ถ๊ฐ๋ง ํ๋ฏ๋ก ๊ธฐ์กด ๋์์๋ ์ํฅ์ด ์๋ค. ๋์ ์ฐ๊ธฐ ๋น์ฉ์ ์ฝ๊ฐ ๋์ด๋๋ค. INSERT/UPDATE ์ ๊ฐฑ์ ํด์ผ ํ ์ธ๋ฑ์ค๊ฐ ํ๋ ๋ ์๊ธฐ๊ธฐ ๋๋ฌธ์ด๋ค. 2์ปฌ๋ผ์ง๋ฆฌ ์ผ๋ฐ B-tree ์ธ๋ฑ์ค๋ผ ๋น์ฉ์ ๋ฏธ๋ฏธํ์ง๋ง, ์ฐ๊ธฐ๊ฐ ๋งค์ฐ ์ฆ์ ํ ์ด๋ธ์ด๋ผ๋ฉด ์ธก์ ํด๋ณด๊ณ ๊ฒฐ์ ํ๋ ๊ฒ์ด ์์ ํ๋ค.
4. ๋ฌธ์ ์ ๋ฆฌ
โ "PK๊ฐ ์์ผ๋๊น ๋น ๋ฅผ ๊ฒ"์ด๋ผ๋ ๊ฐ์ ์ผ๋ก ์๊ฐ์ ๋ญ๋นํ๋ค
PK๋ ๋จ์ํ "์๋ค/์๋ค" ์ ๋ฌธ์ ๊ฐ ์๋๋ผ ์ ๋ ์ปฌ๋ผ์ด WHERE ์กฐ๊ฑด๊ณผ ๋ง๋๋ ์ ๋ฌธ์ ์ด๋ค. PK ์์ ๋ค์ด์๋ ์ปฌ๋ผ์ด๋ผ๋ ๊ทธ๊ฒ์ด ์ธ๋ฑ์ค์ ์ฒซ ๋ฒ์งธ ์ปฌ๋ผ์ด ์๋๋ผ๋ฉด ์ ๋ ฌ ์ด์ ์ ํ์ฉํ ์ ์๋ค.
โก EXPLAIN์ Index Only Scan ์ด ๋ด๋ค๊ณ ์์ฌํ๋ค
๊ณํ ์ข ๋ฅ๋ง ๋ณด์ง ๋ง๊ณ Buffers, Heap Fetches, ๊ฒฐ๊ณผ ํ์๋ฅผ ๊ฐ์ด ๋ด์ผ ํ๋ค. ๊ฒฐ๊ณผ ํ์์ ๋นํด Buffers๊ฐ ๋น์ ์์ ์ผ๋ก ํฌ๋ฉด ์ธ๋ฑ์ค๊ฐ ์ ๋ ฌ ์ด์ ์์ด ์ฐ์ด๊ณ ์๋ค๋ ์ ํธ์ด๋ค. ํ๋น 10 buffer ์ด์ ์ฝ๊ณ ์๋ค๋ฉด ํ ๋ฒ ์์ฌํด๋ณผ ๋ง ํ๋ค.
โข ์ปฌ๋ผ ์์๋ฅผ ๊น์ด ์๊ฐํ์ง ์์๋ค
๋ณตํฉ ์ธ๋ฑ์ค๋ฅผ ์๋ก ๋ง๋ค ๋ "์ด๋ค ์ปฌ๋ผ์ ํฌํจ์ํฌ๊น" ๋งํผ "์ด๋ค ์์๋ก ์ ๋ ฌํ ๊น" ๊ฐ ์ค์ํ๋ค. ๊ธฐ์ค์ ๋จ์ํ๋ค. ์์ฃผ ์ฐ๋ WHERE ์กฐ๊ฑด์ ์ปฌ๋ผ์ ์ ๋๋ก ๋๋ค. ๋ฑํธ ์กฐ๊ฑด๊ณผ ๋ฒ์ ์กฐ๊ฑด์ด ์์ฌ์๋ค๋ฉด ๋ฑํธ ์กฐ๊ฑด์ ๋จผ์ ๋๋ค.
5. ๊ฒฐ๋ก ์ ๋ฆฌ
๋ณตํฉ ์ธ๋ฑ์ค๋ ์ ๋ ฌ๋ ์ฑ ์ด๋ค. ์ ๋ ์ปฌ๋ผ์ด WHERE ์ ์ ์์ผ๋ฉด ์ฑ ์ ์ฒด๋ฅผ ํผ์น๋ค. "Index Only Scan" ์ด๋ผ๋ ๋จ์ด์ ์์ฌํ์ง ๋ง๊ณ , Buffers ÷ ๊ฒฐ๊ณผ ํ์๋ฅผ ํ๋ฒ ๊ณ์ฐํด๋ณด๋ฉด ์ธ๋ฑ์ค๊ฐ ์ ๋๋ก ๋์ํ๋์ง ๋น ๋ฅด๊ฒ ํ๋จํ ์ ์๋ค.
ํด๊ฒฐ์ฑ ์์ฒด๋ ๋จ์ํ๋ค. WHERE ์กฐ๊ฑด์ ๋ง๋ ์ปฌ๋ผ ์์๋ก ๋ณด์กฐ ์ธ๋ฑ์ค๋ฅผ ์ถ๊ฐํ๋ฉด ๋๋ค. PK์ ์ถฉ๋ํ์ง๋ ์๋๋ค. PK๋ ๋จ๊ฑด ์๋ณ, ๋ณด์กฐ ์ธ๋ฑ์ค๋ ๋ชฉ๋ก ์กฐํ๋ก ์ญํ ์ด ๋ค๋ฅด๋ค. ์์ ํ ์ด๋ธ์์๋ ์ ๋๋ฌ๋์ง ์๋ค๊ฐ ๋ฐ์ดํฐ๊ฐ ์์ด๋ฉด ์ด๋ ์๊ฐ ํฐ์ง๋ ์ข ๋ฅ์ ๋ฌธ์ ์ด๊ธฐ ๋๋ฌธ์, ํ ๋ฒ ๊ฒช๊ณ ๋๋ฉด ๋ค์๋ถํฐ๋ ์์ฌ 1์์์ ๋ ์ ์๋ค.
๊ฒฐ๊ตญ ์ธ๋ฑ์ค๋ฅผ ๋๋ฆฌ๋ฉด ์ฝ๊ธฐ๋ ๋นจ๋ผ์ง์ง๋ง ์ฐ๊ธฐ ๋น์ฉ์ ๋์ด๋๋ค. ๋ชจ๋ ์กฐํฉ์ ์ธ๋ฑ์ค๋ฅผ ๊ฑฐ๋ ๊ฒ์ด ์ ๋ต์ ์๋๊ณ , ์ค์ ์ฟผ๋ฆฌ ํจํด๊ณผ ํ ์ด๋ธ์ ์ฝ๊ธฐ/์ฐ๊ธฐ ๋น์จ์ ๊ณ ๋ คํด ํ์ํ ์ธ๋ฑ์ค๋ง ์ ๋ณํด ์ถ๊ฐํ๋ ๊ฒ์ด ํต์ฌ์ด๋ค.
'๐ CS > Database' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| ํธ๋์ญ์ (Transaction)์ด๋? (0) | 2023.04.04 |
|---|
๋ธ๋ก๊ทธ์ ์ ๋ณด
Study Repository
Dongwoongkim