PostgreSQL 17이 2024년 9월 정식 출시되면서 JSON 처리, 병렬 쿼리, 논리 복제 등 핵심 영역에서 중요한 개선이 이루어졌다. 이 글에서는 PostgreSQL 17의 주요 신기능을 실제 SQL 예제와 함께 상세히 다룬다.

1. SQL/JSON 표준 함수 강화

PostgreSQL 17에서는 SQL:2023 표준의 JSON 함수들이 대폭 추가되었다. 기존 PostgreSQL 전용 연산자 대신 표준 SQL 문법으로 JSON을 처리할 수 있다.

JSON_TABLE - JSON을 관계형 테이블로 변환

-- 주문 데이터가 JSON으로 저장된 경우
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    data JSONB NOT NULL
);

INSERT INTO orders (data) VALUES ('
{
    "orderId": "ORD-001",
    "customer": "김개발",
    "items": [
        {"name": "키보드", "price": 89000, "qty": 1},
        {"name": "마우스", "price": 45000, "qty": 2},
        {"name": "모니터", "price": 350000, "qty": 1}
    ],
    "shipping": {"method": "express", "cost": 3000}
}');

-- JSON_TABLE로 주문 항목을 행으로 변환
SELECT jt.*
FROM orders,
    JSON_TABLE(
        data, '$.items[*]'
        COLUMNS (
            item_name   TEXT    PATH '$.name',
            unit_price  INTEGER PATH '$.price',
            quantity    INTEGER PATH '$.qty'
        )
    ) AS jt;

-- 결과:
-- item_name | unit_price | quantity
-- 키보드     |      89000 |        1
-- 마우스     |      45000 |        2
-- 모니터     |     350000 |        1

JSON_QUERY와 JSON_VALUE

-- JSON_VALUE: 스칼라 값 추출 (타입 캐스팅 포함)
SELECT 
    JSON_VALUE(data, '$.customer' RETURNING TEXT) AS customer,
    JSON_VALUE(data, '$.shipping.cost' RETURNING INTEGER) AS shipping_cost,
    JSON_VALUE(data, '$.orderId') AS order_id
FROM orders;

-- JSON_QUERY: JSON 객체/배열 추출
SELECT 
    JSON_QUERY(data, '$.shipping') AS shipping_info,
    JSON_QUERY(data, '$.items' WITH WRAPPER) AS items_array
FROM orders;

-- JSON_EXISTS: 경로 존재 여부 확인
SELECT *
FROM orders
WHERE JSON_EXISTS(data, '$.items[*] ? (@.price > 100000)');

2. 증분 정렬 (Incremental Sort) 개선

이미 부분적으로 정렬된 데이터에 대해 추가 정렬을 효율적으로 수행한다.

-- 복합 인덱스가 일부 컬럼만 커버하는 경우
CREATE INDEX idx_orders_date ON orders_table (order_date);

EXPLAIN ANALYZE
SELECT * FROM orders_table
ORDER BY order_date, amount DESC
LIMIT 100;
-- Incremental Sort 활용으로 전체 정렬 대비 성능 향상

3. 병렬 쿼리 성능 향상

-- PostgreSQL 17에서 UNION ALL의 병렬 처리 지원
EXPLAIN ANALYZE
SELECT product_id, SUM(quantity) as total_qty
FROM (
    SELECT product_id, quantity FROM sales_2024
    UNION ALL
    SELECT product_id, quantity FROM sales_2025
) combined
GROUP BY product_id
ORDER BY total_qty DESC
LIMIT 20;

-- 병렬 설정 조정
SHOW max_parallel_workers_per_gather;  -- 기본값: 2
SET max_parallel_workers_per_gather = 4;

-- 병렬 VACUUM도 개선
VACUUM (PARALLEL 4) large_table;
ANALYZE large_table;

4. 논리 복제 (Logical Replication) 강화

-- PostgreSQL 17: 시퀀스 복제 지원
CREATE PUBLICATION my_pub FOR ALL TABLES, ALL SEQUENCES;

-- 구독자 측
CREATE SUBSCRIPTION my_sub
    CONNECTION 'host=primary dbname=mydb user=repl'
    PUBLICATION my_pub
    WITH (
        copy_data = true,
        origin = 'none',
        failover = true       -- 17 신기능: 페일오버 지원
    );

-- 복제 상태 모니터링
SELECT slot_name, slot_type, active, 
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) as lag
FROM pg_replication_slots;

5. COPY 성능 대폭 향상

-- PostgreSQL 17에서 COPY 성능 최대 2배 향상
COPY products FROM '/data/products.csv' 
WITH (
    FORMAT csv, 
    HEADER true, 
    DELIMITER ',',
    NULL 'NULL'
);

-- 데이터 내보내기
COPY (
    SELECT u.name, COUNT(o.id) as order_count, SUM(o.amount) as total
    FROM users u
    LEFT JOIN orders o ON u.id = o.user_id
    GROUP BY u.name
    ORDER BY total DESC
) TO '/tmp/user_summary.csv' WITH (FORMAT csv, HEADER true);

6. 쿼리 최적화 실전 팁

-- 커버링 인덱스로 Index Only Scan
CREATE INDEX idx_users_covering 
    ON users (email) INCLUDE (name, created_at);

-- 부분 인덱스 (Partial Index)
CREATE INDEX idx_orders_pending 
    ON orders (created_at) 
    WHERE status = 'pending';

-- 표현식 인덱스
CREATE INDEX idx_users_lower_email 
    ON users (LOWER(email));

-- BRIN 인덱스 - 시계열 데이터에 최적
CREATE INDEX idx_logs_brin 
    ON access_logs USING BRIN (created_at)
    WITH (pages_per_range = 32);

7. pg_stat_statements 활용

-- 느린 쿼리 TOP 10
SELECT 
    LEFT(query, 80) AS query_preview,
    calls,
    ROUND(total_exec_time::numeric, 2) AS total_ms,
    ROUND(mean_exec_time::numeric, 2) AS avg_ms,
    rows
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;

-- 캐시 적중률 확인
SELECT 
    LEFT(query, 80) AS query_preview,
    shared_blks_read + shared_blks_hit AS total_blocks,
    ROUND(100.0 * shared_blks_hit / 
        NULLIF(shared_blks_read + shared_blks_hit, 0), 2) AS cache_hit_pct
FROM pg_stat_statements
ORDER BY shared_blks_read DESC
LIMIT 10;

PostgreSQL 17은 JSON 표준 함수 지원과 논리 복제 강화를 통해 현대적인 애플리케이션 요구사항을 더 잘 충족한다. 특히 JSON_TABLE 함수는 JSON 데이터를 관계형 쿼리로 다루는 큰 진전이다. 업그레이드를 적극 권장한다.