PostgreSQL 17 — 성능과 기능의 대폭 향상

PostgreSQL 17은 JSON 처리, 병렬 쿼리 성능, 논리적 복제(Logical Replication) 등 여러 핵심 영역에서 대규모 개선이 이루어진 버전입니다. 특히 복잡한 쿼리 시나리오에서 기존 버전 대비 최대 35% 향상된 병렬 쿼리 성능이 벤치마크로 확인되었습니다. 이 글에서는 실무에서 즉시 활용 가능한 신기능들을 SQL 코드와 함께 상세히 정리합니다.

1. JSON_TABLE — JSON을 관계형 테이블로 변환

PostgreSQL 17의 가장 주목할 기능 중 하나입니다. JSON 데이터를 표준 SQL의 FROM 절에서 직접 테이블처럼 다룰 수 있어, 복잡한 JSON 파싱 로직을 대폭 단순화합니다.

-- API 응답 JSON을 테이블로 변환
SELECT jt.*
FROM json_table(
  '{"orders": [{"id": 1, "product": "노트북", "price": 1200000, "status": "shipped"}, {"id": 2, "product": "마우스", "price": 45000, "status": "pending"}]}'::jsonb,
  '$.orders[*]'
  COLUMNS (
    order_id   INTEGER PATH '$.id',
    product    TEXT    PATH '$.product',
    price      INTEGER PATH '$.price',
    status     TEXT    PATH '$.status'
  )
) AS jt;

-- 실제 테이블의 JSONB 컬럼에 적용
CREATE TABLE api_logs (
  id SERIAL PRIMARY KEY,
  payload JSONB,
  created_at TIMESTAMP DEFAULT NOW()
);

SELECT log.id, jt.event_type, jt.user_id, jt.action
FROM api_logs log,
     JSON_TABLE(log.payload, '$.events[*]'
       COLUMNS (
         event_type TEXT   PATH '$.type',
         user_id    BIGINT PATH '$.userId',
         action     TEXT   PATH '$.action'
       )
     ) AS jt
WHERE log.created_at > NOW() - INTERVAL '7 days';

2. IN 절 B-tree 인덱스 최적화

PostgreSQL 17은 IN 절을 사용하는 쿼리에서 B-tree 인덱스 활용을 개선했습니다. 기존에는 일부 IN 절이 Index Scan 대신 Seq Scan으로 처리되던 문제가 있었습니다.

-- 테스트 테이블 생성
CREATE TABLE orders (
  id BIGSERIAL PRIMARY KEY,
  user_id BIGINT NOT NULL,
  status VARCHAR(20) NOT NULL,
  amount DECIMAL(12,2),
  created_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_orders_composite ON orders(user_id, status, created_at);

-- PostgreSQL 17에서 효율적으로 개선된 IN 절 처리
EXPLAIN ANALYZE
SELECT id, amount, created_at
FROM orders
WHERE user_id IN (101, 205, 347, 891, 1024)
  AND status IN ('pending', 'processing');

-- IS NOT NULL 개선 (약 37% 빠름)
EXPLAIN ANALYZE
SELECT COUNT(*) FROM orders WHERE amount IS NOT NULL;

3. WAL Summarizer와 논리적 복제 개선

-- 논리적 복제 — PostgreSQL 17 신기능: failover 지원
CREATE SUBSCRIPTION my_sub
    CONNECTION 'host=primary port=5432 dbname=mydb'
    PUBLICATION my_pub
    WITH (failover = true);

-- 슬롯 동기화 확인
SELECT slot_name, plugin, slot_type, active, failover
FROM pg_replication_slots;

4. 쿼리 성능 최적화 실전 기법

EXPLAIN ANALYZE 활용

-- 상세 실행 계획 분석
EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)
SELECT u.name, COUNT(o.id) as order_count, SUM(o.amount) as total_amount
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.created_at BETWEEN '2026-01-01' AND '2026-03-31'
  AND o.status = 'completed'
GROUP BY u.id, u.name
HAVING COUNT(o.id) >= 5
ORDER BY total_amount DESC
LIMIT 20;

-- 느린 쿼리 자동 추적
ALTER SYSTEM SET log_min_duration_statement = '1000';
SELECT pg_reload_conf();

-- pg_stat_statements로 상위 느린 쿼리 확인
SELECT query, calls, mean_exec_time, total_exec_time
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;

부분 인덱스와 표현식 인덱스

-- 부분 인덱스: 특정 조건의 행만 인덱싱
CREATE INDEX idx_pending_orders
    ON orders(created_at, user_id)
    WHERE status = 'pending';

-- 표현식 인덱스: 함수 결과를 인덱싱
CREATE INDEX idx_lower_email
    ON users(LOWER(email));

SELECT * FROM users WHERE LOWER(email) = 'user@example.com';

-- BRIN 인덱스: 대용량 시계열 데이터
CREATE INDEX idx_logs_created_brin
    ON system_logs USING BRIN (created_at)
    WITH (pages_per_range = 128);

5. pgvector — PostgreSQL에서의 벡터 검색

AI 임베딩을 데이터베이스에서 직접 처리하는 수요가 급증하면서, pgvector 확장이 PostgreSQL 생태계의 핵심 도구로 자리잡았습니다.

-- pgvector 확장 설치
CREATE EXTENSION IF NOT EXISTS vector;

-- 벡터 컬럼을 가진 상품 테이블
CREATE TABLE products (
  id BIGSERIAL PRIMARY KEY,
  name TEXT NOT NULL,
  description TEXT,
  embedding vector(1536),
  created_at TIMESTAMP DEFAULT NOW()
);

-- IVFFlat 인덱스 생성
CREATE INDEX idx_products_embedding
    ON products USING ivfflat (embedding vector_cosine_ops)
    WITH (lists = 100);

-- 유사 상품 검색 (코사인 유사도)
SELECT id, name,
       1 - (embedding <=> query.vec) AS similarity
FROM products,
     (SELECT embedding AS vec FROM products WHERE id = 1) query
WHERE id != 1
ORDER BY embedding <=> query.vec
LIMIT 10;

-- 하이브리드 검색: 벡터 유사도 + 키워드 필터
SELECT p.id, p.name,
       1 - (p.embedding <=> $1::vector) AS score
FROM products p
WHERE p.category = 'electronics'
  AND p.price BETWEEN 100000 AND 500000
ORDER BY p.embedding <=> $1::vector
LIMIT 20;

6. 파티셔닝 전략과 자동 VACUUM

-- 범위 파티셔닝
CREATE TABLE logs (
  id BIGSERIAL,
  level VARCHAR(10),
  message TEXT,
  created_at TIMESTAMP NOT NULL
) PARTITION BY RANGE (created_at);

CREATE TABLE logs_2026_01 PARTITION OF logs
    FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');

-- 오래된 파티션 삭제 (DELETE보다 100배 이상 빠름)
DROP TABLE logs_2025_01;

-- VACUUM 설정 최적화
ALTER TABLE orders SET (
  autovacuum_vacuum_scale_factor = 0.01,
  autovacuum_analyze_scale_factor = 0.005
);

PostgreSQL 17은 단순한 버전 업그레이드를 넘어, JSON 처리의 패러다임 변화와 AI 시대의 벡터 검색 수요를 동시에 충족시키는 데이터베이스로 발전했습니다. 특히 JSON_TABLE의 도입은 반정형 데이터를 다루는 워크로드에서 코드 복잡성을 크게 줄여줄 것으로 기대됩니다.