PostgreSQL 18 소개

PostgreSQL 18(2025년 출시)은 데이터베이스 역사상 가장 큰 성능 개선 중 하나인 비동기 I/O 서브시스템을 도입했습니다. 동일한 쿼리와 데이터셋에서 최대 3배의 성능 향상을 보여주며, 개발자 편의를 위한 다양한 신기능도 포함되었습니다.

1. 비동기 I/O (AIO) 서브시스템

PostgreSQL 18의 가장 큰 변화입니다. Sequential Scan, Bitmap Heap Scan, VACUUM 등 I/O 집약적 작업에서 극적인 성능 향상을 보입니다.

-- 성능 차이 확인 (대용량 테이블 시퀀셜 스캔)
-- PostgreSQL 17: ~152초
-- PostgreSQL 18: ~51초 (3배 빠름!)

-- AIO 설정 확인
SHOW effective_io_concurrency; -- 비동기 I/O 동시성

-- 서버 설정 최적화 (postgresql.conf)
-- effective_io_concurrency = 200  -- SSD는 200, HDD는 2 권장
-- maintenance_io_concurrency = 100 -- VACUUM, CREATE INDEX 등에 사용

-- EXPLAIN으로 I/O 분석 (PostgreSQL 18 개선)
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT *
FROM orders
WHERE created_at >= NOW() - INTERVAL '30 days'
  AND status = 'pending';

-- 출력에 새로운 I/O, CPU, WAL 통계 포함
-- Buffers: shared hit=1024 read=8192 dirtied=0 written=0
-- I/O Timings: read=45.3 write=0.0
-- CPU: user=0.15 system=0.02

2. Index Skip Scan

다중 컬럼 인덱스를 첫 번째 컬럼 필터 없이도 활용할 수 있는 새로운 플래너 기능입니다.

-- 예시 테이블 및 복합 인덱스
CREATE TABLE sales (
    id         BIGINT PRIMARY KEY,
    region     TEXT NOT NULL,   -- 카디널리티 낮음 (예: 5개 지역)
    product_id BIGINT NOT NULL,
    amount     NUMERIC(12, 2),
    sale_date  DATE
);

CREATE INDEX idx_sales_region_product ON sales (region, product_id);

-- PostgreSQL 17: region 조건 없이 product_id만 검색 -> Sequential Scan
-- PostgreSQL 18: Index Skip Scan 활용 -> 훨씬 빠름!
SELECT DISTINCT product_id
FROM sales
WHERE product_id > 1000; -- region 조건 없음!

-- EXPLAIN으로 확인
EXPLAIN SELECT DISTINCT product_id FROM sales WHERE product_id > 1000;
-- "Index Only Scan using idx_sales_region_product on sales"
-- "  Index Cond: (product_id > 1000)"
-- "  Skip Condition: (region IS NOT NULL)"

3. UUIDv7 지원

타임스탬프 기반으로 정렬 가능한 UUID를 생성하는 uuidv7() 함수가 추가되었습니다. 기존 uuid_generate_v4()보다 인덱스 효율이 크게 향상됩니다.

-- UUIDv7 생성
SELECT uuidv7();
-- 019523e1-b483-72a0-8fd2-4a8fc3a5b612
-- 앞부분이 타임스탬프이므로 시간 순서대로 정렬됨!

-- UUIDv4 vs UUIDv7 비교
-- UUIDv4: 완전 랜덤 -> B-tree 인덱스 단편화 심각
-- UUIDv7: 시간 기반 -> 인덱스 순차 삽입 가능, 단편화 최소화

-- 테이블에 UUIDv7 적용
CREATE TABLE events (
    id         UUID DEFAULT uuidv7() PRIMARY KEY,
    event_type TEXT NOT NULL,
    payload    JSONB,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- UUID가 단조증가하므로 B-tree 인덱스의 마지막 페이지에만 삽입 -> 캐시 효율 극대화

4. RETURNING 절 개선 (OLD/NEW)

INSERT, UPDATE, DELETE, MERGE에서 변경 전후 값을 모두 반환할 수 있습니다.

-- UPDATE 전후 값 동시 반환 (PostgreSQL 18 신기능)
UPDATE products
SET price = price * 1.1, updated_at = NOW()
WHERE category = 'electronics'
RETURNING
    id,
    name,
    OLD.price AS old_price,
    NEW.price AS new_price,
    (NEW.price - OLD.price) AS price_change;

-- DELETE 후 삭제된 값 반환
DELETE FROM cart_items
WHERE cart_id = 123 AND quantity = 0
RETURNING OLD.*;

-- MERGE에서도 활용
MERGE INTO inventory AS target
USING shipment AS source ON target.product_id = source.product_id
WHEN MATCHED THEN
    UPDATE SET quantity = target.quantity + source.quantity
WHEN NOT MATCHED THEN
    INSERT (product_id, quantity) VALUES (source.product_id, source.quantity)
RETURNING
    target.product_id,
    OLD.quantity AS before_qty,
    NEW.quantity AS after_qty;

5. 가상 생성 컬럼 (Virtual Generated Columns)

PostgreSQL 18에서는 가상 생성 컬럼이 기본이 됩니다. 저장 컬럼과 달리 읽기 시점에 계산되어 디스크 공간을 절약합니다.

-- PostgreSQL 18: VIRTUAL 생성 컬럼 (기본값)
CREATE TABLE employees (
    id         SERIAL PRIMARY KEY,
    first_name TEXT NOT NULL,
    last_name  TEXT NOT NULL,
    full_name  TEXT GENERATED ALWAYS AS (first_name || ' ' || last_name) VIRTUAL,
    age        INT,
    age_group  TEXT GENERATED ALWAYS AS (
        CASE
            WHEN age < 30 THEN 'junior'
            WHEN age < 50 THEN 'senior'
            ELSE 'veteran'
        END
    )
);

-- VIRTUAL vs STORED 비교
-- VIRTUAL: 디스크 공간 절약, 읽기 시마다 계산, 인덱스 불가
-- STORED: 디스크 공간 사용, 빠른 읽기, 인덱스 가능

6. 쿼리 최적화 팁 2026

-- 파셜 인덱스 (조건부 인덱스)
CREATE INDEX idx_active_orders ON orders (user_id, created_at)
WHERE status IN ('pending', 'processing'); -- 활성 주문만 인덱스

-- BRIN 인덱스 (시계열 데이터)
CREATE INDEX idx_logs_created_brin ON access_logs
USING BRIN (created_at); -- 매우 작은 크기, 시계열에 효과적

-- 자동 VACUUM 튜닝 (대용량 테이블)
ALTER TABLE orders SET (
    autovacuum_vacuum_scale_factor = 0.01,
    autovacuum_analyze_scale_factor = 0.005
);

-- 연결 풀링 설정 확인 (PgBouncer 권장)
-- max_connections = 100 (PostgreSQL 기본값)
-- pgbouncer pool_size = 20 per database

마치며

PostgreSQL 18은 비동기 I/O로 I/O 바운드 워크로드를 극적으로 개선하고, UUIDv7과 가상 생성 컬럼으로 개발 편의성을 높였습니다. 신규 프로젝트라면 PostgreSQL 18을 바로 적용하는 것을 강력히 권장합니다.