Contents
see ListPostgreSQL 18이 2026년 초 정식 출시되며, 데이터베이스 성능과 기능 면에서 획기적인 발전을 이루었다. 새로운 비동기 I/O 서브시스템은 읽기 성능을 최대 3배까지 향상시키고, UUIDv7 네이티브 지원과 Virtual Generated Columns가 추가되었다. Stack Overflow 2025 설문에서 PostgreSQL이 MySQL을 제치고 1위를 차지한 만큼, 이번 업데이트는 더욱 주목할 만하다.
비동기 I/O(AIO) 서브시스템
PostgreSQL 18의 가장 큰 변화는 새로운 비동기 I/O 서브시스템이다. 기존에는 모든 디스크 읽기가 동기 방식으로 처리되어, I/O 대기 중 CPU가 유휴 상태였다. 이제 순차 스캔, 비트맵 힙 스캔, VACUUM 등의 작업에서 비동기 I/O를 활용할 수 있다.
성능 향상 확인
-- AIO 설정 확인
SHOW io_method;
-- 결과: 'io_uring' (Linux) 또는 'worker' (기타 OS)
-- io_uring 사용 시 (Linux 커널 5.1+)
-- postgresql.conf
io_method = 'io_uring'
io_max_concurrency = 32 -- 동시 I/O 요청 수
-- 성능 비교 테스트
-- 1억 건 테이블 순차 스캔
CREATE TABLE large_data AS
SELECT generate_series(1, 100000000) AS id,
md5(random()::text) AS data,
now() - (random() * interval '365 days') AS created_at;
-- EXPLAIN ANALYZE로 성능 측정
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT count(*) FROM large_data WHERE created_at > '2026-01-01';
-- 기존 (동기 I/O): ~12.5초
-- AIO (io_uring): ~4.2초 (약 3배 향상)
UUIDv7 네이티브 지원
PostgreSQL 18에서 uuidv7() 함수가 내장되었다. UUIDv7은 타임스탬프 기반으로 정렬 가능한 UUID를 생성하여, B-tree 인덱스 성능이 기존 UUIDv4 대비 크게 향상된다.
UUIDv7 vs UUIDv4 비교
-- 기존 UUIDv4: 완전 랜덤, 인덱스 분산
SELECT gen_random_uuid();
-- 결과: 'a3bb189e-8bf9-3888-9912-ace4e6543002' (랜덤)
-- 새로운 UUIDv7: 타임스탬프 순서 보장
SELECT uuidv7();
-- 결과: '01902f4b-8c0a-7def-8543-1a2b3c4d5e6f' (시간순 정렬)
-- 타임스탬프 추출
SELECT uuidv7() AS id,
uuid_extract_timestamp(uuidv7()) AS created_at;
-- id: 01902f4b-8c0a-7def-8543-1a2b3c4d5e6f
-- created_at: 2026-04-05 14:30:00.123+09
테이블 설계에 UUIDv7 적용
-- UUIDv7을 기본키로 사용하는 테이블
CREATE TABLE orders (
id uuid DEFAULT uuidv7() PRIMARY KEY,
user_id uuid NOT NULL,
total numeric(12,2) NOT NULL,
status text DEFAULT 'pending',
created_at timestamptz DEFAULT now()
);
-- UUIDv7은 시간순 정렬이 보장되므로
-- created_at 없이도 생성 순서를 알 수 있다
SELECT id, total, status
FROM orders
ORDER BY id DESC -- 최신순 정렬 = id 역순
LIMIT 20;
-- 인덱스 성능 비교 테스트
CREATE TABLE test_v4 (id uuid DEFAULT gen_random_uuid() PRIMARY KEY, data text);
CREATE TABLE test_v7 (id uuid DEFAULT uuidv7() PRIMARY KEY, data text);
-- 100만 건 삽입 후 인덱스 크기 비교
INSERT INTO test_v4 (data) SELECT md5(i::text) FROM generate_series(1,1000000) i;
INSERT INTO test_v7 (data) SELECT md5(i::text) FROM generate_series(1,1000000) i;
SELECT relname, pg_size_pretty(pg_relation_size(oid)) AS index_size
FROM pg_class WHERE relname LIKE 'test_v%_pkey';
-- test_v4_pkey: 56 MB (페이지 분할 빈번)
-- test_v7_pkey: 40 MB (순차 삽입으로 효율적)
Virtual Generated Columns
기존의 Stored Generated Columns은 데이터를 물리적으로 저장했지만, Virtual Generated Columns은 조회 시점에 계산된다. 저장 공간을 절약하면서도 파생 데이터를 간편하게 제공할 수 있다.
-- Virtual Generated Column 생성
CREATE TABLE products (
id serial PRIMARY KEY,
name text NOT NULL,
price numeric(10,2) NOT NULL,
tax_rate numeric(4,2) DEFAULT 0.10,
-- VIRTUAL: 조회 시 계산 (저장 공간 미사용)
price_with_tax numeric(10,2)
GENERATED ALWAYS AS (price * (1 + tax_rate)) VIRTUAL,
-- STORED: 삽입/수정 시 계산하여 저장 (기존 방식)
search_name tsvector
GENERATED ALWAYS AS (to_tsvector('korean', name)) STORED
);
INSERT INTO products (name, price) VALUES ('무선 키보드', 89000);
SELECT name, price, tax_rate, price_with_tax FROM products;
-- name: 무선 키보드
-- price: 89000.00
-- tax_rate: 0.10
-- price_with_tax: 97900.00 (조회 시 실시간 계산)
-- Virtual Column에도 인덱스 생성 가능
CREATE INDEX idx_price_with_tax ON products (price_with_tax);
Skip Scan 인덱스 최적화
멀티 컬럼 B-tree 인덱스에서 첫 번째 컬럼 조건 없이도 인덱스를 활용할 수 있는 Skip Scan이 지원된다.
-- 복합 인덱스 생성
CREATE INDEX idx_orders_status_date
ON orders (status, created_at);
-- 기존 (PostgreSQL 17): status 조건 없으면 인덱스 미사용
-- PostgreSQL 18: Skip Scan으로 인덱스 활용
EXPLAIN ANALYZE
SELECT * FROM orders
WHERE created_at > '2026-04-01'
ORDER BY created_at DESC
LIMIT 100;
-- PostgreSQL 17: Seq Scan (전체 테이블 스캔)
-- PostgreSQL 18: Index Scan using idx_orders_status_date
-- -> Skip Scan: status 값을 건너뛰며 created_at 조건 검색
OAuth 인증 지원
-- pg_hba.conf 설정
# OAuth 인증 방식 추가
host mydb all 0.0.0.0/0 oauth
issuer="https://auth.example.com"
scope="openid db:read db:write"
-- postgresql.conf
oauth_issuer_url = 'https://auth.example.com/.well-known/openid-configuration'
oauth_client_id = 'postgres-server'
oauth_client_secret = 'your-secret-here'
-- 클라이언트 연결
psql "host=db.example.com dbname=mydb \
oauth_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
Temporal Constraints (범위 제약조건)
-- 시간 범위 중복 방지 (예: 호텔 예약)
CREATE TABLE reservations (
id serial PRIMARY KEY,
room_id int NOT NULL,
guest_name text NOT NULL,
period tstzrange NOT NULL,
-- 같은 방의 예약 기간이 겹치지 않도록
CONSTRAINT no_overlap
EXCLUDE USING gist (room_id WITH =, period WITH &&)
);
-- 정상 삽입
INSERT INTO reservations (room_id, guest_name, period)
VALUES (101, '홍길동', '[2026-04-10, 2026-04-15)');
-- 기간 겹침 시 에러 발생
INSERT INTO reservations (room_id, guest_name, period)
VALUES (101, '김철수', '[2026-04-13, 2026-04-18)');
-- ERROR: conflicting key value violates exclusion constraint
업그레이드 팁
- pg_upgrade를 사용하여 인플레이스 업그레이드 수행
- AIO 활성화 전 스토리지 시스템이 io_uring을 지원하는지 확인
- 기존 UUIDv4 컬럼을 UUIDv7으로 전환할 때는 새 데이터부터 적용하는 것을 권장
- Virtual Generated Columns 전환 시 기존 Stored Columns과의 성능 트레이드오프 검토