작은 웹 서비스를 가상 서버 한 대와 SQLite 파일 하나로 돌리고 있다고 해 보자. 별도 데이터베이스 서버를 띄울 필요도 없고 같은 머신에서 파일을 바로 읽으니 쿼리도 빠르다. 문제는 어느 날 예고 없이 찾아온다. 그 서버의 디스크가 고장 나는 순간 데이터베이스도 함께 사라지는 것이다. 백업을 해 두었더라도 새 서버에 복원하는 동안 서비스가 멈춰 서고 백업이 없었다면 그걸로 끝이다.
이 약점을 메우려고 지난 12년 동안 SQLite에 복제(replication) 기능을 붙인 프로젝트가 줄지어 나왔다. rqlite와 dqlite를 시작으로 Litestream, LiteFS, cr-sqlite, libSQL, Cloudflare D1까지 이름도 다양하다. 그중에는 벤처 투자를 받은 회사도 있고 인수된 팀도 있어서 이제 ‘분산 SQLite’는 어엿한 한 분야로 불린다.
2014년에 rqlite를 처음 만든 필립 오툴(Philip O’Toole)이 최근 이 분야의 12년 역사를 직접 되짚는 글을 썼다. 요즘 이 분야를 다루는 글은 대개 2021년 무렵에서 이야기를 시작하지만 오툴은 그보다 7년 앞선 출발점부터 현장을 지켜봐 온 당사자다. 프로젝트마다 어떤 질문에 답하려고 태어났는지 그 갈래를 따라가 보면 내 서비스에 맞는 도구가 무엇인지 선명하게 보인다.
rqlite는 2014년 SQLite 앞에 Raft 로그를 세웠다
첫 단추는 실무에서 겪은 구체적인 필요였다. 2014년 오툴이 일하던 jut.io에서는 클러스터 설정과 상태를 담을 작은 관계형 저장소가 필요했는데 그 SQLite 파일을 어느 노드에 두어야 안전할지 마땅한 답이 없었다. 그는 여러 서버가 “이 쓰기들을 이 순서대로 적용한다”에 의견을 맞추는 합의 프로토콜 Raft를 SQLite 앞단에 붙이면 된다고 봤다. 회사는 결국 다른 길을 택했지만 문제는 범용적이고 해법은 작다는 확신이 들자 그는 개인 프로젝트로 개발을 이어 갔다. 그리고 2014년 9월 설계 소개 글을 올린 그 주말에 GitHub에 소스코드를 공개했다.
rqlite의 구조는 단순하다. 각 노드의 SQLite 앞에 Raft 로그를 두고 들어오는 모든 쓰기를 클러스터 전체에 복제한다. 덕분에 노드 하나를 잃어도 나머지 노드에 똑같은 데이터가 남아 있어 서비스가 멈추지 않는다. 초기에는 당시 InfluxDB와 etcd가 쓰던 goraft 라이브러리를 썼고 2016년 초 HashiCorp의 Raft 구현체로 바꿔 지금까지 쓰고 있다.
내부 라이브러리는 바뀌었어도 핵심 결정은 그대로다. rqlite는 분산 시스템의 갈림길에서 ‘CP’를 택했다. 서버 사이 네트워크가 끊어졌을 때 노드마다 데이터가 달라지는 일을 막으려고 일관성(Consistency)을 우선하고 그 대가로 일부 고립된 노드가 요청을 거절하는 상황을 받아들인다는 뜻이다. 모든 쓰기는 리더 노드를 거쳐 과반수의 승인을 받아야 확정되며 네트워크 분할로 소수 쪽에 고립된 노드는 쓰기를 거부한다. HTTP API와 읽기 일관성 수준, 스냅샷 같은 나머지 설계는 모두 이 결정에서 따라 나왔다.
비슷한 시기에 같은 아이디어를 떠올린 팀도 있었다. Expensify는 몇 년 전부터 사내에서 Paxos 계열 프로토콜로 SQLite를 복제해 쓰다가 2016년 이를 Bedrock으로 공개했고 Erlang으로 만든 ActorDB도 비슷한 무렵에 나왔다. 다만 둘 다 태어난 사내 환경 밖으로 널리 퍼지지 못했고 누구든 실행 파일 하나만 받아 클러스터를 꾸릴 수 있는 범용 오픈소스로 자리 잡은 것은 rqlite였다.
dqlite는 같은 일을 제품 안에 넣는 라이브러리로 한다
2017년에는 우분투로 잘 알려진 Canonical이 dqlite를 내놓았다. 컨테이너 도구인 LXD와 MicroK8s에 무거운 etcd를 따로 띄우지 않고도 복제되는 저장소를 넣고 싶었기 때문이다. 당시 Canonical 창업자 마크 셔틀워스가 오툴에게 직접 전화해 rqlite를 쓸 수 있을지 이야기했지만 끝내 맞지 않았다. dqlite는 Go로 시작했다가 자체 Raft 라이브러리를 갖춘 C 구현으로 다시 쓰였다.
두 프로젝트는 ‘Raft 합의로 SQLite를 복제한다’는 목적은 거의 같지만 배포 형태가 정반대다. dqlite는 다른 제품 코드 안에 넣어 함께 빌드하는 라이브러리이고 rqlite는 제품 옆에 따로 띄워 두고 호출하는 서버다. Canonical에게는 자기 제품 안에 숨어 돌아갈 데이터베이스가 필요했던 반면 오툴이 처음부터 만들고 싶었던 것은 누구의 제품 옆에든 세워 둘 수 있는 데이터베이스였다.
a database beside anyone’s product.
실무에서 둘을 놓고 고를 때는 트랜잭션 지원도 중요한 기준이다. Hacker News 댓글에서도 자주 언급되듯 dqlite는 여러 쿼리를 묶는 트랜잭션을 완전히 지원하지만 rqlite는 대화형 BEGIN과 COMMIT을 공식 지원하지 않는다. 여러 SQL 문을 조건에 따라 한 트랜잭션으로 묶어야 하는 앱이라면 이 차이가 크다.
Litestream은 합의 없이 WAL을 S3로 흘려보낸다
2021년 초 벤 존슨(Ben Johnson)이 공개한 Litestream은 접근을 완전히 바꿔 합의를 아예 쓰지 않는다. SQLite는 데이터를 고칠 때 변경 내용을 ‘WAL(데이터 파일에 반영하기 전에 먼저 적는 로그 파일)’에 쌓는데 Litestream은 옆에서 이 WAL을 지켜보다가 바뀐 부분을 S3로 계속 보낸다. 서버는 한 대 그대로이고 클러스터도 없다. 그 대신 서버가 죽으면 S3에 쌓인 기록으로 데이터베이스를 되살릴 수 있어 거의 비용 없이 재해 복구 수단이 생긴다.
존슨의 메시지는 명료했다. 대다수 애플리케이션에는 다중 노드 클러스터가 아니라 머신 한 대와 그 머신이 죽었을 때 데이터를 되살릴 값싼 방법만 있으면 충분하다는 것이다. 이 진단은 수많은 소규모 서비스의 현실에 정확히 들어맞았고 이후 몇 년 동안 블로그와 콘퍼런스를 타고 번진 “SQLite면 충분하다”는 흐름에 불을 지폈다.
결국 Litestream과 rqlite의 차이는 질문 하나로 갈린다. Litestream은 “머신이 죽으면 데이터를 어떻게 살리지?”에 답하고 rqlite는 “머신이 죽었는데 복원될 때까지 기다릴 수 없으면 어떡하지?”에 답한다. 앞의 질문만으로 충분한 경우가 훨씬 흔하지만 당장 몇 분의 중단도 허용할 수 없는 사람은 뒤의 질문을 들고 rqlite를 찾아온다. 장애가 났을 때 서비스가 멈추는 시간과 잃어도 되는 데이터의 양을 어디까지 견딜 수 있는지가 두 도구를 가르는 기준이다.
이후 Fly.io는 2022년 존슨을 영입해 파일시스템 수준에서 SQLite를 복제하는 LiteFS를 내놓았다. 복제 SQLite를 직접 설치해 관리하는 소프트웨어 대신 플랫폼 기능으로 제공하려는 첫 시도였다. 재미있게도 Litestream의 깔끔한 WAL 처리 방식은 나중에 원조 격인 rqlite v8.0의 내부 구조에도 영향을 주었다.
합의·스트리밍·병합으로 나뉜 세 갈래 설계
2022년에는 rqlite도 Litestream도 가지 않은 길이 두 개 더 열렸다. cr-sqlite는 SQLite 확장으로 ‘CRDT’를 붙였다. CRDT는 여러 곳에서 따로 고친 데이터를 나중에 충돌 없이 합칠 수 있게 설계된 자료 구조다. 당장의 일관성보다 언제든 쓸 수 있는 가용성을 고르는 ‘AP’ 설계라서 기기마다 오프라인으로 먼저 쓰다가 나중에 합치는 로컬 우선 앱에 잘 맞는다. 이런 앱에서는 과반수 연결을 요구하는 rqlite의 CP 보장이 오히려 걸림돌이 된다.
같은 해 나온 libSQL은 아예 SQLite 본체를 포크했다. 원래 SQLite 프로젝트는 코드는 공개하되 외부 기여는 받지 않기 때문에 엔진 내부를 바꾸고 싶었던 ChiselStrike(이후 Turso)가 직접 갈래를 낸 것이다. 그 위에 전 세계 엣지에 읽기 복제본을 두는 호스팅 서비스를 만들어 벤처 투자를 받았고 2026년 Supabase에 인수되었다. 비슷한 시기에 나온 SQLite Cloud는 Raft로 복제한 SQLite를 호스팅해 주는 서비스로 내부 설계만 놓고 보면 2014년 rqlite의 원래 구조를 클라우드 상품으로 감싼 형태에 가깝다.
이렇게 늘어놓고 보면 분산 SQLite의 설계는 등장한 순서대로 세 갈래로 나뉜다. 가장 먼저 나온 ‘합의’ 방식은 리더 하나와 클러스터 과반수 합의로 강한 일관성을 보장한다. 두 번째인 ‘스트리밍’ 방식은 클러스터 없이 단일 서버의 변경 로그를 외부 저장소로 보내 값싼 복구를 더한다. 마지막 ‘병합’ 방식은 여러 쓰기 주체를 허용하는 대신 잠시 어긋났다가 결국 같아지는 최종 일관성을 받아들인다.
커뮤니티에서 벌어지는 논쟁도 들여다보면 어느 방식이 더 우월한지보다 지금 풀려는 문제가 무엇인지를 두고 벌어진다. 지난 12년 동안 적어도 여덟 팀이 저마다 다른 필요와 프로토콜로 SQLite 저장 엔진 바깥에 복제를 붙이는 구조를 다시 만들어 왔고 세 갈래 모두 여전히 뚜렷한 수요를 갖고 있다.
투자는 복제 SQLite를 유료 서비스로 만든 쪽에 몰렸다
기술 갈래가 나뉘는 사이 비즈니스의 중심도 움직였다. Cloudflare는 2022년 엣지 네트워크 안에서 도는 서버리스 SQLite 데이터베이스 D1을 발표했고 나중에는 Durable Objects 안에도 SQLite 저장소를 넣었다. Turso의 서비스도 같은 모양이어서 사용자는 내부 복제 구조를 몰라도 그냥 사서 쓰면 된다. 분산 SQLite가 ‘직접 돌리는 오픈소스’에서 ‘사서 쓰는 클라우드 서비스’로 넓어진 셈이다.
자본 시장의 돈도 정확히 그 경계를 따라 흘렀다. 길을 먼저 닦은 rqlite와 dqlite, Litestream은 지금도 무료 오픈소스인 반면 투자를 받거나 인수된 쪽은 예외 없이 복제 SQLite를 월별 청구서가 붙는 호스팅 서비스로 바꾼 프로젝트들이다. 오픈소스 인프라 바닥에서 흔히 겪는 흐름이지만 누군가 이 아이디어를 상품으로 팔기 시작하기 8년 전부터 이미 무료 오픈소스로 존재했다는 사실만큼은 기억해 둘 만하다.
물론 사서 쓰는 쪽에도 고민은 있다. Hacker News에는 D1을 실제 운영 환경에 써 봤는데 신뢰성이 기대에 못 미쳤다는 경험담이나 데이터베이스를 동적으로 즉시 만들 수 없어 고객사(테넌트)별로 파일을 나누기 어렵다는 불만도 올라왔다. 관리형 서비스는 편하지만 내부를 모르는 만큼 플랫폼이 정한 제약도 함께 받아들여야 한다.
분산 SQLite를 고를 때 먼저 물어볼 것
12년의 흐름을 따라오고 나면 도구를 고르는 순서가 자연스럽게 보인다. 가장 먼저 머신이 죽었을 때 복원되는 몇 분 동안 서비스가 멈춰도 되는지 묻는다. 기다릴 수 있다면 단일 SQLite에 Litestream만 붙여 S3로 백업하는 구성으로 충분할 가능성이 높다. 실제로 웹 애플리케이션의 90% 이상은 이 조합이 가장 잘 맞는다.
기다릴 수 없다면 합의 방식으로 간다. 데이터베이스를 별도 서버로 띄워 운영하고 싶으면 rqlite가 맞고 제품 바이너리 안에 내장하고 싶으면 dqlite가 후보인데 이때 트랜잭션(BEGIN·COMMIT) 지원 같은 세부 기능을 꼭 확인해야 한다. 여러 기기에서 따로 쓰다가 나중에 합쳐야 한다면 병합 방식인 cr-sqlite를 살펴보면 되고 운영까지 통째로 맡기고 싶다면 플랫폼 제약을 감수하는 조건으로 D1이나 Turso 같은 관리형 서비스를 고르면 된다.
화려한 클라우드 서비스들이 쏟아져 나온 지금도 rqlite는 2014년 첫날과 같은 일을 훨씬 성숙하게 해내고 있다. 누구에게도 사용료를 내지 않고 직접 운영하며 속속들이 이해할 수 있는 내결함성 관계형 데이터베이스가 필요할 때 바로 꺼내 쓸 수 있는 도구다. 12년 동안 갈래는 여럿 생겼어도 출발점의 질문은 그대로다. 내 데이터가 머신 한 대보다 오래 살아남아야 하는지, 그렇다면 서버가 죽었을 때 얼마나 빨리 다시 돌아와야 하는지다.
출처: Philip O’Toole, Twelve Years of Distributed SQLite, 2026년 10월. Hacker News 토론