본문으로 건너뛰기

5.3 Dolt와 Doltgres

지금까지 살펴본 접근은 모두 "PostgreSQL은 그대로 두고 아래 계층에서 사본을 저렴하게 만드는" 방식이었습니다. Dolt는 반대로 저장 구조 자체를 Git처럼 구성한 데이터베이스입니다. 테이블은 Prolly tree(확률적 B-tree)로 저장되며, commit은 tree의 루트 해시를 가리킵니다. 따라서 branch는 포인터 하나로 표현되며, 변경되지 않은 행은 branch 간에 구조적으로 공유됩니다. 그 결과 Dolt에는 Neon과 DBLab에 없는 기능이 생깁니다. branch 간 diffmerge, 충돌 해결입니다.

Dolt는 MySQL 호환 데이터베이스이고, PostgreSQL 호환 버전은 Doltgres(DoltgreSQL)입니다. Doltgres는 2025년 베타를 거쳐 2026년 8월 1.0으로 정식 출시되었습니다. 공식 발표에서는 production 워크로드에 사용할 만한 품질이라고 밝힙니다. 이 노트의 실측은 그 이후에 나온 1.3.1 버전을 기준으로 합니다.

image: dolthub/doltgresql:latest
digest: sha256:6c85cb1f35beabf47f094336a420255130b841b1645f36d79ef046276af36851
버전: Doltgres 1.3.1

요약

  • branch 생성 시간은 데이터 크기와 무관합니다. 이 노트의 실습 환경에서는 30만 행 테이블을 넣은 뒤 branch를 만들어도 0.4초 안에 끝났습니다. 디스크 사용량도 변하지 않았습니다.
  • 버전 관리 명령은 모두 SQL 함수입니다. Doltgres에는 Dolt의 Git 스타일 CLI가 없습니다.
  • branch는 접속 데이터베이스 이름에 데이터베이스/branch 형식으로 지정합니다. 세션에서 dolt_checkout()을 호출하는 방법도 있습니다.
  • 머지 충돌은 트랜잭션 안에서 처리해야 합니다. autocommit 상태에서 충돌이 발생하면 트랜잭션이 롤백됩니다.
  • 1.0 발표 기준으로 SQLLogicTest(약 560만 쿼리) 통과율은 99% 이상입니다. 읽기 지연은 PostgreSQL 대비 약 2.7배이며, 후속 자료에서는 약 2.6배로 나옵니다. 벤치마크 종류와 비교 대상 PostgreSQL 버전이 함께 공개되지는 않았으므로 순서 비교로만 읽습니다.

기동

docker run -d --name doltgres-lab -e DOLTGRES_PASSWORD=password \
-p 55440:5432 dolthub/doltgresql:latest
docker exec -e PGPASSWORD=password doltgres-lab \
psql -h localhost -U postgres -d postgres -c "select version();"

이미지 안에 psql이 들어 있어 호스트에 클라이언트가 없어도 됩니다. 기본 superuser는 postgres이고 비밀번호는 환경변수로 지정합니다.

첫 commit과 branch

create table accounts(id int primary key, owner text, balance int);
insert into accounts values (1,'kim',100),(2,'lee',250),(3,'park',40);
select dolt_add('-A');
select dolt_commit('-m', 'init: accounts');
select dolt_branch('feature');
select name, hash from dolt.branches;
name | hash
---------+----------------------------------
feature | ev57n4i0k4e4nrjcbfepsk5v2udhbp4a
main | ev57n4i0k4e4nrjcbfepsk5v2udhbp4a

두 branch는 같은 commit 해시를 가리킵니다. Git에서 git branch feature를 실행한 직후와 같은 상태입니다. 이 노트의 실습 환경에서 dolt_branch()는 첫 호출에 0.28초가 걸렸습니다. 이후 호출에는 0.1초 안팎이 걸렸습니다.

branch에서 쓰고 main 확인

접속 문자열의 데이터베이스 이름에 branch를 붙이면 해당 세션은 그 branch에서 동작합니다.

docker exec -e PGPASSWORD=password doltgres-lab \
psql -h localhost -U postgres -d 'postgres/feature'
select active_branch(); -- feature
update accounts set balance = balance + 1000 where id = 1;
insert into accounts values (4,'choi',10);
select dolt_commit('-a', '-m', 'feature: bonus + new account');

같은 서버에 -d postgres로 다시 접속하면 main입니다.

postgres=# select * from accounts order by id;
id | owner | balance
----+-------+---------
1 | kim | 100
2 | lee | 250
3 | park | 40

feature에서 변경한 1번 행과 추가한 4번 행은 main에 없습니다. 격리 방식 자체는 Neon branch와 같지만, 다음 단계가 다릅니다.

diff와 머지

select to_id, to_owner, to_balance, from_id, from_balance, diff_type
from dolt_diff('main', 'feature', 'accounts');
to_id | to_owner | to_balance | from_id | from_balance | diff_type
-------+----------+------------+---------+--------------+-----------
1 | kim | 1100 | 1 | 100 | modified
4 | choi | 10 | | | added

dolt_diff_stat('main','feature')는 테이블별 추가, 삭제, 수정 행 수를 반환합니다. 리뷰어는 branch의 변경 내용을 SQL로 확인합니다. 머지는 한 줄이면 됩니다.

select dolt_merge('feature');
-- (d61git9134c015cs81m4r0un06nmn9th,1,0,"merge successful")

이 노트의 실습 환경에서 fast-forward 머지는 0.11초에 끝났습니다. 결과 튜플의 두 번째 값인 1은 fast-forward 여부를 나타냅니다. 세 번째 값인 0은 충돌 수입니다. Neon에는 이 단계가 없습니다. Neon branch에서 검증한 마이그레이션은 사람이 운영 branch에 다시 실행해야 합니다. 반면 Dolt는 데이터 변경까지 머지합니다.

충돌 처리

main과 branch가 같은 행을 서로 다르게 바꾸면 충돌이 발생합니다. 이 노트의 실습 환경에서 autocommit 상태로 dolt_merge()를 호출하자 다음 오류가 발생하고 트랜잭션이 롤백되었습니다.

ERROR: Merge conflict detected, @autocommit transaction rolled back.
@autocommit must be disabled so that merge conflicts can be resolved using
the dolt_conflicts and dolt_schema_conflicts tables before manually committing
the transaction. Alternatively, ... set @@dolt_allow_commit_conflicts = 1

트랜잭션 안에서 다시 실행하면 충돌 테이블을 확인하고 충돌을 해결한 뒤 commit합니다.

begin;
select dolt_merge('conflict'); -- ("",0,1,"conflicts found")
select * from dolt_conflicts; -- accounts | 1
select base_balance, our_balance, their_balance, our_diff_type, their_diff_type
from dolt_conflicts_accounts;
-- 250 | 2 | 1 | modified | modified
select dolt_conflicts_resolve('--ours', 'accounts');
select dolt_commit('-a', '-m', 'merge conflict branch, keep ours');
commit;

dolt_conflicts_<테이블>에는 base, ours, theirs 세 버전이 나란히 표시됩니다. Git의 3-way merge와 같은 정보입니다. 행별로 값을 직접 수정한 뒤 충돌 행을 지우는 방식도 가능합니다.

저장 공간 실측

30만 행 테이블(md5 문자열 payload)을 넣고 commit한 뒤 데이터 디렉터리 크기를 측정했습니다.

단계/var/lib/doltgres 크기
30만 행 적재 + commit 후13,680 KB
branch exp2 생성 후13,680 KB
exp2에서 1행 수정 + commit 후13,692 KB

branch를 생성한 뒤에는 du의 KB 해상도에서 증가가 관측되지 않았습니다. 1행 수정에는 12 KB가 추가되었습니다. branch 참조와 working set 메타데이터 자체는 어딘가에 기록되어야 합니다. 따라서 정확히 0바이트라는 뜻은 아니며, 측정 단위보다 작다는 뜻입니다. 1행 수정에 추가된 12 KB는 Prolly tree에서 변경된 leaf와 그 경로의 내부 노드만 새로 기록되었다는 신호입니다. 이 표는 이 노트의 실습 환경에서 측정한 수치입니다. chunk 크기와 payload 분포에 따라 달라집니다.

실측 중 확인한 호환성 한계

  • 실습 데이터를 하나의 SQL 문으로 만들려고 실행한 아래 형태의 create table ... as select에서 TableCopier only accepts CreateTable or TableNode 오류가 발생했습니다. create tableinsert ... select로 나누면 통과했습니다.
-- 이 형태에서 실패 (Doltgres 1.3.1)
create table big as
select g as id, md5(g::text) as payload from generate_series(1,300000) g;

공식 지원표는 CREATE TABLE AS를 지원으로 표시하고 WITH NO DATA만 미지원으로 적습니다. 따라서 CTAS 전체가 빠진 것이라기보다 이 SELECT 형태에서 발생한 문제로 보입니다. 정확히 판단하려면 재현 SQL과 위 이미지 digest를 함께 남긴 버그 보고가 필요합니다.

  • pg_size_pretty()가 없습니다. 크기는 컨테이너 안에서 du로 확인했습니다.
  • CREATE EXTENSION 구문 자체는 지원하지만, 임의의 네이티브 C 확장을 적용하는 방식은 아닙니다. Doltgres 팀이 Go로 다시 구현한 목록만 동작합니다. 2026년 9월 발표 기준으로 uuid-ossp와 pgvector가 구현되었습니다. PostGIS는 작업 중이고 pgcrypto, citext, hstore가 예정되어 있습니다. pg_stat_statements처럼 목록에 없는 확장을 전제로 한 워크로드는 그대로 옮기지 못합니다.
  • 30만 행 insert ... select와 commit에는 1.3초가 걸렸습니다. 작은 데이터에서는 체감하기 어렵지만, PostgreSQL 대비 두 배 남짓 느린 특성은 큰 배치에서 드러납니다.

어디에 맞는가

1.0 이후 upstream은 production 사용을 표방합니다. 그러나 이미 운영 중인 PostgreSQL OLTP를 호환성 검증 없이 그대로 옮기는 결정은 위험합니다. 확장 목록, 지연 특성, 미지원 구문을 워크로드 기준으로 먼저 확인해야 합니다. 반면 Doltgres가 뚜렷하게 맞는 곳은 데이터 자체가 검토 대상인 환경입니다. 요금표, 코드 테이블, 설정, ML 학습 데이터셋처럼 "누가 언제 무엇을 왜 바꿨는지"가 중요한 참조 데이터가 이에 해당합니다. AI agent가 쓰기를 시도하는 실험 공간도 적합합니다. agent마다 branch를 할당하고 결과를 diff로 검토한 뒤 머지하는 흐름은 Dolt가 2026년에 내세우는 주력 용도입니다. 이는 Neon의 agent용 branching과 겹치는 지점이기도 합니다. 차이는 Neon이 "실패하면 버린다"에 그치지만, Dolt는 "성공하면 합친다"까지 지원한다는 점입니다.

정리

docker rm -f doltgres-lab

연습 문제

  1. 두 branch에서 같은 테이블에 서로 다른 컬럼을 추가한 뒤 머지합니다. schema 충돌이 어떤 테이블에 나타나는지 확인합니다.
  2. dolt_log('main..feature')로 feature에만 있는 commit을 나열합니다.
  3. 100만 행을 적재한 뒤 branch 생성 시간과 디스크 증가량을 이 장의 표와 비교합니다.

참고