Innovating today, leading tomorrow

OpenSQL_Technical Guide
[OpenSQL] Explain 사용법 (1) – 개요

[OpenSQL] Explain 사용법 (1) – 개요

Explain 이란?

Explain은 PostgreSQL의 쿼리 최적화 도구로, 지정한 SQL 쿼리에 대해 PostgreSQL이 생성하는 실행 계획(Execution Plan)을 확인할 수 있게 해줍니다.

실행 계획은 원하는 자료를 출력하기 위해서, 어떤 테이블이 테이블 전체 순차 검색을 하는지, 인덱스 검색을 하는지를 보여줍니다.

또한 여러 테이블이 조인이 될 경우 그 각 테이블들의 조인 알고리즘은 어떤 것을 사용할 것인지를 보여줍니다.

Explain 시놉시스

EXPLAIN [ ( option [, ...] ) ] statement
EXPLAIN [ ANALYZE ] [ VERBOSE ] statement

where option can be one of:

    ANALYZE [ boolean ]
    VERBOSE [ boolean ]
    COSTS [ boolean ]
    SETTINGS [ boolean ]
    BUFFERS [ boolean ]
    WAL [ boolean ]
    TIMING [ boolean ]
    SUMMARY [ boolean ]
    FORMAT { TEXT | XML | JSON | YAML }

옵션으로는 ANALYZE, VERBOSE, COSTS, SETTINGS, BUFFERS, WAL, TIMING, SUMMARY, FORMAT 등이 있으며, 각 옵션은 더 구체적인 정보를 제공합니다.

테스트 테이블 사이즈 및 인덱스:

# 10,000,000 테이블 (1천만 테이블)

pgbench -h localhost -p 5432 -U postgres -d test -i -s 100
dropping old tables...
creating tables...
generating data (client-side)...
10000000 of 10000000 tuples (100%) done (elapsed 85.17 s, remaining 0.00 s)
vacuuming...
creating primary keys...
done in 133.24 s (drop tables 0.06 s, create tables 0.04 s, client-side generate 87.92 s, vacuum 9.52 s, primary keys 35.72 s).

[opensql@postgresql:~]$ psql
psql (16.4)
Type "help" for help.

postgres=# l+
                                                                                         List of databases
   Name    |  Owner   | Encoding | Locale Provider |   Collate   |    Ctype    | ICU Locale | ICU Rules |   Access privileges   |  Size   | Tablespace |                Description
-----------+----------+----------+-----------------+-------------+-------------+------------+-----------+-----------------------+---------+------------+--------------------------------------------
 postgres  | postgres | UTF8     | libc            | en_US.UTF-8 | en_US.UTF-8 |            |           |                       | 7556 kB | pg_default | default administrative connection database
 template0 | postgres | UTF8     | libc            | en_US.UTF-8 | en_US.UTF-8 |            |           | =c/postgres          +| 7345 kB | pg_default | unmodifiable empty database
           |          |          |                 |             |             |            |           | postgres=CTc/postgres |         |            |
 template1 | postgres | UTF8     | libc            | en_US.UTF-8 | en_US.UTF-8 |            |           | =c/postgres          +| 7572 kB | pg_default | default template for new databases
           |          |          |                 |             |             |            |           | postgres=CTc/postgres |         |            |
 test      | postgres | UTF8     | libc            | en_US.UTF-8 | en_US.UTF-8 |            |           |                       | 1503 MB | pg_default |
(4 rows)


test=# dt+
                                         List of relations
 Schema |       Name       | Type  |  Owner   | Persistence | Access method |  Size   | Description
--------+------------------+-------+----------+-------------+---------------+---------+-------------
 public | pgbench_accounts | table | postgres | permanent   | heap          | 1281 MB |
 public | pgbench_branches | table | postgres | permanent   | heap          | 40 kB   |
 public | pgbench_history  | table | postgres | permanent   | heap          | 0 bytes |
 public | pgbench_tellers  | table | postgres | permanent   | heap          | 80 kB   |
(4 rows)


test=# d+ pgbench_accounts
                                         Table "public.pgbench_accounts"
  Column  |     Type      | Collation | Nullable | Default | Storage  | Compression | Stats target | Description
----------+---------------+-----------+----------+---------+----------+-------------+--------------+-------------
 aid      | integer       |           | not null |         | plain    |             |              |
 bid      | integer       |           |          |         | plain    |             |              |
 abalance | integer       |           |          |         | plain    |             |              |
 filler   | character(84) |           |          |         | extended |             |              |
Indexes:
    "pgbench_accounts_pkey" PRIMARY KEY, btree (aid)
Access method: heap
Options: fillfactor=100




test db 사이즈 = 1.5GB
pgbench_accounts 테이블 사이즈 = 1.281GB
pgbench_accounts 테이블 인덱스 = pgbench_accounts_pkey

pgbench_accounts 테이블 구성:

테스트 Admin Tool : pgAdmin 4

개요 및 기본 기능 : https://tmaxtibero.blog/5355/

옵션

  • ANALYZE

커멘드를 실행하고 실제 실행 시간과 기타 통계를 표시합니다. 디폴트 값은 FALSE 입니다.

  • VERBOSE

계획에 관한 추가 정보를 표시합니다. 구체적으로, 계획 트리의 각 노드에 대한 출력 열 목록, 스키마 테이블, 함수명을 포함하며, 표현식의 변수에 항상 범위 테이블 별칭을 레이블하고, 통계가 표시되는 각 트리거의 이름을 항상 출력합니다. 쿼리 식별자가 계산된 경우에도 표시됩니다. 디폴트 값은 FALSE 입니다.

  • COSTS

각 계획 노드의 예상 시작 및 총 비용, 예상 행 수 및 각 행의 예상 너비에 대한 정보를 포함합니다. 디폴트 값은 TRUE 입니다.

  • SETTINGS

구성 매개변수에 대한 정보를 포함합니다. 구체적으로, 내장된 기본값과 다른 값으로 쿼리 계획에 영향을 미치는 옵션을 포함합니다. 디폴트 값은 FALSE 입니다.

  • BUFFERS

버퍼 사용에 대한 정보를 포함합니다. 구체적으로, 공유 블록, 로컬 블록, 임시 블록의 HIT, 읽기, 쓰기, 변경 내용을 포함합니다. 결과에 나오는 항목 중 dirted 블럭은 해당쿼리로 변경된 블록의 수를, written 블럭은 해당 쿼리가 실행되며 세션이 공유버퍼에서 변경된 블록을 디스크에 기록한 수를 출력합니다. 텍스트 형식에서는 0이 아닌 값만 출력됩니다. 디폴트 값은 FALSE 입니다.

  • WAL

WAL 레코드 생성에 대한 정보를 포함합니다. 구체적으로, 레코드 수, 전체 페이지 이미지 수(fpi) 및 생성된 WAL 양(바이트)을 포함합니다. 텍스트 형식에서는 0이 아닌 값만 출력됩니다. 이 매개변수는  ANALYZE 가 활성화된 경우에만 사용할 수 있습니다. 디폴트 값은 FALSE 입니다.

  • TIMING

실제 각 노드별 소모 시간을 보여줍니다. 반복적으로 시스템 시간을 확인하는데서 오는 오버헤드는 쿼리의 성능을 굉장히 저하시킬 수 있기 때문에 정확한 시간이 필요한 것이 아니라면 FALSE로 두는 것이 좋습니다. 다만 FALSE 상태로 두더라도 해당 쿼리의 총 실행시간은 항상 계산됩니다. 이 매개변수는  ANALYZE 가 활성화된 경우에만 사용할 수 있습니다. 디폴트 값은 TRUE 입니다.

  • SUMMARY

쿼리 계획 뒤에 요약 정보(예: 총 TIMING 정보)를 포함합니다. 이 매개변수는 기본적으로 ANALYZE사용될 때 포함되지만, 그렇지 않으면 이 옵션을 사용하여 활성화할 수 있습니다. 계획 시간에는 EXPLAIN EXECUTE캐시에서 계획을 가져오는 데 필요한 시간이 포함되며, 재계획에 필요한 시간이 포함되는 경우도 있습니다.

  • FORMAT

실행계획 값을 TEXT, XML, JSON, YAML의 타입으로 출력합니다. 내용은 모두 동일하며, 기본 출력값은 TEXT 형식입니다. 다만 pgAdmin에서는 FORMAT GUI 가 없기에, 아래와 같이 명령어를 입력해야 합니다.

**EXPLAIN (FORMAT 타입) 쿼리문**

e.g.
EXPLAIN (FORMAT JSON) SELECT * FROM PGBENCH_ACCOUNTS;

JSON 타입 출력예시:

XML 타입 출력예시:

YAML 타입 출력예시:

EXPLAIN 항목별 의미

1. EXPLAIN 출력시 항목

실행계획을 위해 가장 기본적으로 사용하는 구문은 EXPLAIN 입니다. Admin 툴 사용시 GUI 에 Explain 항목이 따로 있습니다.

(pgAdmin4 기준)

pgAdmin 출력예시:

리눅스 출력예시:

test=# EXPLAIN SELECT * FROM PGBENCH_ACCOUNTS;
                                 QUERY PLAN
-----------------------------------------------------------------------------
 Seq Scan on pgbench_accounts  (cost=0.00..263935.00 rows=10000000 width=97)
(1 row)

테스트 테이블의 1천만개의 행을 조건 없이 조회했을 때의 플랜 조회입니다. 기본적으로 cost, rows, width 가 출력됩니다.

  • COST

cost는 예상비용입니다. cost의 계산식은 아래와 같습니다.

(읽은 디스크 페이지 * seq_page_cost ) + (스캔된 행 * cpu_tuple_cost )

# seq_page_cost 와 cpu_tuple_cost 의 기본값은 각각 1.0과 0.01 입니다. 

--읽은 디스크 페이지:

test=# SELECT relpages FROM pg_class WHERE relname = 'pgbench_accounts';
 relpages
----------
   163935
(1 row)

-- 스캔된 행 (전체 행)

test=# SELECT COUNT(*) FROM pgbench_accounts;
  count
----------
 10000000
(1 row)

(163935 * 1) + (10000000 * 0.01) = 263935
cost = 263935
  • ROWS

rows는 스캔된 행의 갯수입니다. 특별한 경우가 없는 한, 전체 행의 갯수가 출력됩니다.

test=# SELECT COUNT(*) FROM pgbench_accounts;
  count
----------
 10000000
(1 row)

row = 10000000
  • WIDTH

width는 컬럼별 평균 byte 수입니다. 아래의 명령어로 확인할 수 있습니다.

SELECT * FROM pg_stats WHERE tablename = 'pgbench_accounts';

# 너무 가독성이 떨어지기에 아래와 같이 조건을 걸어 출력합니다.

SELECT
    attname AS "Column Name",
    n_distinct AS "Distinct Values",
    avg_width AS "Average Width",
    substring(most_common_vals::text, 1, 50) || '...' AS "Most Common Values",
    substring(most_common_freqs::text, 1, 50) || '...' AS "Most Common Frequencies",
    correlation AS "Correlation"
FROM
    pg_stats
WHERE
    tablename = 'pgbench_accounts';
    
 Column Name | Distinct Values | Average Width |                  Most Common Values                   |                Most Common Frequencies                | Correlation
-------------+-----------------+---------------+-------------------------------------------------------+-------------------------------------------------------+-------------
 aid         |              -1 |             4 |                                                       |                                                       |           1
 bid         |             100 |             4 | {21,7,76,29,79,93,70,69,16,97,10,94,49,28,62,15,41... | {0.011433333,0.011266666,0.011266666,0.0112333335,... |           1
 abalance    |               1 |             4 | {0}...                                                | {1}...                                                |           1
 filler      |               1 |            85 | {"                                                ... | {1}...                                                |           1
(4 rows)

width = sum(avg_width)
4 + 4 + 4 + 85 = 97

width = 97

2. EXPLAIN ANALYZE 출력시 항목

ANALYZE 옵션을 넣고 조회했을 때의 출력값입니다. pgAdmin4 기준 GUI 에 Explain Analyze 항목이 따로 있습니다. pgAdmin4 에서는 visualize 기능을 지원하고 있습니다. pgAdmin4에서 실행계획은 아래와 같이 출력됩니다.

pgAdmin 출력예시:

리눅스 EXPLAIN ANALYZE 출력예시:

test=# explain analyze select count(*) from pgbench_accounts;
                                                                                         QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Finalize Aggregate  (cost=133248.18..133248.19 rows=1 width=8) (actual time=1590.177..1594.604 rows=1 loops=1)
   ->  Gather  (cost=133247.97..133248.18 rows=2 width=8) (actual time=1589.966..1594.595 rows=3 loops=1)
         Workers Planned: 2
         Workers Launched: 2
         ->  Partial Aggregate  (cost=132247.97..132247.98 rows=1 width=8) (actual time=1585.179..1585.180 rows=1 loops=3)
               ->  Parallel Index Only Scan using pgbench_accounts_pkey on pgbench_accounts  (cost=0.43..121831.30 rows=4166667 width=0) (actual time=0.055..1084.067 rows=3333333 loops=3)
                     Heap Fetches: 0
 Planning Time: 0.163 ms
 Execution Time: 1594.655 ms
(9 rows)

ANALYZE 옵션을 넣고 조회했을 때의 출력값입니다. 기본적으로 COST, ROWS, WIDTH 에 더해 ACTUAL TIME, LOOPS, PLANNING TIME, EXECUTION TIME 이 출력됩니다.

  • ACTUAL TIME

실제 쿼리를 수행하는데 소모된 시간을 보여주며 [값 1.. 값 2] 형태로 표현됩니다. 앞부분(값 1)은 작업 결과로 첫 번째 row를 리턴하기 전까지 비용이고, 뒷부분(값 2)은 마지막 row를 리턴할 때까지의 비용입니다.

  • LOOPS

row가 조회되는 횟수를 나타냅니다.

  • PLANNING TIME

쿼리 플랜을 생성하고 최적화하는 데 걸린 시간을 나타냅니다. 여기에는 파싱이나 재작성한 시간은 포함되지 않습니다.

  • EXECUTION TIME

실제 쿼리를 실행하는 데 걸린 시간입니다. 시작 및 종료 시간과 실행된 모든 트리거를 실행하는 시간이 포함되지만 구문 분석, 재작성 또는 계획 시간은 포함되지 않습니다.

실행계획 분석

리눅스 EXPLAIN ANALYZE 출력예시:

test=# explain analyze select count(*) from pgbench_accounts;

                                                                                         QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Finalize Aggregate  (cost=133248.18..133248.19 rows=1 width=8) (actual time=1590.177..1594.604 rows=1 loops=1)
   ->  Gather  (cost=133247.97..133248.18 rows=2 width=8) (actual time=1589.966..1594.595 rows=3 loops=1)
         Workers Planned: 2
         Workers Launched: 2
         ->  Partial Aggregate  (cost=132247.97..132247.98 rows=1 width=8) (actual time=1585.179..1585.180 rows=1 loops=3)
               ->  Parallel Index Only Scan using pgbench_accounts_pkey on pgbench_accounts  (cost=0.43..121831.30 rows=4166667 width=0) (actual time=0.055..1084.067 rows=3333333 loops=3)
                     Heap Fetches: 0
 Planning Time: 0.163 ms
 Execution Time: 1594.655 ms
(9 rows)

쿼리 플랜에 순서는 하단에서 상단으로 읽습니다. 위 예시의 경우, Parallel Index Only Scan → Partial Aggregate → Gather → Finalize Aggregate 순으로 읽습니다. pgAdmin4의 실행계획을 참조한다면 쿼리플랜을 한눈에 볼 수 있습니다.

  1. Parallel Index Only Scan
Parallel Index Only Scan using pgbench_accounts_pkey on pgbench_accounts
  (cost=0.43..121831.30 rows=4166667 width=0) (actual time=0.055..1084.067 rows=3333333 loops=3)
  Heap Fetches: 0
  • 기능:
    • pgbench_accounts 테이블의 기본 키(primary key) 인덱스를 사용해 데이터를 스캔(읽기)합니다.
    • 데이터를 읽을 때 테이블(Heap)을 직접 접근하지 않고 인덱스만 사용하므로 더 빠릅니다.
  • 정보:
    • Heap Fetches: 0. 테이블 데이터(Heap)에 접근하지 않았음을 의미합니다.
    • 병렬 작업자당 처리된 행 수(rows): 한 작업자가 약 3333333개의 행을 처리.전체 행 수는 약 3333333 × 3 = 1000만 개입니다.
    • 실제 실행 시간(actual time): 각 작업자가 0.055ms에 시작해 약 1084ms 동안 작업했습니다.

2. Partial Aggregate

Partial Aggregate  (cost=132247.97..132247.98 rows=1 width=8) (actual time=1585.179..1585.180 rows=1 loops=3)
  • 기능:
    • 각 작업자(worker)가 담당하는 데이터의 부분 합계를 계산합니다.
  • 정보:
    • 실제 실행 시간(actual time): 각 작업자가 약 1585ms 동안 부분 합계를 계산을 했습니다.
    • 루프 수(loops): 3번 실행된 것은 3개의 병렬 작업자가 데이터를 처리했기 때문입니다.

3. Gather

Gather  (cost=133247.97..133248.18 rows=2 width=8) (actual time=1589.966..1594.595 rows=3 loops=1)
	 Workers Planned: 2
	 Workers Launched: 2
  • 기능:
    • 여러 병렬 작업자(worker)가 각각 계산한 부분 결과를 모아서 전달합니다.
  • 정보:
    • Workers Planned: 2개의 작업자(worker)를 계획.
    • Workers Launched: 실제로 2개의 작업자가 시작되었습니다.
    • 결과 행 수(rows): 각 작업자가 부분 합계를 1개씩 계산해서 총 3개의 행을 반환했습니다.
    • 루프 수(loops): 이 작업도 1번 실행되었습니다.

4. Finalize Aggregate

Finalize Aggregate  (cost=133248.18..133248.19 rows=1 width=8) (actual time=1590.177..1594.604 rows=1 loops=1)
  • 기능:
    • 최종적으로 여러 작업자(worker)들이 계산한 부분 결과(partial result)를 모아서 전체 합계를 계산합니다.
  • 정보:
    • 비용(cost): 133248.18에서 133248.19. 이 값은 PostgreSQL이 예상한 작업 비용입니다.
    • 실제 실행 시간(actual time): 작업이 시작된 시간(1590.177ms)과 끝난 시간(1594.604ms).
    • 결과 행 수(rows): 최종 결과로 1개의 행(카운트 값)이 생성되었습니다.
    • 루프 수(loops): 이 작업은 1번 실행되었습니다.

실행계획 정리

  1. 병렬 처리:
    • 쿼리는 병렬 처리(Parallel Execution)를 사용해 데이터 스캔 속도를 높였습니다.
    • 2개의 작업자가 기본 키 인덱스를 사용해 데이터를 스캔했습니다.
  2. 데이터 합산 과정:
    • 각 작업자가 자기 데이터를 스캔하고 부분 합계(Partial Aggregate)를 계산했습니다.
    • Gather 단계에서 작업자들이 계산한 부분 합계를 모았습니다.
    • Finalize Aggregate에서 최종 합계를 계산했습니다.
  3. 실행 시간:
    • 데이터 스캔: 약 1084ms.
    • 부분 합산과 결과 처리: 약 1594ms.
    • 최종적으로 약 1594.655ms(1.59초)가 걸렸습니다.

광고성 정보 수신

개인정보 수집, 활용 목적 및 기간

(주)티맥스티베로의 개인정보 수집 및 이용 목적은 다음과 같습니다.
내용을 자세히 읽어보신 후 동의 여부를 결정해 주시기 바랍니다.

  • 수집 목적: 티맥스티베로 뉴스레터 발송 및 고객 관리
  • 수집 항목: 성함, 회사명, 회사 이메일, 연락처, 부서명, 직급, 산업, 담당업무, 관계사 여부, 방문 경로
  • 보유 및 이용 기간: 동의 철회 시까지

※ 위 개인정보 수집 및 이용에 대한 동의를 거부할 권리가 있습니다.
※ 필수 수집 항목에 대한 동의를 거부하는 경우 뉴스레터 구독이 제한될 수 있습니다.

개인정보의 처리 위탁 정보
  • 업체명: 스티비 주식회사
  • 위탁 업무 목적 및 범위: 광고가 포함된 뉴스레터 발송 및 수신자 관리
 

개인정보 수집 및 이용

개인정보 수집, 활용 목적 및 기간

(주)티맥스티베로의 개인정보 수집 및 이용 목적은 다음과 같습니다. 내용을 자세히 읽어보신 후 동의 여부를 결정해 주시기 바랍니다.

  • 수집 목적: 티맥스티베로 뉴스레터 발송 및 고객 관리
  • 수집 항목: 성함, 회사명, 회사 이메일, 연락처, 부서명, 직급, 산업, 담당업무, 관계사 여부, 방문 경로
  • 보유 및 이용 기간: 동의 철회 시까지

※ 위 개인정보 수집 및 이용에 대한 동의를 거부할 권리가 있습니다.
※ 필수 수집 항목에 대한 동의를 거부하는 경우 뉴스레터 구독이 제한될 수 있습니다.

개인정보의 처리 위탁 정보

  • 업체명: 스티비 주식회사
  • 위탁 업무 목적 및 범위: 광고가 포함된 뉴스레터 발송 및 수신자 관리
  •