PostgreSQL은 Oracle과는 달리 SQL Hint 기능이 기본으로 제공되지 않기 때문에, 별도의 pg_hint_plan Extension을 설치하여 사용할 수 있습니다. SQL Hint 기능은 SQL문을 실행할 때, Optimizer가 생성한 SQL Plan을 사용자가 원하는 방식으로 변경할 수 있도록 Optimizer에게 알려주는 역할이 됩니다. pg_hint_plan Extension이 설치되어 있지 않으면 SQL문에 Hint를 사용하여도 Hint가 적용되지 않으므로 SQL문장에 Hint를 사용하기 위해서는 반드시 설치되어야 합니다.
📢 본 문서에서는 pg_hint_plan Extension 설치 방법과 Hint 사용 방법에 대해 기술합니다.
pg_hint_plan Extension 설치 파일 준비
PostgreSQL 버전과 OS 버전에 따라 pg_hint_plan Extension 파일이 상이하므로, 설치 환경에 맞는 Extension 설치 파일을 준비합니다. pg_hint_plan Extension 설치 파일은 Github에서 다운로드할 수 있습니다.
📢 pg_hint_plan Extension Github URL : https://github.com/ossc-db/pg_hint_plan
pg_hint_plan Github를 들어가면 오른쪽 중간 Release(빨간 박스)를 클릭하여 PostgreSQL 버전과 OS 버전에 따라 필요한 설치 파일을 확인할 수 있습니다. 파일의 경우, pg_hint_plan{PostgreSQL_Version
} 으로 구분되어 있으니 스크롤을 내려 사용 중인 PostgreSQL 버전과 매칭되는 제목을 찾아 Assets에서 OS 버전에 맞는 파일을 준비합니다.
pg_hint_plan Extension 설치
📢 본 문서에서는 Rocky Linux 8.9 / PostgreSQL 16.1에서 테스트를 진행합니다. RPM과 Source Code를 사용하여 pg_hint_plan Extension을 설치할 수 있습니다.
RPM으로 설치
RPM 다운로드
Github에서 RPM 설치 파일의 링크를 복사하여 Server에 다운로드합니다.
## 설치파일 다운로드
[root@tech-225 ~] wget https://github.com/ossc-db/pg_hint_plan/releases/download/REL16_1_6_0/pg_hint_plan16-1.6.0-1.el8.x86_64.rpm
## 다운로드 파일 확인
[root@tech-225 ~] ls pg_hint_plan*
pg_hint_plan16-1.6.0-1.el8.x86_64.rpm
RPM 설치
[root@tech-225 ~] rpm -Uvh pg_hint_plan16-1.6.0-1.el8.x86_64.rpm
Verifying... ################################# [100%]
Preparing... ################################# [100%]
Updating / installing...
1:pg_hint_plan16-1.6.0-1.el8 ################################# [100%]
Source Code로 설치
Source Code 다운로드
[postgres@tech-231 ~]$ wget https://github.com/ossc-db/pg_hint_plan/archive/refs/tags/REL16_1_6_0.tar.gz
[postgres@tech-231 ~]$ ls
REL16_1_6_0.tar.gz
Source Code 압축 해제 및 설치(make && make install)
[postgres@tech-231 ~]$ tar -zxvf REL16_1_6_0.tar.gz
[postgres@tech-231 ~]$ ls
pg_hint_plan-REL16_1_6_0 REL16_1_6_0.tar.gz
[postgres@tech-231 ~]$ cd pg_hint_plan-REL16_1_6_0
[postgres@tech-231 ~]$ make && make install
pg_hint_plan Extension 설치 확인
설치 확인
PostgreSQL 엔진이 설치된 경로에서 pg_hint_plan Extension 설치 여부를 확인합니다.
📢 PostgreSQL 설치 환경에 따라 아래 표시되는 경로는 다를 수 있습니다.
{PostgreSQL 엔진 경로}/lib
경로에서 pg_hint_plan.so 라이브러리 파일 확인
[root@tech-225 lib] pwd
/usr/pgsql-16/lib
[root@tech-225 lib] ls pg_hint_plan*
pg_hint_plan.so
{PostgreSQL 엔진 경로}/share/extension
경로에서 pg_hint_plan 관련 control파일과 sql파일 확인
[root@tech-225 extension] pwd
/usr/pgsql-16/share/extension
[root@tech-225 extension] ls pg_hint_plan*
pg_hint_plan--1.3.0--1.3.1.sql pg_hint_plan--1.3.3--1.3.4.sql pg_hint_plan--1.3.7--1.3.8.sql pg_hint_plan--1.4.1--1.4.2.sql pg_hint_plan.control
pg_hint_plan--1.3.0.sql pg_hint_plan--1.3.4--1.3.5.sql pg_hint_plan--1.3.8--1.3.9.sql pg_hint_plan--1.4.2--1.5.sql
pg_hint_plan--1.3.1--1.3.2.sql pg_hint_plan--1.3.5--1.3.6.sql pg_hint_plan--1.3.9--1.4.sql pg_hint_plan--1.5--1.5.1.sql
pg_hint_plan--1.3.2--1.3.3.sql pg_hint_plan--1.3.6--1.3.7.sql pg_hint_plan--1.4--1.4.1.sql pg_hint_plan--1.5.1--1.6.0.sql
PostgreSQL에 pg_hint_plan Extension 적용
pg_hint_plaln Extension을 사용하기 위해서 PostgreSQL 설정 파일 내 shared_preload_libraries
Parameter 변경 후 PostgreSQL 재기동이 필요합니다.
[root@tech-225 extension] vi /var/lib/pgsql/16/data/postgresql.conf
...
shared_preload_libraries = 'pg_hint_plan'
...
[root@tech-225 extension] systemctl restart postgresql-16.service
Extension 설치
postgres=# CREATE EXTENSION pg_hint_plan ;
CREATE EXTENSION
postgres=# \dx
List of installed extensions
Name | Version | Schema | Description
--------------+---------+------------+------------------------------
pg_hint_plan | 1.4.2 | hint_plan |
plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language
(2 rows)
pg_hint_plan Extension 활용
Extension 설치 여부 확인
postgres=# SELECT * FROM pg_extension ;
oid | extname | extowner | extnamespace | extrelocatable | extversion | extconfig | extcondition
-------+--------------------+----------+--------------+----------------+------------+---------------+--------------
14482 | plpgsql | 10 | 11 | f | 1.0 | |
16814 | pg_hint_plan | 10 | 16813 | f | 1.6.0 | {16816,16815} | {"",""}
16825 | pg_stat_statements | 10 | 2200 | t | 1.10 | |
인덱스 정보 확인
postgres=# SELECT * FROM pg_indexes WHERE tablename = 'movie' ;
schemaname | tablename | indexname | tablespace | indexdef
------------+-----------+------------------+------------+--------------------------------------------------------------------------
public | movie | idx_release_year | | CREATE INDEX idx_release_year ON public.movie USING btree (release_year)
Hint 작성 방법
📢 pg_hint_plan Extension이 없는 경우에는 SQL에 Hint를 작성하더라도 적용되지 않습니다
postgres=# SELECT /*+ Hint 내용 작성 */ film_id , release_year FROM movie ;
pg_hint_plan Extension 확용 : SQL Plan 제어
Scan Method : Index Scan
Hint 없이 수행
해당 Query는 비트맵 인덱스를 사용하도록 SQL Plan이 수립되었습니다.
postgres=# EXPLAIN
SELECT *
FROM movie
WHERE release_year = 2003 ;
QUERY PLAN
---------------------------------------------------------------------------------
Bitmap Heap Scan on movie (cost=4.93..80.73 rows=101 width=384)
Recheck Cond: ((release_year)::integer = 2003)
-> Bitmap Index Scan on idx_release_year (cost=0.00..4.91 rows=101 width=0)
Index Cond: ((release_year)::integer = 2003)
Hint 적용
Query에서 특정 인덱스를 사용하도록 유도하기 위하여 indexscan Hint
를 적용하였습니다.
postgres=# EXPLAIN
SELECT /*+ indexscan ( movie idx_release_year ) */
film_id , release_year
FROM movie
WHERE release_year = 2003 ;
QUERY PLAN
----------------------------------------------------------------------------------
Index Scan using idx_release_year on movie (cost=0.15..241.28 rows=101 width=8)
Index Cond: ((release_year)::integer = 2003)
Join Method : Nested Loop Join + Leading
Hint 없이 수행
movie 테이블과 language 테이블을 Hash Join 하는 SQL Plan이 수립되었습니다.
postgres=# EXPLAIN
SELECT m.title, m.description, l.name
FROM movie m JOIN language l ON m.language_id = l.language_id ;
QUERY PLAN
-----------------------------------------------------------------------
Hash Join (cost=1.14..93.62 rows=1000 width=193)
Hash Cond: (m.language_id = l.language_id)
-> Seq Scan on movie m (cost=0.00..88.00 rows=1000 width=111)
-> Hash (cost=1.06..1.06 rows=6 width=88)
-> Seq Scan on language l (cost=0.00..1.06 rows=6 width=88)
Hint 적용
language 테이블을 Driving Table로 하고, movie 테이블과 Nested loop join 하도록 SQL Plan을 조정하기 위해 leading
, nestloop
Hint를 사용하였습니다.
postgres=# EXPLAIN
SELECT /*+ nestloop (m l) leading ((l m))*/
m.title , m.description , l.name
FROM movie m JOIN language l ON m.language_id = l.language_id ;
QUERY PLAN
-------------------------------------------------------------------------
Nested Loop (cost=0.00..181.56 rows=1000 width=193)
Join Filter: (l.language_id = m.language_id)
-> Seq Scan on language l (cost=0.00..1.06 rows=6 width=88)
-> Materialize (cost=0.00..93.00 rows=1000 width=111)
-> Seq Scan on movie m (cost=0.00..88.00 rows=1000 width=111)
여러 가지 Hint 혼용 : Parallel + Rows + Set
Hint 없이 수행
Hint의 이해를 돕기 위하여 조인 방법이나 Scan 방법이 아닌 Hint를 적용하기 위해 인덱스를 모두 없애고 Query를 수행하였습니다.
postgres=# EXPLAIN
SELECT m.title, m.release_year, p.amount
FROM payment p JOIN rental r ON p.rental_id = r.rental_id
JOIN inventory i ON r.inventory_id = i.inventory_id
JOIN movie m ON m.film_id = i.film_id
WHERE p.amount > 1
ORDER BY release_year ;
QUERY PLAN
--------------------------------------------------------------------------------------------
Sort (cost=2057.20..2086.83 rows=11852 width=25)
Sort Key: m.release_year
-> Hash Join (cost=739.56..1255.24 rows=11852 width=25)
Hash Cond: (i.film_id = m.film_id)
-> Hash Join (cost=639.06..991.78 rows=11852 width=8)
Hash Cond: (r.inventory_id = i.inventory_id)
-> Hash Join (cost=510.99..832.56 rows=11852 width=10)
Hash Cond: (p.rental_id = r.rental_id)
-> Seq Scan on payment p (cost=0.00..290.45 rows=11852 width=10)
Filter: (amount > '1'::numeric)
-> Hash (cost=310.44..310.44 rows=16044 width=8)
-> Seq Scan on rental r (cost=0.00..310.44 rows=16044 width=8)
-> Hash (cost=70.81..70.81 rows=4581 width=6)
-> Seq Scan on inventory i (cost=0.00..70.81 rows=4581 width=6)
-> Hash (cost=88.00..88.00 rows=1000 width=23)
-> Seq Scan on movie m (cost=0.00..88.00 rows=1000 width=23)
Hint 적용
Query 수행과 SQL Plan 조절에 도움이 되는 Hint( Rows, Parallel, Set )를 적용하였습니다.
Rows
Hint를 사용함으로써 SQL Plan에 출력되는 최종 rows를 특정 값으로 변경하여 출력하도록 적용하였습니다. Parallel
Hint에는 soft의 경우(default) 병렬 허용을 하되 Optimizer의 판단에 자율적으로 적용을 맡기는 병렬 수행으로 적용하고 , hard의 경우 강제 병렬 수행하도록 적용합니다.
Set
Hint는 PostgreSQL Configuration(postgresql.conf) 파일에 포함된 SQL Plan 관련 Parameter 중 일부를 SQL Plan에 적용할 수 있습니다.
postgres=# EXPLAIN
SELECT /*+ Rows(p r i m #1234) Parallel (r 2 hard) Parallel(m 5) Set(random_page_cost 2.0)*/
m.title , m.release_year , p.amount
FROM payment p JOIN rental r ON p.rental_id = r.rental_id
JOIN inventory i ON r.inventory_id = i.inventory_id
JOIN movie m ON m.film_id = i.film_id
WHERE p.amount > 1
ORDER BY release_year ;
QUERY PLAN
--------------------------------------------------------------------------------------------------------
Sort (cost=1008.17..1011.25 rows=1234 width=25)
Sort Key: m.release_year
-> Hash Join (cost=429.12..944.81 rows=1234 width=25)
Hash Cond: (i.film_id = m.film_id)
-> Hash Join (cost=328.62..681.34 rows=11852 width=8)
Hash Cond: (r.inventory_id = i.inventory_id)
-> Hash Join (cost=200.55..522.12 rows=11852 width=10)
Hash Cond: (p.rental_id = r.rental_id)
-> Seq Scan on payment p (cost=0.00..290.45 rows=11852 width=10)
Filter: (amount > '1'::numeric)
-> Hash (cost=0.00..0.00 rows=16044 width=8)
-> Gather (cost=0.00..0.00 rows=16044 width=8)
Workers Planned: 2
-> Parallel Seq Scan on rental r (cost=0.00..0.00 rows=6685 width=8)
-> Hash (cost=70.81..70.81 rows=4581 width=6)
-> Seq Scan on inventory i (cost=0.00..70.81 rows=4581 width=6)
-> Hash (cost=88.00..88.00 rows=1000 width=23)
-> Seq Scan on movie m (cost=0.00..88.00 rows=1000 width=23)
📢 random_page_cost : sequential이 아닌 read의 디스크 page fetch 추정치 설정 Parameter (default 4.0)
pg_hint_plan Extension에서 제공하는 Hint List
📢 Format에서 [ ] 안에 작성된 항목은 필수 옵션이 아니므로 작성하지 않아도 Hint 적용이 가능합니다.
Scan Method
Hint List | Description | Format |
SeqScan | 테이블 순차 스캔을 하도록 적용 | SeqScan(Table) |
TidScan | 테이블 TID 스캔을 하도록 적용 | TidScan(Table) |
IndexScan | 테이블에서 특정 인덱스가 있는 경우, 인덱스 스캔을 하도록 적용. 이 때 테이블에 인덱스가 하나거나 인덱스를 특정할 필요가 없는 경우, 테이블만 작성 가능 |
IndexScan(Table [index]) |
IndexOnlyScan | 테이블에서 특정 인덱스가 있는 경우, 인덱스 전용 스캔을 하도록 적용. 이 때 테이블에 인덱스가 하나거나 인덱스를 특정할 필요가 없는 경우, 테이블만 작성 가능 |
IndexOnlyScan(Table [index]) |
BitmapScan | 테이블에서 특정 인덱스가 있는 경우, 비트맵 스캔을 하도록 적용. 이 때 테이블에 인덱스가 하나거나 인덱스를 특정할 필요가 없는 경우, 테이블만 작성 가능 |
BitmapScan(Table [index]) |
IndexScanRegexp | 테이블에서 특정 POSIX 정규식 패턴과 일치하는 인덱스로 인덱스 스캔을 하도록 적용. 이 때 테이블에 특정 정규식 패턴이 하나거나 정규식 패턴을 특정할 필요가 없는 경우, 테이블만 작성 가능 |
IndexScanRegexp(Table [POSIX Regrexp..]) |
IndexOnlyScanRegexp | 테이블에서 특정 POSIX 정규식 패턴과 일치하는 인덱스로 인덱스 전용 스캔을 하도록 적용 (9.2v 이상). 이 때 테이블에 특정 정규식 패턴이 하나거나 정규식 패턴을 특정할 필요가 없는 경우, 테이블만 작성 가능 |
IndexOnlyScanRegexp(Table [POSIX Regrexp..]) |
BitmapScanRegexp | 테이블에서 특정 POSIX 정규식 패턴과 일치하는 인덱스로 비트맵 스캔을 하도록 적용. 이 때 테이블에 특정 정규식 패턴이 하나거나 정규식 패턴을 특정할 필요가 없는 경우, 테이블만 작성 가능 |
BitmapScanRegexp(Table [POSIX Regrexp..]) |
NoSeqScan | 테이블에서 순차 스캔을 하지 않도록 적용 | NoSeqScan(Table) |
NoTidScan | 테이블에서 TID 스캔을 하지 않도록 적용 | NoTidScan(Table) |
NoIndexScan | 테이블에서 인덱스 스캔이나 인덱스 전용 스캔을 하지 않도록 적용 | NoIndexScan(Table) |
NoIndexOnlyScan | 테이블에서 인덱스 전용 스캔을 하지 않도록 적용 | NoIndexOnlyScan(Table) |
NoBitmapScan | 테이블에서 비트맵 스캔 하지 않도록 적용 | NoBitmapScan(Table) |
Join Method
Hint List | Description | Format |
Leading | 조인의 순서를 지정하도록 적용. 조인 방향까지 제어하려면 Format에서 괄호를 우측 (2)번으로 작성해야하고 이때 Table1이 Driving 또는 Outer Table |
(1) Leading(Table Table) (2) Leading((Table1 Table2)) |
NestLoop | 특정 테이블이 중첩 루프(Nested Loop) 조인으로 풀리도록 적용 | NestLoop(TableTable [Table..]) |
HashJoin | 특정 테이블이 해시 조인으로 풀리도록 적용 | HashJoin(Table Table [Table..]) |
MergeJoin | 특정 테이블이 머지 조인으로 풀리도록 적용 | MergeJoin(Table Table [Table..]) |
NoNestLoop | 특정 테이블이 중첩 루프(Nested Loop) 조인으로 풀리지 않도록 적용 | NoNestLoop(Table Table [Table..]) |
NoHashJoin | 특정 테이블이 해시 조인으로 풀리지 않도록 적용 | NoHashJoin(Table Table [Table..]) |
NoMergeJoin | 특정 테이블이 머지 조인으로 풀리지 않도록 적용 | NoMergeJoin(Table Table [Table..]) |
Behavior Control on Join
Hint List | Description | Format |
Memoize | Memoize가 활성화된 경우, 특정 테이블 간의 조인 중 최상위 조인의 결과값을 캐싱하도록 적용 | Memoize(Table Table [ Table...]) |
NoMemoize | 특정 테이블 사이의 조인에서 내부 결과를 캐싱하여 기억하는 것을 금지하도록 적용 | NoMemoize(Table Table [ Table...]) |
Row Number Correction
Hint List | Description | Format |
Rows | 절대(#), 더하기(+), 빼기(-) 및 곱하기(*)를 이용하여 특정 테이블에 대한 조인 결과의 행 번호를 수정하도록 적용 | Rows(Table Table [ Table...] correction) |
Parallel Query Configuration
Hint List | Description | Format |
Parallel | 테이블 스캔에 병렬 수행을 적용 ( default : soft ) | Parallel(Table <# of workers> [soft|hard]) |
GUC(Grand Unified Configuration) : postgresql.conf Parameter
Hint List | Description | Format |
Set | Optimizer가 실행되는 동안 SQL Plan과 관련된 GUC 매개변수 값을 Hint에 정의된 값으로 적용. 다른 Hint와 충돌한 경우 적용되지않음 |
Set(GUC-param value) |
'엑셈 경쟁력 > DB 인사이드' 카테고리의 다른 글
DB 인사이드 | PWI - LOCK > Relation-level Lock (0) | 2024.06.11 |
---|---|
DB 인사이드 | PWI - PostgreSQL Wait Interface (0) | 2024.05.27 |
DB 인사이드 | PostgreSQL Extension - Introduction (0) | 2024.04.18 |
DB 인사이드 | PostgreSQL 16 Release - New Feature (3) (2) | 2023.12.27 |
DB 인사이드 | PostgreSQL 16 Release - New Feature (2) (0) | 2023.11.30 |
댓글