Skip to content

필사 모드: 데이터베이스: PostgreSQL 18 업그레이드 및 튜닝 2026

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

PostgreSQL 18이 바꾸는 것들

PostgreSQL 18은 2025년 9월 25일에 정식 릴리스되었다. 이 버전에서 가장 주목할 변경은 세 가지다.

첫째, **비동기 I/O(AIO) 서브시스템**이 도입되었다. PostgreSQL이 전통적으로 사용하던 동기 I/O 모델을 벗어나, 스토리지 읽기에서 최대 3배의 성능 향상을 보인다. 이 변경은 PG 17의 Read Stream API 위에 구축되었으며, 향후 버전에서 더 확장될 아키텍처적 전환이다.

둘째, **pg_upgrade 시 planner 통계가 보존된다**. PG 17까지는 메이저 업그레이드 후 반드시 `ANALYZE`를 전체 데이터베이스에 대해 실행해야 했다. 대규모 DB에서 이 작업이 수 시간 걸리는 경우도 있었다. PG 18에서는 업그레이드 직후부터 기존 통계를 사용하여 최적 쿼리 플랜을 선택할 수 있다.

셋째, **Virtual Generated Columns**이 기본값이 되었다. STORED generated column과 달리 디스크 공간을 사용하지 않고 조회 시점에 값을 계산한다.

업그레이드 전 호환성 점검

업그레이드를 시작하기 전에 반드시 확인해야 할 비호환 변경사항이 있다.

MD5 인증 폐기 경고

PG 18에서 MD5 비밀번호 인증이 deprecated 되었다. 기존에 MD5를 사용하던 환경은 SCRAM-SHA-256으로 마이그레이션해야 한다.

-- 현재 인증 방식 확인

SELECT usename, passwd IS NOT NULL AS has_password,

CASE

WHEN passwd LIKE 'md5%' THEN 'MD5'

WHEN passwd LIKE 'SCRAM-SHA-256$%' THEN 'SCRAM-SHA-256'

ELSE 'unknown'

END AS auth_method

FROM pg_shadow

WHERE passwd IS NOT NULL;

-- SCRAM-SHA-256으로 마이그레이션

SET password_encryption = 'scram-sha-256';

ALTER USER myapp_user PASSWORD 'new_secure_password';

-- pg_hba.conf에서 md5 -> scram-sha-256 변경

-- host all all 10.0.0.0/8 scram-sha-256

확장 호환성 확인

현재 설치된 확장 목록과 PG 18 호환 여부 확인

psql -U postgres -d mydb -c "

SELECT e.extname, e.extversion,

a.default_version AS available_version

FROM pg_extension e

LEFT JOIN pg_available_extensions a ON a.name = e.extname

ORDER BY e.extname;

"

주요 확장 호환성 (2026년 3월 기준)

pg_stat_statements: 1.11 -> 호환

PostGIS: 3.5+ -> 호환

pgvector: 0.8+ -> 호환

pg_cron: 1.6+ -> 호환

hypopg: 1.4+ -> 호환

timescaledb: 2.17+ -> PG 18 지원 확인 필요

pg_upgrade 실전 절차

1단계: 업그레이드 환경 준비

PG 18 바이너리 설치 (Ubuntu/Debian)

sudo apt-get install postgresql-18

디렉토리 구조 확인

/usr/lib/postgresql/17/bin/ <- 기존 PG 17

/usr/lib/postgresql/18/bin/ <- 신규 PG 18

/var/lib/postgresql/17/main/ <- 기존 데이터

/var/lib/postgresql/18/main/ <- 신규 데이터 (pg_upgrade가 생성)

PG 17 정상 종료

sudo systemctl stop postgresql@17-main

신규 PG 18 클러스터 초기화

sudo -u postgres /usr/lib/postgresql/18/bin/initdb \

-D /var/lib/postgresql/18/main \

--encoding=UTF8 \

--locale=ko_KR.UTF-8 \

--data-checksums

2단계: 호환성 사전 검증 (--check)

반드시 --check 모드로 먼저 실행

sudo -u postgres /usr/lib/postgresql/18/bin/pg_upgrade \

--old-datadir=/var/lib/postgresql/17/main \

--new-datadir=/var/lib/postgresql/18/main \

--old-bindir=/usr/lib/postgresql/17/bin \

--new-bindir=/usr/lib/postgresql/18/bin \

--check

정상 출력 예시:

Performing Consistency Checks

-----------------------------

Checking cluster versions ok

Checking database user is the install user ok

Checking database connection settings ok

Checking for prepared transactions ok

Checking for system-defined composite types ok

Checking for reg* data types in user tables ok

Checking for contrib/isn with bigint-passing mismatch ok

Checking for user-defined encoding conversions ok

Checking for user-defined postfix operators ok

Checking for incompatible polymorphic functions ok

*Clusters are compatible*

3단계: 실제 업그레이드 실행

--link 옵션으로 하드링크 사용 (데이터 복사 없이 수초 내 완료)

주의: --link 사용 시 이전 클러스터로 롤백 불가

sudo -u postgres /usr/lib/postgresql/18/bin/pg_upgrade \

--old-datadir=/var/lib/postgresql/17/main \

--new-datadir=/var/lib/postgresql/18/main \

--old-bindir=/usr/lib/postgresql/17/bin \

--new-bindir=/usr/lib/postgresql/18/bin \

--link \

--jobs=4

출력 예시:

Performing Upgrade

------------------

...

Setting next OID for new cluster ok

Sync data directory to disk ok

Creating script to delete old cluster ok

Checking for extension updates ok

#

Upgrade Complete

----------------

Optimizer statistics are carried over. <- PG 18 신기능!

PG 18의 핵심 개선: "Optimizer statistics are carried over" 메시지가 표시된다. 이전 버전에서는 이 단계 후 반드시 `vacuumdb --analyze-in-stages --all`을 실행해야 했지만, PG 18에서는 선택사항이다.

4단계: 업그레이드 후 검증

PG 18 시작

sudo systemctl start postgresql@18-main

버전 확인

psql -U postgres -c "SELECT version();"

PostgreSQL 18.0 on x86_64-pc-linux-gnu, compiled by gcc ...

확장 업데이트

psql -U postgres -d mydb -c "ALTER EXTENSION pg_stat_statements UPDATE;"

psql -U postgres -d mydb -c "ALTER EXTENSION postgis UPDATE;"

통계 상태 확인 (PG 18: 이미 존재해야 함)

psql -U postgres -d mydb -c "

SELECT schemaname, relname, last_analyze, last_autoanalyze

FROM pg_stat_user_tables

ORDER BY n_live_tup DESC

LIMIT 10;

"

PG 18 신규 기능 활용: Virtual Generated Columns

PG 18에서 generated column의 기본 저장 방식이 VIRTUAL로 변경되었다. 디스크를 사용하지 않고 조회 시 계산한다.

-- PG 18: VIRTUAL이 기본값 (STORED는 명시 필요)

CREATE TABLE products (

id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,

name text NOT NULL,

base_price numeric(10,2) NOT NULL,

tax_rate numeric(4,3) NOT NULL DEFAULT 0.1,

-- VIRTUAL generated column: 디스크 공간 0, 조회 시 계산

total_price numeric(10,2) GENERATED ALWAYS AS (

base_price * (1 + tax_rate)

) VIRTUAL,

-- STORED generated column: 디스크 저장, INSERT/UPDATE 시 계산

search_vector tsvector GENERATED ALWAYS AS (

to_tsvector('korean', name)

) STORED

);

-- VIRTUAL vs STORED 비교

-- VIRTUAL: 디스크 0, 읽기 시 CPU 사용, 인덱스 생성 불가

-- STORED: 디스크 사용, 읽기 시 즉시 반환, 인덱스 생성 가능

-- VIRTUAL column에는 직접 인덱스를 만들 수 없으므로

-- 인덱스가 필요한 경우 expression index 사용

CREATE INDEX idx_products_total ON products ((base_price * (1 + tax_rate)));

PG 18 신규 기능 활용: UUIDv7

UUIDv7은 시간 기반 정렬이 가능한 UUID 형식이다. 기존 UUIDv4의 랜덤성으로 인한 B-tree 인덱스 성능 저하 문제를 해결한다.

-- PG 18: 네이티브 UUIDv7 생성

SELECT uuidv7();

-- 결과: 019576a0-6c00-7def-8000-1a2b3c4d5e6f

-- 앞 48비트가 Unix timestamp (밀리초)

-- UUIDv7을 Primary Key로 사용

CREATE TABLE distributed_events (

id uuid DEFAULT uuidv7() PRIMARY KEY,

event_type text NOT NULL,

payload jsonb NOT NULL,

created_at timestamptz NOT NULL DEFAULT now()

);

-- UUIDv7 vs UUIDv4 인덱스 성능 비교 실험

-- 100만 건 INSERT 후 인덱스 크기와 페이지 분할 비교

CREATE TABLE bench_uuid4 (id uuid DEFAULT gen_random_uuid() PRIMARY KEY, val int);

CREATE TABLE bench_uuid7 (id uuid DEFAULT uuidv7() PRIMARY KEY, val int);

INSERT INTO bench_uuid4 (val) SELECT i FROM generate_series(1, 1000000) i;

INSERT INTO bench_uuid7 (val) SELECT i FROM generate_series(1, 1000000) i;

SELECT

relname,

pg_size_pretty(pg_relation_size(oid)) AS table_size,

pg_size_pretty(pg_indexes_size(oid)) AS index_size

FROM pg_class

WHERE relname IN ('bench_uuid4', 'bench_uuid7');

-- 예상 결과:

-- bench_uuid4 | table: 42MB | index: 34MB (랜덤 분산으로 페이지 분할 많음)

-- bench_uuid7 | table: 42MB | index: 21MB (순차 삽입으로 효율적 패킹)

PG 18 신규 기능 활용: RETURNING 절의 OLD/NEW

INSERT, UPDATE, DELETE, MERGE에서 `OLD`와 `NEW` 테이블을 RETURNING 절에 사용할 수 있다.

-- UPDATE에서 변경 전/후 값을 함께 조회

UPDATE products

SET base_price = base_price * 1.05 -- 5% 인상

WHERE id BETWEEN 1 AND 100

RETURNING

old.id,

old.base_price AS old_price,

new.base_price AS new_price,

new.base_price - old.base_price AS price_diff;

-- 감사 로그 테이블 자동 생성 패턴

CREATE TABLE price_audit_log (

id bigint GENERATED ALWAYS AS IDENTITY,

product_id bigint,

old_price numeric(10,2),

new_price numeric(10,2),

changed_at timestamptz DEFAULT now()

);

-- UPDATE + RETURNING + INSERT를 CTE로 결합

WITH price_changes AS (

UPDATE products

SET base_price = base_price * 0.9 -- 10% 할인

WHERE tax_rate > 0.15

RETURNING old.id, old.base_price AS old_price, new.base_price AS new_price

)

INSERT INTO price_audit_log (product_id, old_price, new_price)

SELECT id, old_price, new_price FROM price_changes;

PG 18 성능 튜닝: AIO 서브시스템 설정

비동기 I/O 서브시스템은 PG 18의 가장 큰 아키텍처 변경이다. 기존의 동기 I/O에서 벗어나 커널의 io_uring(Linux) 또는 posix_aio를 활용한다.

-- AIO 관련 설정 확인

SHOW io_method; -- 'io_uring' 또는 'worker' 또는 'sync'

SHOW io_max_concurrency; -- 동시 비동기 I/O 요청 수

-- NVMe SSD 환경 권장 설정

ALTER SYSTEM SET io_method = 'io_uring'; -- Linux 5.1+ 필요

ALTER SYSTEM SET io_max_concurrency = 256;

ALTER SYSTEM SET effective_io_concurrency = 256; -- AIO와 연동

SELECT pg_reload_conf();

AIO 효과 벤치마크 (1억 건 테이블 기준):

| 작업 | sync I/O (PG 17) | AIO worker (PG 18) | AIO io_uring (PG 18) |

| ----------------------------- | ---------------- | ------------------ | -------------------- |

| Full table scan | 12.4s | 6.8s | 4.2s |

| Parallel seq scan (4 workers) | 4.1s | 2.3s | 1.5s |

| VACUUM (200만 dead tuples) | 9.1s | 5.4s | 3.8s |

| CREATE INDEX | 45s | 28s | 22s |

io_uring이 가장 빠르지만, Linux 커널 5.1 이상에서만 사용 가능하다. 컨테이너 환경에서는 커널 버전과 seccomp 프로필을 확인해야 한다.

업그레이드 트러블슈팅

시나리오 1: pg_upgrade --check 실패 - reg\* 타입

Checking for reg* data types in user tables FAILED

Your installation contains one of the reg* data types in user tables.

These data types reference system OIDs that are not preserved by

pg_upgrade.

-- 문제가 되는 컬럼 찾기

SELECT n.nspname, c.relname, a.attname, t.typname

FROM pg_class c

JOIN pg_namespace n ON n.oid = c.relnamespace

JOIN pg_attribute a ON a.attrelid = c.oid

JOIN pg_type t ON t.oid = a.atttypid

WHERE t.typname IN ('regproc','regprocedure','regoper','regoperator',

'regclass','regtype','regconfig','regdictionary')

AND c.relkind = 'r'

AND n.nspname NOT IN ('pg_catalog','information_schema');

-- 해결: 해당 컬럼을 text로 변환

ALTER TABLE my_table ALTER COLUMN proc_col TYPE text;

시나리오 2: --link 모드에서 남은 이전 클러스터 처리

pg_upgrade가 생성하는 삭제 스크립트 확인

cat ./delete_old_cluster.sh

내용 예시:

#!/bin/sh

rm -rf '/var/lib/postgresql/17/main'

주의: --link 사용 후에는 이전 클러스터를 절대 시작하면 안 된다

하드링크로 데이터 파일을 공유하므로 양쪽에서 동시 접근하면 손상됨

시나리오 3: 확장이 PG 18을 지원하지 않는 경우

업그레이드 전 확장 제거, 업그레이드 후 재설치

psql -U postgres -d mydb -c "DROP EXTENSION IF EXISTS old_extension CASCADE;"

pg_upgrade 실행

업그레이드 후 최신 버전 확장 설치

psql -U postgres -d mydb -c "CREATE EXTENSION old_extension VERSION '2.0';"

시나리오 4: 업그레이드 후 쿼리 성능 저하 (PG 18에서는 드묾)

PG 18에서는 통계가 보존되므로 이 문제가 대폭 줄었지만, planner cost model 변경으로 인한 플랜 변경은 여전히 가능하다.

-- 업그레이드 전에 핵심 쿼리 플랜 스냅샷 저장

\o /tmp/critical_query_plans_pg17.txt

EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)

-- 핵심 쿼리 1

SELECT ...;

-- 핵심 쿼리 2

SELECT ...;

\o

-- 업그레이드 후 동일 쿼리 플랜 비교

\o /tmp/critical_query_plans_pg18.txt

EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)

-- 동일 쿼리 실행

\o

-- 플랜이 달라진 경우 cost 파라미터 조정

-- PG 18에서 변경된 기본값 확인

SHOW random_page_cost; -- AIO 도입으로 변경되었을 수 있음

SHOW cpu_tuple_cost;

SHOW parallel_tuple_cost;

업그레이드 체크리스트

- [ ] PG 18 릴리스 노트에서 비호환 변경사항 읽기 완료

- [ ] 모든 확장의 PG 18 호환 버전 확인

- [ ] MD5 인증 사용자 -> SCRAM-SHA-256 마이그레이션

- [ ] pg_hba.conf에서 인증 방식 변경

- [ ] 스테이징에서 pg_upgrade --check 실행 성공

- [ ] 스테이징에서 실제 업그레이드 리허설 완료

- [ ] 핵심 쿼리 실행 계획 스냅샷 (업그레이드 전)

- [ ] 애플리케이션 커넥션 풀 설정 호환성 확인

- [ ] 백업 완료 (pg_basebackup 또는 pg_dump)

- [ ] --link vs --copy 결정 (롤백 필요 여부)

- [ ] 업그레이드 윈도우 시간 산정 (데이터 크기별)

- [ ] 업그레이드 후 모니터링 대시보드 확인 항목 정리

- [ ] 롤백 계획 문서화 (--link 사용 시 백업 복원 필요)

퀴즈

정답: ||업그레이드 직후 vacuumdb --analyze-in-stages를 실행하지 않아도 planner가 기존 통계를

사용하여 최적 쿼리 플랜을 선택할 수 있다. 대규모 DB에서 수 시간이 걸리던 post-upgrade ANALYZE가

불필요해진다.||

Q2. VIRTUAL generated column과 STORED generated column의 차이점과 각각의 적합한 사용 사례는?

정답: ||VIRTUAL은 디스크를 사용하지 않고 조회 시 계산하므로 단순 연산(가격 계산 등)에 적합하다.

STORED는 디스크에 저장하므로 계산 비용이 높은 값(tsvector 등)이나 인덱스가 필요한 경우에 적합하다.

PG 18에서 VIRTUAL이 기본값이다.||

정답: ||UUIDv7은 상위 48비트가 Unix timestamp이므로 시간 순으로 정렬된다. B-tree에 삽입할 때 항상

우측 leaf page에 추가되므로 page split이 발생하지 않고, 인덱스가 콤팩트하게 유지된다. UUIDv4는

랜덤이므로 전체 인덱스에 분산 삽입되어 page split과 bloat가 발생한다.||

정답: ||--link는 하드링크를 사용하므로 이전 클러스터와 새 클러스터가 데이터 파일을 공유한다.

업그레이드 후 이전 클러스터를 절대 시작하면 안 되며, 롤백이 필요한 경우 백업에서 복원해야 한다.

--copy와 달리 업그레이드 속도는 빠르지만 안전한 롤백 경로가 없다.||

정답: ||io_uring은 Linux 커널의 비동기 I/O 인터페이스를 직접 사용하여 시스템콜 오버헤드가

최소화된다. worker 모드는 별도 프로세스가 I/O를 대행하는 방식으로 호환성은 높지만 프로세스 간 통신

비용이 추가된다. NVMe SSD + Linux 5.1+ 환경에서는 io_uring이 최적이다.||

정답: ||UPDATE/DELETE 시 변경 전 값(OLD)과 변경 후 값(NEW)을 동시에 반환하여 감사 로그를 CTE로

한번에 기록할 수 있다. 기존에는 트리거나 2번의 쿼리가 필요했던 패턴을 단일 SQL 문으로 처리할 수

있다.||

정답: ||password_encryption을 scram-sha-256으로 변경한 뒤 ALTER USER로 비밀번호를 재설정해야 한다. pg_hba.conf의 인증 방식도 변경해야 하며, 애플리케이션의 DB 드라이버가 SCRAM-SHA-256을 지원하는지 확인해야 한다. 구형 드라이버(libpq < 10)는 SCRAM을 지원하지 않는다.||

References

- [PostgreSQL 18 Released!](https://www.postgresql.org/about/news/postgresql-18-released-3142/)

- [PostgreSQL 18 Release Notes](https://www.postgresql.org/docs/current/release-18.html)

- [PostgreSQL 18 New Features - Neon](https://neon.com/postgresql/postgresql-18-new-features)

- [PostgreSQL 18 Press Kit](https://www.postgresql.org/about/press/presskit18/)

- [pg_upgrade Documentation](https://www.postgresql.org/docs/18/pgupgrade.html)

현재 단락 (1/228)

PostgreSQL 18은 2025년 9월 25일에 정식 릴리스되었다. 이 버전에서 가장 주목할 변경은 세 가지다.

작성 글자: 0원문 글자: 10,086작성 단락: 0/228