1986년 발표된 Postgres 원조 설계 목표 여섯 가지를 짚어 보며, 현대 PostgreSQL이 데이터 타입과 확장성, 활성 데이터베이스 기능, WAL 기반 복구와 고가용성, 하드웨어 활용, 관계 모델을 토대로 한 유연성을 통해 이 목표를 어떻게 완벽히 실현했는지 설명한다.
지난주에 시간을 내어 Postgres를 플랫폼으로 발표한 원논문과 1986년의 원래 설계 목표를 차분히 읽어볼 기회가 있었습니다. 선견지명에 압도되었고—그 초기 목표들이 오늘날 사실상 전 세계를 장악해 가는 데이터베이스의 토대를 어떻게 놓았는지 감탄했습니다.
PostgreSQL의 창시자들은 정말 완벽히 해냈습니다. 다양한 비즈니스 사용 사례를 포괄하는 유연한 프레임워크를 제시했고, 30년이 지난 뒤 가장 인기 있는 데이터베이스로 자리 잡는 길을 닦았습니다.
논문은 6가지 프로젝트 목표를 제시합니다:
이제 각각을 현대 Postgres의 기능과 함께 살펴보겠습니다.
Postgres에는 단순한 기록 보관부터 복잡한 데이터 분석에 이르기까지 방대한 비즈니스 사용 사례를 충족하도록 설계된 풍부하고 유연한 네이티브 데이터 타입이 있습니다.
정수형 Numeric 타입인 SMALLINT, INTEGER는 정수에 쓰이고, BIGINT는 사용자의 고유 ID나 기본 키에 적합합니다. NUMERIC, DECIMAL 같은 고정 소수 정밀 타입은 정확한 정밀도가 중요할 때 사용되며, 특히 금액 데이터에 적합합니다. REAL, DOUBLE PRECISION 같은 부동소수점 타입은 절대적 정밀도보다 값의 범위가 중요한 과학·엔지니어링 계산에 유용합니다. 분산 시스템과 보안 URL에는 UUID(Postgres 18의 인덱싱 가능한 UUID)도 사용할 수 있습니다.
문자형 타입인 VARCHAR(n)과 CHAR(n)은 최대 길이(n)까지 가변 길이 텍스트를 저장하며, 실제 텍스트에 필요한 만큼만 저장소를 사용합니다.
날짜/시간 타입 중 DATE는 연-월-일만 저장합니다. TIMESTAMPTZ는 사실상 날짜·시간 타입의 최강자로, 전역 시스템에 쉽게 적용할 수 있습니다.
하지만 이게 전부가 아닙니다. Postgres는 각 사용 사례의 구체적 요구에 맞춘 "맞춤형 데이터 타입"을 손쉽게 만들고, 데이터에 제약을 줄 수 있는 기능을 내장하고 있습니다.
CREATE DOMAIN 사용으로 생일 범위 확인이나 이메일 형식 유효성 같은 값 검사를 지정할 수 있습니다.
-- Postgres 도메인 생성
CREATE DOMAIN date_of_birth AS date
CHECK (value > '1930-01-01'::date);
CREATE DOMAIN valid_email AS text
NOT NULL
CHECK (value ~* '^[A-Za-z0-9._%-]+@[A-Za-z0-9.-]+[.][A-Za-z]+$');
또는 CREATE TYPE을 직접 사용해 복합 타입을 만들 수 있습니다. 예를 들어, 높이·너비·무게를 하나의 필드에 저장하는 새 맞춤형 패키지 치수 타입을 만들 수 있습니다.
-- 복합 타입으로 Postgres 타입 생성
CREATE TYPE physical_package AS (
height numeric,
width numeric,
weight numeric);
ENUM은 미리 정의된 값 집합을 갖는 사용자 정의 타입을 만들게 해줍니다.
-- Postgres enum
CREATE TYPE order_status AS ENUM (
'pending',
'shipped',
'cancelled');
제약 조건은 열거형을 한 단계 더 발전시켜 데이터에 대한 규칙과 제한을 지정할 수 있게 합니다. 예컨대 시작·종료 시간을 갖는 예약에서 다른 필드를 참조하는 CHECK 제약을 추가할 수 있습니다.
-- Postgres CHECK 제약
ALTER TABLE public.reservations
ADD CONSTRAINT start_before_end
CHECK (start_time < end_time);
대부분의 애플리케이션이 자체적으로도 데이터를 제약하지만, Postgres의 엄격하면서도 유연한 타입 시스템 덕분에 타당성 검증의 엄격함과 유연함을 모두 확보할 수 있습니다.
저자들은 단지 데이터 타입만으로는 충분치 않음을 알고 있었습니다—시스템 자체가 확장 가능해야 했죠. 제 생각에 이것이 바로 Postgres의 킬러 기능입니다. 데이터베이스 자체도 탄탄하지만, 확장 생태계의 기발함과 활기는 정말 특별합니다.
예를 들어 PostGIS를 보겠습니다. 이 확장은 지리공간 타입을 저장하기 위해 포인트, 라인, 폴리곤 같은 핵심 데이터 타입을 추가합니다. PostGIS는 수백 개의 함수도 제공합니다. 이제는 이 프로젝트를 중심으로 오픈 소스 지도 제작과 유료 GIS 시스템(예: ESRI)에 필적하는 완전한 오픈 소스 웹 서버까지 아우르는 독자적 생태계가 형성되어 있습니다.
pgvector 확장도 Postgres 확장성의 훌륭한 사례입니다. 이제 Postgres는 임베딩 데이터를 애플리케이션 데이터와 나란히 저장할 수 있습니다. LLM이 여러분의 데이터로부터 임베딩을 만들게 하고, 데이터에서 유사도를 질의할 수 있습니다. 데이터베이스 내부에서 자체 Postgres RAG 시스템을 구축할 수도 있습니다.
-- 두 임베딩 값 간 거리 계산
recipe_1.embedding <=> recipe_2.embedding
데이터 타입과 확장만이 전부가 아닙니다. Postgres의 인덱스 자체도 놀라울 정도로 발전했습니다. GIN(Generalized Inverted Index)과 GiST(Generalized Search Tree)는 각각 위에서 언급한 많은 복합 데이터 타입을 지원하는, 스스로 확장 가능한 인덱싱 프레임워크입니다.
현대의 Postgres 사용자는 데이터베이스가 필요한 작업을 수행하도록 해주는 도구 모음을 사용할 수 있습니다. 트리거 시스템은 한 필드가 변경되면 관련 필드를 손쉽게 갱신합니다.
-- 필드 변경 시 인벤토리 갱신 예시 함수
CREATE OR REPLACE FUNCTION update_inventory_on_sale()
RETURNS TRIGGER AS $$
BEGIN
UPDATE products
SET quantity_on_hand = quantity_on_hand - NEW.quantity_sold
WHERE id = NEW.product_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'No product found with ID %', NEW.product_id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
데이터베이스 외부 이벤트를 위해 Postgres에는 NOTIFY/LISTEN이라는 편리한 메커니즘이 있습니다. 이를 통해 애플리케이션이나 대시보드가 새 주문이 접수되거나 특정 동작이 발생했음을 알 수 있습니다. 이제는 LISTEN/NOTIFY 시스템 이벤트를 WebSocket으로 사용하는 확장도 있습니다.
Postgres의 논리적 복제 역시 ‘활성 데이터베이스’ 아이디어를 잘 활용합니다. PostgreSQL의 논리적 복제는 물리적 블록 단위 복사 대신 개별 데이터 변경을 스트리밍하므로, 주요 버전이 다른 Postgres 간이나 심지어 다른 플랫폼 간에도 데이터를 복제할 수 있습니다. 이 유연성 덕분에 특화된 읽기 전용 레플리카 생성, 여러 데이터베이스를 중앙으로 통합, 무중단 메이저 버전 업그레이드 같은 강력한 사용 사례가 가능합니다.
-- Postgres 논리적 복제 구성
CREATE PUBLICATION user_pub FOR TABLE user_id, forum_posts;
초기의 Postgres 데이터 복구 방식은 커밋 전에 모든 데이터 수정 내용을 디스크 파일에 기록하는 "force-to-disk" 방식에 의존했습니다. 하지만 이 초기 구현은 심각한 성능 문제와 손상 가능성을 안고 있었습니다. 버전 7.1에서 도입된 WAL(Write-Ahead Log)은 변경 내용을 먼저 로그 파일에 기록한 뒤 메인 데이터 파일에 적용하는 방식으로 아키텍처를 바꾸었습니다.
WAL은 Postgres의 뛰어난 백업 및 재해 복구 스토리의 토대입니다. WAL은 증분 백업 생성에 사용되며, 오늘날 많은 이들이 활용하는 시점 복구(Point-in-Time) DR도 가능하게 합니다.
WAL은 Postgres 스트리밍 복제의 기본이기도 하여 고가용성을 가능하게 합니다. 프라이머리는 모든 데이터베이스 변경(삽입, 갱신, 삭제)을 WAL에 기록하고, 이 WAL 레코드를 네트워크를 통해 대기(레플리카) 노드로 스트리밍합니다. 대기 노드는 이 WAL 레코드를 수신해 자신의 데이터베이스 사본에 적용하고, 프라이머리와 동기화를 유지합니다. 비상 상황에서는 Patroni 같은 자동 장애 조치(failover) 도구가 새 프라이머리를 승격할 수 있습니다.
PostgreSQL은 단일 코어 CPU, 메가바이트 단위로 제한된 RAM, 느린 회전식 하드 드라이브가 보편이던 당시의 하드웨어 현실에 맞춰 설계되었습니다. 설계의 최우선은 순수 성능보다 정확성과 데이터 내구성이었습니다. PostgreSQL은 안정성과 ACID 준수로 전설적 명성을 쌓았고, 덜 신뢰할 수 있는 하드웨어에서도 데이터를 안전하게 지켰습니다.
오늘날로 빨리 넘어오면, PostgreSQL은 수십 개 CPU 코어, 초고속 NVMe 스토리지 수 테라바이트, 방대한 RAM(이제는 무려 0.5TB RAM도 가능합니다)을 갖춘 하드웨어에서 동작합니다. PostgreSQL은 최근 병렬 쿼리 실행을 도입하여 복잡한 쿼리를 분할·동시에 실행하고 마지막에 결과를 모읍니다. 또한 현대의 PostgreSQL은 잠금 메커니즘, 커넥션 풀링, 복제 기능을 크게 개선하여, 단일 서버의 견고한 데이터베이스에서 수평 확장과 현대 인터넷의 방대한 동시 작업 부하를 처리하는 고성능 엔진으로 진화했습니다.
오늘날 Postgres는 아직 최신 CPU의 멀티스레딩을 지원하지 않지만, 이는 머지않아 다가올 것이며 Postgres 18에는 비동기 I/O가 막 추가되었습니다.
2000년대 후반과 2010년대 초반 NoSQL 운동이 절정이었을 때, 관계형 데이터베이스는 과거의 유물이라는 이야기가 흔했습니다. 빅데이터와 비정형 데이터의 부상으로, 이 낡은 모델은 곧 폐기될 것처럼 보였죠.
Postgres는 늘 그래왔듯 본연의 강점—유연한 데이터 타이핑—을 고수하면서, NoSQL의 아이디어 일부를 받아들였습니다. Postgres는 JSON 데이터 타입을 도입했고, 이후 바이너리이자 인덱싱 가능한 JSONB 타입을 추가했습니다. 이로써 애플리케이션은 스키마리스 API 기반 JSON 데이터를 관계형 데이터베이스에 직접 저장하고, 풍부한 연산자와 함수로 효율적으로 질의할 수 있게 됐습니다. json_table 같은 기능을 통해 배열과 전통적 테이블 간을 자유롭게 오갈 수도 있습니다.
요즘 Postgres 세계의 최신 혁신은, 비정형 플랫 파일과 Postgres를 직접 연결하는 기술의 채택으로 보입니다. pg_duckdb, pg_mooncake, Crunchy Data Warehouse 같은 프로젝트는 사용자 정의 확장을 활용해, 원격 오브젝트 스토어의 데이터 레이크에 상주하는 CSV, Parquet, Iceberg 파일을 직접 다룹니다. 데이터가 다른 위치로 추상화되어 있더라도, Postgres의 관계형 모델은 여전히 유효하고 효율적이며 신뢰받습니다.
Postgres의 유연성 덕분에, 외래 키와 JOIN을 갖춘 완전 정규화된 관계형 스키마를 가지면서도 인덱싱된 JSONB 문서와 완전한 공간 기하(geometry)를 함께 보유할 수 있습니다. 우리는 AI, 과학, 연구가—처음 만들어질 당시에는 오늘의 세상을 상상도 못했을—데이터베이스에 의해 뒷받침되는 시대에 살고 있습니다. Postgres는 여전히 여기 있습니다.
이 초기 목표들은 프로젝트에 지대한 영향을 끼쳤습니다. 성장하는 비즈니스 환경에서 복잡성과 유연성을 허용하면서, 개별 사용 사례에 맞춰 쉽게 바꿀 수 있게 했습니다. 또한 Postgres의 분산을 한층 쉽게 만드는 하드웨어(그리고 클라우드) 기술에도 대비해 왔습니다.