PostgreSQL 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

업그레이드 팁

  1. pg_upgrade를 사용하여 인플레이스 업그레이드 수행
  2. AIO 활성화 전 스토리지 시스템이 io_uring을 지원하는지 확인
  3. 기존 UUIDv4 컬럼을 UUIDv7으로 전환할 때는 새 데이터부터 적용하는 것을 권장
  4. Virtual Generated Columns 전환 시 기존 Stored Columns과의 성능 트레이드오프 검토