LLM WikiAccess-protected knowledge portal
← 스터디 홈
72편 · 약 17분

Dolt 2.0: Prolly Tree와 Archive 포맷으로 SQL 데이터베이스에 Git-style 버전 관리를 넣은 방법

SQL 데이터베이스에 버전 관리가 필요한 이유

스키마 변경을 잘못 적용한 이후 "롤백해야 한다"는 상황을 한 번쯤 경험한 DBA는 안다. MySQL이나 PostgreSQL에서 롤백이란 백업에서 복원하거나, binlog를 역방향으로 재생하거나, gh-ost를 다시 실행하는 것이다. 어느 방법이든 시간이 걸리고, 두 시점 사이의 데이터를 어떻게 처리할지 결정해야 한다.

Git은 소스 코드에서 이 문제를 다르게 접근한다. 모든 커밋은 스냅샷이고, 브랜치는 비용이 없으며, diff는 즉각적이다. Dolt는 이 패러다임을 관계형 데이터베이스에 그대로 가져온 MySQL 호환 데이터베이스다.

Dolt는 2026년 5월 11일 2.0을 출시했다. 핵심 변화는 세 가지다.

  1. Archive 포맷 — 새 온디스크 포맷으로 스토리지 30~50% 추가 감소
  2. 자동 가비지 컬렉션 — 기본 활성화, 별도 GC 관리 불필요
  3. Adaptive Storage — TOAST 타입 지원으로 TEXT·JSON·BLOB 대형 값 최적화
  4. 벡터 지원(베타) — MariaDB Vector 타입, 버전 관리되는 유일한 벡터 DB

Prolly Tree: 버전 관리 가능한 스토리지 엔진의 핵심

B-tree의 문제

표준 B-tree는 제자리 갱신(in-place update)을 한다. 페이지를 열고, 값을 바꾸고, 다시 닫는다. 이전 상태는 WAL에만 남는다. 두 버전을 비교하려면 WAL을 뒤지거나 별도 스냅샷이 필요하다. 브랜칭이란 개념 자체가 없다.

Prolly Tree: 확률적 B-tree

Prolly Tree(Probabilistic B-tree)는 Noms 팀이 데이터베이스 버전 관리를 위해 설계한 콘텐츠 주소 기반 트리다.

핵심 속성은 두 가지다.

콘텐츠 주소(content address): 각 내부 노드는 자신의 내용을 해시한 값으로 식별된다. 같은 데이터는 항상 같은 해시를 갖는다. 두 트리의 루트 해시가 같으면 내용이 완전히 동일하다는 것이 보장된다.

확률적 청킹(probabilistic chunking): 노드 경계를 어디서 자를지는 내용의 롤링 해시(rolling hash)가 결정한다. 롤링 해시 값이 특정 임계값 이하로 떨어지면 새 청크가 시작된다. 임계값은 평균 4KB 청크를 만들도록 조정된다. 이 방식은 삽입 순서와 무관하게 동일한 데이터라면 항상 동일한 청크 경계가 생긴다(History Independence)는 성질을 보장한다.

Prolly Tree — 콘텐츠 주소 기반 구조적 공유 버전 A (커밋 abc123) Root A hash: 0x4a2f... Internal A1 hash: 0x7b3e... Internal C hash: 0x2c8a... Leaf A1 0x1a0c... Leaf A2 0x9f4d... Leaf S 0x5e7b... ★ 버전 B (커밋 def456, 일부 변경) Root B hash: 0x8d1c... (다름) Internal B1 hash: 0x3d9f... (새) Internal C hash: 0x2c8a... ★ Leaf B1 0xc4a1... (변경) Leaf A2 0x9f4d... (공유) Leaf S 0x5e7b... ★ ★ 공유 노드: 두 버전에서 동일한 해시 → 한 번만 저장 diff 알고리즘: 루트 해시 비교 → 다른 서브트리만 재귀 탐색 Root A(0x4a2f) ≠ Root B(0x8d1c) → 하위 탐색 Internal C(0x2c8a) = Internal C(0x2c8a) → 탐색 중단 (같음) Internal A1(0x7b3e) ≠ Internal B1(0x3d9f) → 리프 비교 결과: O(변경된 행 수) — 변경된 부분만 탐색, DB 크기와 무관
Prolly Tree 구조와 버전 간 공유

구조적 공유(Structural Sharing)

값이 변경될 때 Prolly Tree는 기존 노드를 수정하지 않는다. 변경된 노드만 새로 만들고, 변경되지 않은 서브트리는 같은 해시 포인터를 재사용한다. 루트 해시만 바뀌면 새 버전이 완성된다. 브랜치 생성은 루트 해시를 하나 더 기록하는 일이므로 비용이 거의 없다.


Dolt 2.0: Archive 포맷과 스토리지 개선

기존 청크 스토어의 한계

Dolt 1.x까지 사용한 청크 스토어는 Prolly Tree 노드 하나가 하나의 청크로 저장된다. 히스토리가 깊어질수록, 특히 서로 다른 버전에서 비슷한 데이터를 많이 갖는 경우, 저장된 청크는 내용이 거의 같은데도 각각 별도로 존재한다.

Archive 포맷

Dolt 2.0의 archive 포맷은 청크들을 사전 기반 압축(dictionary compression)으로 묶는다. 비슷한 청크들을 그룹으로 모아 사전을 만들고, 각 청크는 사전 대비 델타만 저장한다. 여기에 zStd 압축을 추가해 기존 대비 30~50% 스토리지 절감이 가능하다.

Archive 포맷은 Dolt 2.0에서 기본값이며, 기존 Dolt 1.x 데이터베이스는 dolt archive 명령으로 변환할 수 있다.

자동 가비지 컬렉션(AutoGC)

Dolt 1.x에서는 삭제된 커밋이나 브랜치의 데이터가 디스크에 남아 주기적으로 dolt gc를 실행해야 했다. Dolt 2.0부터 AutoGC가 기본 활성화되어 대부분의 사용자는 GC를 신경 쓰지 않아도 된다.

Adaptive Storage (TOAST)

TEXT·JSON·BLOB 같은 대형 값을 효율적으로 처리하는 Adaptive Storage가 추가됐다. PostgreSQL의 TOAST(The Oversized-Attribute Storage Technique)와 유사하게, 큰 값은 별도 블록에 저장하고 메인 행은 포인터만 갖는다. 이로 인해 넓은 행이나 JSON 집약적 스키마에서 쿼리 성능이 개선됐다.

성능 벤치마크

Dolt 2.0 팀은 MySQL 8.0과의 비교에서 쓰기 13% 빠름, 읽기 5% 빠름을 달성했다고 발표했다. Dolt 1.x 초창기에는 MySQL 대비 2~3배 느렸음을 감안하면, 버전 관리 오버헤드를 거의 없애는 수준까지 최적화된 것이다.


SQL로 버전 관리하기

Dolt는 MySQL 프로토콜을 완전히 지원하므로 기존 MySQL 클라이언트로 접속할 수 있다. 버전 관리 기능은 시스템 테이블과 함수, 저장 프로시저로 노출된다.

핵심 SQL 인터페이스

-- 브랜치 목록 조회
SELECT * FROM dolt_branches;

-- 새 브랜치 생성
CALL dolt_branch('feature/new-schema');

-- 브랜치 전환
CALL dolt_checkout('feature/new-schema');

-- 변경 내용 스테이징 및 커밋
CALL dolt_add('orders');
CALL dolt_commit('-m', 'Add status column to orders');

-- 두 커밋 사이의 diff 조회
SELECT * FROM dolt_diff_orders
WHERE from_commit = 'abc123' AND to_commit = 'def456';

-- 특정 시점의 데이터 조회
SELECT * FROM orders AS OF 'feature/new-schema';
-- 또는
SELECT * FROM `main/orders` AS OF '2026-04-01';

-- 머지
CALL dolt_merge('feature/new-schema');

-- 커밋 히스토리
SELECT commit_hash, committer, date, message
FROM dolt_log
ORDER BY date DESC
LIMIT 10;

충돌 해결

Dolt의 3-way merge는 공통 조상을 기준으로 양쪽 브랜치의 변경을 비교한다. 같은 행을 양쪽에서 수정했을 때는 dolt_conflicts 테이블에 충돌이 기록되고, 사용자가 SQL로 해결한다.

-- 충돌 확인
SELECT * FROM dolt_conflicts_orders;

-- 우리 버전으로 해결
UPDATE dolt_conflicts_orders
SET resolved = 'ours'
WHERE base_status IS NULL AND ours_status IS NOT NULL;

-- 충돌 해결 커밋
CALL dolt_commit('-m', 'Resolve merge conflicts');

벡터 지원 (베타)

Dolt 2.0은 MariaDB Vector 타입을 채택해 버전 관리되는 벡터 인덱스를 제공한다. 이는 벡터를 버전 관리하는 유일한 데이터베이스라고 팀은 주장한다. ML 모델의 임베딩을 커밋 단위로 추적하거나 롤백하는 시나리오에 유용하다.

CREATE TABLE product_embeddings (
    id INT PRIMARY KEY,
    name TEXT,
    embedding VECTOR(1536)
);

-- 특정 커밋 시점의 임베딩 조회
SELECT * FROM product_embeddings AS OF 'v1.2-baseline';

언제 Dolt를 사용할 것인가

적합한 사례
ML 학습 데이터 버전 관리 (실험별 브랜치)
규정 감사 데이터 — 특정 시점 데이터 재현 필요
데이터 파이프라인 품질 검증 (PR 기반 리뷰)
스키마 변경 안전 테스트 (브랜치에서 DDL 검증)
멀티팀 데이터셋 협업 (fork/merge)
제한 사항
MySQL의 모든 기능 미지원 (일부 함수·플러그인)
초고빈도 쓰기 OLTP (버전 관리 오버헤드)
매우 큰 단일 테이블 — Prolly Tree 깊이 증가
벡터 지원은 베타 — 프로덕션 신중 검토 필요
Dolt 적합 사례와 한계

운영 체크리스트

  • [ ] Dolt 2.0 설치 및 MySQL 클라이언트 연결 확인 (dolt sql-server 기동)
  • [ ] 기존 Dolt 1.x DB를 archive 포맷으로 변환: dolt archive
  • [ ] AutoGC 동작 확인: 오래된 브랜치 삭제 후 디스크 사용량 모니터링
  • [ ] 브랜치 전략 설계: main (프로덕션), staging, 기능별 브랜치
  • [ ] dolt_diff_* 뷰로 PR 리뷰 워크플로우 자동화 스크립트 작성
  • [ ] AS OF 구문으로 특정 커밋 시점 데이터 재현 테스트
  • [ ] DoltHub 또는 자체 호스팅 저장소와 dolt push/pull 연동 설정
  • [ ] 충돌 해결 절차(dolt_conflicts_*) 팀 문서화

References