포스트

PostgreSQL 실행계획(EXPLAIN) 노드·필드 사전 — 무엇이 보이면 무슨 뜻인가

EXPLAIN 출력의 스캔·조인·집계·정렬 노드와 Buffers·Heap Fetches·Sort Method 같은 계측 라인을 빠르게 찾아보는 PostgreSQL 실전 레퍼런스.

PostgreSQL 실행계획(EXPLAIN) 노드·필드 사전 — 무엇이 보이면 무슨 뜻인가

이 글은 원리 설명이 아니라 PostgreSQL EXPLAIN 노드·필드를 빠르게 찾는 실전 사전이다. 왜 특정 Plan이 선택되는지에 대한 일반 원리는 정보관리기술사 Knowledge의 쿼리-옵티마이저-통계-카디널리티-실행계획.md, JOIN 내부 원리는 질의최적화와-JOIN-실행전략.md, Index 구조는 BTree-클러스터드-넌클러스터드-인덱스.md에서 관리한다.

여기서는 PostgreSQL Plan을 실제로 볼 때 무슨 노드인지, 무엇을 같이 확인해야 하는지에 집중한다.

1. 스캔 노드

노드의미확인할 것
Seq ScanTable 전체를 순차 Scan큰 Table에서 Rows Removed by Filter가 큰가
Index ScanIndex로 위치를 찾고 Heap Row 접근Index Cond가 실제 검색 조건인가
Index Only Scan필요한 값을 Index에서 해결Heap Fetches가 충분히 작은가
Bitmap Index Scan조건에 맞는 Heap Page 위치 수집Bitmap Heap Scan과 함께 봄
Bitmap Heap ScanBitmap이 가리키는 Page 접근Recheck Cond, Lossy 여부
CTE ScanMaterialized CTE 결과 ScanCTE 재사용·Materialization 비용
Foreign ScanFDW 등 외부 Data 접근Filter/Join Pushdown 여부

Index Only Scan이라는 이름만 보고 Heap Access가 없다고 단정하지 않는다. Visibility Map 상태에 따라 Heap Fetches가 발생할 수 있다.

2. 조인·결합 노드

노드의미확인할 것
Nested LoopOuter Row마다 Inner 반복 LookupInner loops, Index Lookup 비용
Hash JoinBuild Side를 Hash로 만들고 ProbeHash Size, Batches, Disk Spill
Merge Join정렬된 입력을 함께 순회하위 Sort 비용
Materialize하위 결과를 저장해 반복 Scan 완화Memory/Temp 사용
MemoizeParameter 값별 Inner 결과 CacheHits, Misses, Evictions
Gather병렬 Worker 결과 취합Planned vs Launched Worker
Gather Merge정렬 순서를 유지하며 병렬 결과 취합정렬 비용 포함 여부
Append여러 하위 Plan 결과 연결Partition Pruning 여부

Nested Loop, Hash, Merge는 같은 Logical JOIN을 실행하는 서로 다른 물리 전략이다. 알고리즘 이름보다 현재 Row 수·Index·Sort·Memory 조건에 맞는 선택인지를 본다.

3. 집계·정렬 노드

노드의미확인할 것
Aggregate전체 집계입력 Row 수
HashAggregateHash 기반 GroupingDisk Usage, Memory
GroupAggregate정렬된 입력 Grouping하위 Sort 필요 여부
WindowAggWindow Function 처리Partition/Order Sort 비용
Sort전체 정렬Sort Method, Disk 사용
Incremental Sort이미 정렬된 Prefix를 활용Presorted Key
Limit상위 N Row만 통과하위 Plan이 조기 종료되는가
ModifyTableINSERT/UPDATE/DELETE/MERGETrigger·Partition Routing
LockRowsFOR UPDATE/SHARE Row LockLock 경합

4. 자주 보는 계측 필드

EXPLAIN (ANALYZE, BUFFERS)에서 특히 많이 보는 필드다.

필드의미위험 신호
actual time실제 실행시간특정 Node가 시간 대부분 차지
rows1 Loop당 실제 Row 수예상 Row와 큰 차이
loopsNode 실행 횟수Nested Loop Inner에서 매우 큼
Rows Removed by FilterScan 후 Filter에서 폐기통과 Row보다 압도적으로 큼
Rows Removed by Join FilterJoin 후 폐기Join Key/조건 재검토
Heap FetchesIndex Only Scan 중 Heap 방문크면 Index Only 이점 감소
Buffers: shared hitShared Buffer 적중정상적인 Cache Hit
Buffers: shared readStorage에서 읽은 Block크면 I/O 비용 확인
Buffers: temp read/writtenTemp File I/OSort/Hash Spill 가능성
Sort Method: external mergeDisk 기반 Sortwork_mem 부족 후보
Workers Planned/Launched계획/실제 병렬 WorkerLaunched가 더 적음
Planning TimePlan 생성 시간짧은 Query 대비 과도한가
Execution Time전체 실행시간기준 지표

표시되는 actual time과 rows는 loops가 여러 번이면 Loop당 평균값이라는 점을 주의한다.

1
actual rows=1 loops=1,000,000

이면 실제로는 1 Row Lookup이 백만 번 일어난 것이다.

5. 위험 신호를 문제 유형으로 연결한다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Estimated Rows ≠ Actual Rows
→ 통계·Cardinality 추정 문제

Nested Loop Inner loops 폭증
→ Outer Row 추정 / Inner Access Path 확인

Hash Batches 증가 / temp written
→ Memory 부족·Spill 확인

external merge Disk
→ Sort가 Memory를 넘음

Rows Removed by Filter 폭증
→ Access Path가 너무 많은 Row를 먼저 읽음

Heap Fetches 폭증
→ Index Only Scan이 Heap을 자주 방문

Append 하위 Plan이 너무 많음
→ Partition Pruning 확인

6. 읽는 순서

Plan을 볼 때는 Node 이름을 전부 외우는 것보다 다음 순서를 반복하는 게 실용적이다.

1
2
3
4
5
6
7
8
1. 실제 실행시간이 큰 Node 찾기
2. Estimated Rows와 Actual Rows 비교
3. loops 확인
4. Scan / Join Access Path 확인
5. Sort·Hash Spill 확인
6. Buffers로 I/O 확인
7. 원인에 맞게 Index·통계·Query·Memory 설정 조정
8. 다시 EXPLAIN ANALYZE로 측정

정리

이 문서는 PostgreSQL 실행계획의 제품별 실전 Reference다.

일반 원리는 기술사 Knowledge에서 관리하고, 여기서는 실제 Plan을 만났을 때 빠르게 다음을 찾는다.

1
2
3
4
5
6
무슨 Node인가?
→ 얼마나 실행됐나?
→ 예상과 실제 Row가 맞나?
→ Memory / Disk Spill이 있나?
→ Filter 후 버린 Row가 많은가?
→ I/O가 병목인가?

이렇게 보면 EXPLAIN을 Node 이름 암기가 아니라 실제 병목을 좁혀가는 진단 도구로 사용할 수 있다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.