Postgres 18의 비동기 I/O, UUID v7, B-트리 스킵 스캔, 가상 생성 열, OAuth 2.0 등 핵심 업데이트와 성능 향상을 소개합니다.
Postgres 18는 불과 몇 주 안에 출시됩니다! 가장 중요하고 흥미로운 기능들을 정리해 보겠습니다.
Postgres 18에는 비동기 I/O가 추가됩니다. 이는 많은 사용 사례에서 더 빠른 읽기를 의미합니다. 또한 향후 Postgres의 더 큰 성능 개선 계획의 일부로, 그 일환으로 멀티스레딩도 포함될 수 있습니다. 다음 버전들에서 이와 관련한 더 많은 개선을 기대해도 좋습니다.
무엇이 비동기 I/O인가요?
데이터가 이미 공유 메모리 버퍼에 없다면 Postgres는 디스크에서 읽어야 하고, 데이터를 가져오기 위해 I/O가 필요합니다. 동기식 I/O는 디스크에 대한 개별 요청마다 완료될 때까지 기다렸다가 다음 작업을 수행하는 방식입니다. 활동이 많은 바쁜 데이터베이스에서는 이것이 병목이 될 수 있습니다.
Postgres 18은 비동기 I/O를 도입하여, 워커가 유휴 시간을 최적화하고 읽기 작업을 배치로 묶어 시스템 처리량을 개선할 수 있게 합니다. 현재 Postgres는 지능적인 I/O 처리를 운영체제에 의존하며, 순차 스캔에서는 OS나 스토리지의 리드어헤드를 기대하고, 비트맵 인덱스 스캔과 같은 다른 읽기 유형에서는 Linux의 posix_fadvise 같은 기능을 사용합니다. 이러한 작업을 데이터베이스 내부의 비동기 I/O로 옮기면, 데이터베이스 수준에서 배치 연산을 더 예측 가능하고 더 나은 성능으로 수행할 수 있습니다. 또한 비동기 I/O 시스템에 대한 정보를 제공하는 새로운 시스템 뷰 pg_aios가 제공됩니다.
Postgres의 쓰기 작업은 ACID 준수를 위해 계속 동기식으로 진행됩니다.
비동기 I/O가 헷갈린다면, 음식점에서 주문하는 상황을 떠올려 보세요. 동기 모델에서는 주문을 하고 계산대에 서서 음식이 나올 때까지 기다린 뒤에야 다른 일을 할 수 있습니다. 비동기 모델에서는 주문 후 호출 벨을 받고, 벨이 울릴 때까지 자리로 돌아가 친구들과 대화하다가, 벨이 울리면 음식을 가져오면 됩니다.
비동기 I/O의 영향 범위:
기본적으로 Postgres는 io_method = worker를 켭니다. 기본 워커 수는 3이며, CPU 코어가 많은 시스템에서는 이를 늘릴 수 있습니다. 이에 대한 신뢰할 만한 권장값은 아직 보지 못했으니, 조만간 저희 팀의 추가 안내를 기대해 주세요.
Linux 5.1+에서 실행되는 Postgres의 경우 io_uring 시스템 콜을 활용할 수 있으며, 선택적 설정인 io_method = io_uring을 사용하면 별도 프로세스 대신 실제 백엔드 프로세스에서 호출을 수행할 수 있습니다.
이번 버전에서는 UUID가 v7로 업그레이드됩니다.
UUID는 전역적으로 고유한 임의 문자열로, 종종 기본 키로 사용됩니다. 현대 애플리케이션에서 UUID가 인기 있는 이유는 다음과 같습니다.
2024년 중반, 일련의 표준 업데이트를 통해 UUID v7 표준이 나왔습니다. 기존에 Postgres가 네이티브로 지원하던 버전은 UUIDv4였습니다. 하지만 큰 테이블에서의 정렬과 인덱싱은 높은 무작위성 때문에 성능 문제가 있었고, 인덱스 단편화와 낮은 지역성으로 이어졌습니다. UUIDv7은 이러한 정렬/인덱싱 문제를 개선합니다. 여전히 무작위성을 가지지만, 처음 48비트(16진수로 12자)는 타임스탬프이고 나머지 비트는 랜덤입니다. 덕분에 비슷한 시점에 삽입된 데이터는 지역성이 좋아져 인덱스 효율이 향상됩니다.
타임스탬프 부분은 16진수 값(즉, 압축된 10진수)입니다. 예를 들어 01896d6e4a5d6(hex)으로 시작하는 UUID는 2707238289622(decimal)를 나타내며, 이는 1970년 이후 경과한 밀리초 수입니다.
다음은 UUID v7을 사용하는 DDL 예시입니다:
CREATE TABLE user_actions (
action_id UUID PRIMARY KEY DEFAULT uuidv7(),
user_id BIGINT NOT NULL,
action_description TEXT,
action_time TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_action_id ON user_actions (action_id);
Postgres 18에서는 일부 다중 열 B-트리 인덱스에 유의미한 성능 향상이 있습니다.
Postgres에서 어떤 테이블에 (status, date) 순서의 인덱스가 있다면, 이 인덱스는 status와 date를 모두 조회하는 쿼리나, status만 조회하는 쿼리에 사용할 수 있습니다.
Postgres 17 및 이전 버전에서는 같은 인덱스로 date만 조건에 사용하는 쿼리를 처리할 수 없었습니다. 그 열에 대한 별도 인덱스가 없으면 데이터베이스는 전체 테이블 스캔 후 필터링으로 처리해야 했습니다.
Postgres 18에서는 많은 경우 date만을 사용하는 쿼리에도 이 다중 열 인덱스를 자동으로 사용할 수 있습니다. 이를 스킵 스캔(skip scan)이라 하며, 인덱스의 일부 구간을 "건너뛰는" 방식입니다.
이 최적화는 조건절에서 선행 열을 사용하지 않으면서, 생략된 선행 열의 카디널리티(서로 다른 값의 수)가 낮을 때 동작합니다. 동작 방식은 다음과 같습니다.
예를 들어, status와 date 열을 가진 sales 테이블이 있고, 다음과 같은 다중 열 인덱스가 있다고 합시다.
CREATE INDEX idx_status_date
ON sales (status, date);
예시 쿼리는 where 절에 status가 포함되지 않을 수 있습니다.
SELECT * FROM sales
WHERE date = '2025-01-01';
실행 계획만으로는 이것이 스킵 스캔인지 알 수 없으므로, 다음과 같이 인덱스 조건이 보이는 일반적인 인덱스 스캔으로 나타납니다.
QUERY PLAN
-------------------------------------------------------------
Index Only Scan using idx_status_date on sales (cost=0.29..21.54 rows=4 width=8)
Index Cond: (date = '2025-01-01'::date)
(2 rows)
18 이전에는 인덱스의 선행 열이 조건에 포함되지 않았기 때문에 전체 테이블 스캔이 수행되었겠지만, 스킵 스캔 덕분에 이제 동일한 인덱스를 사용한 인덱스 스캔이 가능합니다.
Postgres 18에서는 status의 카디널리티가 낮아 값의 가짓수가 적으므로 복합 인덱스 스캔을 수행할 수 있습니다. 이 최적화는 = 연산자를 사용하는 쿼리에만 동작하며, 부등식이나 범위 조건에는 적용되지 않습니다.
이 모든 것은 Postgres 플래너 내부에서 자동으로 이뤄지므로 별도로 켤 필요가 없습니다. 필터와 조건이 자주 바뀌고 기존 인덱스와 반드시 일치하지 않는 분석용 워크로드에 특히 도움이 됩니다.
스킵 스캔을 사용할지 여부는 테이블 통계와 생략된 열의 고유값 개수 등을 바탕으로 쿼리 플래너가 판단합니다.
PostgreSQL 18은 가상(virtual) 생성 열을 도입합니다. 이전에는 생성 열이 항상 디스크에 저장되었고, 이는 생성 열의 값이 INSERT나 UPDATE 시점에 계산되어 약간의 쓰기 오버헤드가 발생함을 의미했습니다.
PostgreSQL 18에서는 가상 생성 열이 생성 열의 기본 유형이 되었습니다. 생성 열을 정의할 때 STORED를 명시하지 않으면, 가상 생성 열로 생성됩니다.
CREATE TABLE user_profiles (
user_id SERIAL PRIMARY KEY,
settings JSONB,
username VARCHAR(100) GENERATED ALWAYS AS (settings ->> 'username') VIRTUAL
);
JSON 데이터를 사용하는 분들에겐 아주 반가운 업데이트입니다. 쿼리를 단순화할 수 있고, 데이터 변경이나 정규화를 필요할 때 즉시(on-the-fly) 수행할 수 있습니다.
가상 생성 열은 디스크에 저장되지 않기 때문에 인덱싱할 수 없다는 점에 유의하세요. JSONB를 인덱싱하려면 STORED 생성 열을 사용하거나 표현식 인덱스를 사용하세요.
Okta, Keycloak 등 관리형 인증 서비스를 사용하는 분들께 희소식입니다. 이제 Postgres가 OAuth 2.0과 호환됩니다. 이는 기본 호스트 기반 인증 설정 파일(pg_hba.conf)에서 지정합니다.
OAuth 시스템은 베어러 토큰을 사용합니다. 클라이언트 애플리케이션은 비밀번호 대신 토큰을 제시해 신원을 증명합니다. 토큰은 불투명(opaque)한 문자열이며, 그 형식은 권한 부여 서버가 결정합니다. 이 기능을 통해 데이터베이스에 비밀번호를 저장할 필요가 없어집니다. 또한 외부 ID 제공자가 관리하는 다중 요소 인증(MFA), 싱글 사인온(SSO)과 같은 더 강력한 보안 조치를 활용할 수 있게 됩니다.
Postgres 18에는 200명 이상의 기여자가 만든 무려 3,000건의 커밋이 포함되어 있습니다. 이들 중 상당수는 기능이지만, 겉으로 드러나지 않는 쿼리 플래너와 시스템의 다른 부분에도 수많은 추가와 최적화가 이루어졌습니다. 선택 기능을 사용하지 않더라도 성능 향상(음... 비동기 I/O는 정말 큽니다), 버그 수정, 보안 패치 덕분에 정기적으로 업그레이드하는 것은 언제나 좋은 선택입니다.