- Graft는 모든 클라이언트에 전체 변경 로그를 보내는 대신, 물리 복제의 단순성과 논리 복제의 효율을 결합하려는 오픈소스 트랜잭션 스토리지 엔진임
- 고정 크기 Page로 구성된 Volume을 Snapshot 단위로 다루며, 서버는 실제 데이터가 아니라 변경된 페이지 인덱스의 압축 비트셋인
graft를 내려줌 - 클라이언트는
graft를 보고 필요한 페이지만 가져오며, Leap 기반 프리패칭, 도메인별 프리패칭, 전체 변경분을 가져오는 선제 가져오기를 선택할 수 있음 - 객체 스토리지와 엣지 서버를 활용해 브라우저, 모바일 앱, 서버리스 함수, 임베디드 환경 같은 제약 환경에서도 부분 복제를 목표로 함
- 일관성 모델은 Serializable Snapshot Isolation이며, 오래된 Snapshot 기반 커밋은 거부하고 클라이언트가 reset/replay, merge, Volume fork 중 하나로 처리함
Graft가 풀려는 복제 문제
- 부분 복제는 필요한 데이터만 동기화하면 쉬워 보이지만, 실제 설계에서는 복제 방식마다 뚜렷한 대가가 있음
- 논리 복제는 모든 변경을 정밀하게 추적하지만 강한 일관성을 복잡하게 만듦
- 물리 복제는 그 복잡성을 피하지만, 나중에 버릴 변경까지 모두 동기화해야 함
- Graft는 지연 동기화, 부분 복제, 강한 일관성, 수평 확장성, 객체 스토리지 내구성을 목표로 만든 오픈소스 트랜잭션 스토리지 엔진임
- 출발점은 SQLSync에서의 경험임
- SQLSync는 SQLite 위에 구축된 프론트엔드 최적화 데이터베이스 스택이며, Git과 분산 시스템 아이디어를 동기화 엔진에 사용함
- SQLSync는 전체 변경 로그를 모든 클라이언트에 복제하는 구조라 서버에서는 괜찮아도 엣지와 브라우저 환경에는 맞지 않음
- Graft의 목표는 클라이언트가 원하는 속도로 동기화하고, 필요한 것만 가져오며, 엣지와 오프라인 기기에서도 임의 데이터를 강한 일관성으로 복제하는 것임
전체 복제와 스키마 인식 diff 사이의 설계
- 기존 해법은 크게 두 갈래로 나뉨
- 전체 복제: 전체 데이터셋을 각 클라이언트에 동기화하므로 서버리스 함수나 웹앱 같은 제약 환경에 실용적이지 않음
- 스키마 인식 diff: CDC나 CRDT처럼 행 또는 필드 수준의 논리 변경을 추적하지만, 애플리케이션과 깊게 통합해야 하고 임의 데이터에는 일반화하기 어려움
- Graft는 전체 복제처럼 스키마에 무관함
- 저장 데이터의 종류를 알거나 신경 쓰지 않고, 바이트가 담긴 페이지를 복제함
- 동시에 논리 복제처럼 마지막 동기화 이후 무엇이 바뀌었는지에 대한 압축된 설명을 클라이언트에 전달함
- 핵심 추상화는 Volume임
- Volume은 고정 크기 Page의 희소하고 정렬된 컬렉션임
- 클라이언트는 특정 Snapshot에서 트랜잭션 API로 Volume을 읽고 씀
- 내부적으로 Graft는 필요한 것만 저장·복제하며, 객체 스토리지를 내구성 있고 확장 가능한 백엔드로 사용함
지연 동기화: 클라이언트가 원하는 시점에 따라잡기
- Graft는 엣지 클라이언트가 가끔 깨어나고, 네트워크가 불안정하며, 실행 시간이 짧은 환경을 전제로 설계됨
- 지속적인 복제에 의존하지 않고, 클라이언트가 언제 동기화할지 직접 선택함
- 동기화는 “마지막 Snapshot 이후 무엇이 바뀌었는가”라는 질문에서 시작함
- 서버는 실제 데이터를 보내지 않고, 변경된 페이지 인덱스의 압축 비트셋인
graft를 응답함graft는 기존 Snapshot에 새 변경을 붙이는 안내 역할을 함- 클라이언트는 어떤 페이지를 재사용할 수 있고, 어떤 페이지를 필요할 때 가져와야 하는지 알 수 있음
graft는 데이터가 아니라 변경 메타데이터이므로, 무엇을 언제 가져올지에 대한 제어권이 클라이언트에 남음
부분 복제와 프리패칭
- 브라우저 탭, 모바일 앱, 서버리스 함수에서는 일부 쿼리를 처리하기 위해 전체 데이터셋을 내려받기 어려움
- 클라이언트는
graft를 받은 뒤 어떤 페이지가 여전히 유효하고 어떤 페이지를 가져와야 하는지 판단함 - 필요한 페이지만 선택적으로 가져오므로, 실제로 사용할 데이터만 복제할 수 있음
- Graft는 페이지 접근 지연을 줄이기 위해 여러 프리패칭 방식을 지원함
- 일반 목적 프리패칭: Leap 알고리듬 기반 내장 프리패처가 접근 패턴을 식별해 향후 페이지 접근을 예측함
- 도메인별 프리패칭: 애플리케이션이 사용자 프로필처럼 자주 조회되는 데이터에 대한 지식을 활용해 관련 페이지를 미리 가져올 수 있음
- 선제 가져오기: 필요하면 모든 변경분을 가져와 사실상 전체 복제로 되돌릴 수 있으며, 서버 측 Graft 워크로드에 특히 유용함
- 페이지는 객체 스토리지에 직접 호스팅되므로 내구성과 확장성을 갖춘 복제 기반으로 쓰임
엣지 배치와 임베디드 클라이언트
- Graft의 엣지 복제는 어떤 데이터를 동기화할지뿐 아니라, 필요한 위치에 데이터를 두는 것까지 목표로 함
- 페이지는 객체 스토리지에서 글로벌 엣지 서버 플릿을 통해 제공됨
- 자주 접근되는 hot page는 클라이언트 가까이에 캐시될 수 있음
- 전 세계 사용자 위치와 관계없이 낮은 지연과 높은 응답성을 목표로 함
- Graft 클라이언트는 가볍고 임베드되도록 설계됨
- 의존성이 적고 런타임이 작음
- 브라우저, 기기, 모바일 앱, 서버리스 함수 같은 환경에 통합할 수 있음
- 엣지 캐싱은 일관성과 충돌 처리 문제를 만들기 때문에, Graft는 강한 일관성 모델을 함께 제공함
일관성 모델과 충돌 처리
- Graft는 Serializable Snapshot Isolation을 일관성 모델로 사용함
- 클라이언트는 특정 Snapshot에서 격리되고 일관된 데이터 뷰를 얻으며, 읽기는 서로 방해하지 않고 동시에 진행될 수 있음
- 쓰기는 엄격하게 직렬화되어 모든 트랜잭션에 전역적으로 일관된 순서가 생김
- 오프라인 우선과 지연 복제 특성 때문에, 클라이언트가 오래된 Snapshot을 바탕으로 커밋을 시도할 수 있음
- 이런 커밋을 무조건 수락하면 strict serializability가 깨짐
- Graft는 해당 커밋을 안전하게 거부하고, 클라이언트가 처리 방식을 선택하게 함
- 클라이언트의 일반적인 선택지는 세 가지임
- Reset and replay: 최신 Snapshot을 가져오고 로컬 트랜잭션을 다시 적용한 뒤 재시도함
- 전역 데이터는 strict serializable 상태를 유지함
- 로컬에서는 Optimistic Snapshot Isolation을 경험하며, 읽기는 내부적으로 일관된 Snapshot을 보지만 커밋 거부 시 해당 Snapshot이 버려질 수 있음
- Merge: 로컬 상태를 서버의 최신 Snapshot과 병합함
- 이 경우 전역 일관성 모델이 snapshot isolation으로 낮아질 수 있음
- Volume fork: 영구적으로 새 Volume을 만들어 분리함
- 전역 serializability를 유지함
- Reset and replay: 최신 Snapshot을 가져오고 로컬 트랜잭션을 다시 적용한 뒤 재시도함
만들 수 있는 애플리케이션
- 오프라인 우선 앱: 노트, 작업 관리, CRUD 앱처럼 부분적으로 오프라인에서 동작하는 앱에서 Graft가 동기화를 담당할 수 있음
- 충돌 핸들러와 결합하면 임의 데이터 위에 멀티플레이어 기능도 가능함
- 크로스 플랫폼 데이터: 모바일 플랫폼, 기기, 웹에서 데이터를 공유하고 벤더 종속을 줄일 수 있음
- 상태 없는 읽기 복제본: 로컬 상태 없이 데이터베이스 복제본을 띄우고 최신 Snapshot 메타데이터를 가져온 뒤 바로 쿼리를 실행할 수 있음
- 전체 데이터를 내려받거나 로그를 재생할 필요가 없음
- 임의 데이터 복제: Graft는 페이지 복제에 집중하므로 페이지 내부 데이터 형식에는 관여하지 않음
- SQLite 데이터베이스, AI 모델, Parquet, Lance 파일, Geospatial tilesets 같은 데이터를 대상으로 삼을 수 있음
SQLite 확장 libgraft
- 현재 Graft를 쓰기 가장 쉬운 방법은 네이티브 SQLite 확장인
libgraft임 libgraft는 SQLite가 동작하는 곳에서 쓸 수 있고, 클라이언트가 실제로 사용하는 데이터베이스 일부만 복제함- SQLite VFS를 구현해 데이터베이스 읽기와 쓰기를 가로챔
- SQLite가 WAL mode에서 제공하는 것과 같은 트랜잭션 및 동시성 의미론을 제공함
- 제공 기능은 다음과 같음
- 객체 스토리지와의 비동기 복제
- 엣지와 기기에서의 지연 부분 복제
- Serializable Snapshot Isolation
- 특정 시점 복원
- 문서는 GitHub의 SQLite 문서에서 볼 수 있음
참여와 관리형 서비스 계획
- Graft는 GitHub에서 공개 개발됨
- 이슈, 토론, Pull Request를 받을 수 있으며 contribution guide를 제공함
- 대화 채널로 Discord와 이메일이 제공됨
- Graft Managed Service 출시도 계획되어 있으며, 대기 목록 가입 링크가 제공됨
로드맵
- Graft는 1년간의 연구, 여러 번의 반복, 한 번의 큰 방향 전환을 거쳤지만 아직 남은 작업이 많음
- 계획된 항목은 다음과 같음
- WebAssembly 지원: 브라우저에서 Graft를 사용할 수 있게 하며, SQLite 공식 Wasm 빌드, wa-sqlite, sql.js 지원을 목표로 함
- Graft와 SQLSync 통합: Wasm 지원 뒤 SQLSync의 mutation, rebase, query subscription 계층을 분리해 Graft 복제 데이터베이스 위에 올리는 계획임
- 클라이언트 라이브러리 확대: Python, JavaScript, Go, Java용 네이티브 Graft 클라이언트 래퍼를 원함
- 저지연 쓰기: 현재 push 작업은 객체 스토리지에 완전히 커밋될 때까지 블록됨
- S3 express zone 실험
- 객체 스토리지 앞단에 저지연 내구성 합의 그룹을 두는 방식
- 가비지 컬렉션, 체크포인팅, 컴팩션: 쿼리 성능 극대화, 낭비 공간 최소화, 영구 삭제를 위해 필요함
- 인증과 권한 부여: 관리형 서비스 계정부터 Volume 읽기/쓰기 세밀 권한까지 포함하는 넓은 작업임
- Volume forking: 서비스는 Segment 참조를 새 Volume에 복사해 zero-copy fork를 수행할 수 있지만, 로컬 fork는 현재 모든 페이지를 복사해야 함
- 충돌 처리: 내장 충돌 해결 전략과 확장 지점을 제공할 계획이며, 초기 전략은 겹치지 않는 트랜잭션을 자동 병합하는 것임
SQLite 복제 해법과의 비교
- 비교 정보는 문서와 블로그 글에서 모은 것이며, 완전히 정확하지 않을 수 있다는 단서가 붙어 있음
-
mvSQLite
- mvSQLite는 SQLite 페이지를 FoundationDB에 직접 저장하는 커스텀 VFS 계층을 구현함
- Graft와 mvSQLite는 페이지 수준 버전 관리로 지연 fetch와 부분 데이터베이스 뷰를 가능하게 하는 점이 비슷함
- 차이는 저장 위치와 페이지 변경 추적 방식임
- mvSQLite는 FoundationDB에 의존하고 모든 노드가 클러스터에 직접 접근해야 함
- Graft의 Splinter 기반 changeset은 자체 포함형이라 배포하기 쉽고, 변경 페이지 버전을 알기 위해 FoundationDB를 직접 질의할 필요가 없음
-
Litestream
- Litestream은 SQLite WAL frame을 객체 스토리지에 계속 복제하는 스트리밍 백업 솔루션임
- Graft는 커스텀 VFS로 SQLite 커밋 과정에 직접 통합되어 지연 부분 복제와 분산 쓰기를 가능하게 함
- 둘 다 페이지를 객체 스토리지에 복제하고 특정 시점 복원을 지원함
-
cr-sqlite
- cr-sqlite는 테이블을 CRDT로 바꿔 논리적 행 수준 복제를 가능하게 하는 SQLite 확장임
- 자동 충돌 해결을 제공하지만 스키마 인식과 애플리케이션 수준 통합이 필요함
- Graft는 스키마에 무관하고 임의 SQLite 확장 및 커스텀 데이터 구조와 호환되지만, 전역 serializability를 위해 애플리케이션이 충돌 해결을 명시적으로 처리해야 함
-
Cloudflare Durable Objects with SQLite Storage
- Durable Objects와 SQLite를 결합하면 비즈니스 로직으로 감싼 강한 일관성과 높은 내구성의 데이터베이스를 Cloudflare 엣지 네트워크에 둘 수 있음
- 내부적으로 SQLite WAL을 객체 스토리지에 복제하고 주기적으로 체크포인트하는 점에서 Litestream과 비슷함
- Graft는 복제를 1급 기능으로 노출하고 엣지와의 효율적 복제를 목표로 함
-
Cloudflare D1
- Cloudflare D1은 HTTP API로 접근하는 관리형 SQLite 데이터베이스임
- Graft는 데이터를 클라이언트 애플리케이션에 임베드해 엣지로 직접 복제하는 분산형 모델임
-
Turso & libSQL
-
rqlite & dqlite
-
Verneuil
- Verneuil은 객체 스토리지를 통해 SQLite Snapshot을 읽기 복제본에 비동기 복제하며 신뢰성을 우선함
- 복제 지연이나 신선도를 최소화하는 메커니즘은 명시적으로 피함
- Graft는 다중 작성자 분산 데이터베이스에 더 가깝게 동작하며, 선택적 실시간 부분 복제를 강조함