SQLite 파일 포맷과 호환되지만 아키텍처는 완전히 다른 새로운 엔진 Turso를 살펴보고, Rust로의 재작성으로 해결되는 SQLite의 한계, 확장(Extensions), 그리고 인프로세스에서 네트워크형까지 스케일하는 설계가 무엇을 의미하는지 정리한다.
URL: https://kerkour.com/turso-sqlite
Title: Deep dive into Turso, the "SQLite rewrite in Rust"
저는 Rust를 사랑하고 SQLite도 사랑합니다. 그래서 “SQLite를 Rust로 다시 쓴다”는 소식을 들었을 때 꽤나 흥분했죠: Turso.
SQLite는 아마도 세상에서 가장 널리 배포된 데이터베이스일 겁니다. 여러분의 어떤 기기에도 수십 개의 데이터베이스가 들어 있고, 현존하는 소프트웨어 중 가장 신뢰할 수 있는 것 중 하나일 가능성이 큽니다. 코드보다 테스트가 590배 더 많거든요(테스트도 코드라는 건 알지만 단순화를 위해 이렇게 말하겠습니다): 테스트 약 92,053,100줄 vs 코드 약 155,800줄.
게다가 거의 모든 프로그래밍 언어에 SQLite 바인딩이 존재합니다. 그렇다면 대체 누가 이걸 Rust로 다시 쓰고 싶어할까요?
우리가 SQLite를 이야기할 때, 대부분은 먼저 C 라이브러리, 그 신뢰성, 전설적인 테스트 커버리지, 그리고 임베딩이 쉬운 점을 떠올립니다.
하지만 SQLite는 “단일 파일 데이터베이스 포맷”에 대한 하나의 명세(specification)로 볼 수도 있고, 바로 여기서부터 흥미로운 지점이 시작됩니다. SQLite 데이터베이스는 대체로 스키마와 몇 가지 메타데이터를 포함한, 디스크(또는 메모리)에 저장된 초고도로 최적화된 B+Tree들의 집합입니다.
만약 이 파일 포맷을 이해할 수 있는 다른 데이터베이스 엔진이 있다면 어떨까요? Turso가 바로 그것입니다. Turso는 (MVCC 모드 같은 극히 일부 예외를 제외하면) SQLite 데이터베이스 파일 포맷과 호환되지만, 아키텍처 수준에서는 완전히 다른 새로운 데이터베이스 엔진입니다.
사용자 경험도(적어도 Rust에서는) 동일합니다. 다음은 Hello World 예시입니다:
rustuse turso::{Builder, EncryptionOpts}; #[tokio::main] async fn main() -> Result<(), anyhow::Error> { let db = Builder::new_local("test.db").build().await?; let conn = db.connect()?; let rows = conn.query("SELECT * FROM users", ()).await?; return Ok(()); }
맞습니다. SQLite는 예술 작품 같은 존재이지만, 방심한 개발자를 괴롭히는 문제도 많습니다.
첫째, SQLite의 테스트 스위트는 오픈 소스가 아닙니다. 따라서 SQLite를 수정하려는 사람들은 원 개발자들이 누리는 것과 같은 마음의 평화를 얻기 어렵습니다.
둘째, SQLite 개발자들은 외부 기여를 사실상 잘 받지 않습니다. 버그를 보고하면 빠르게 고쳐주고 기능 요청도 할 수는 있지만, 여러분의 상황에 충분하지 않을 수도 있습니다.
마지막으로, SQLite는 C로 작성되어 있어서 변경 로그가 보여주듯 쉽게 피할 수 있는 버그에 취약하며, C의 빈약한 타입 시스템과 안전하지 않은 메모리 관리 때문에 유지보수와 신규 기능 추가가 매우 어렵습니다.
그 외에도 SQLite는 (공식적으로) 동시 쓰기(concurrent writes)를 지원하지 않습니다. 또 SQLite의 컬럼은 기본적으로 약한 타입(weakly typed)이어서 데이터베이스에 기대하는 것과 다를 수 있고, 스키마 변경은 정말 골치 아픈 경우가 많아 어떤 작업에서는 전체 테이블을 직접 복사해야 합니다.
_Why Is SQLite Coded In C_에서 SQLite 저자들은 Rust가 SQLite를 다시 쓰기 위한 최고의 후보일 수 있다고 암시합니다. 다만 SQLite는 정말 이국적인(exotic) 환경에도 배포되기 때문에, 현재로서는 Rust 코드를 배포할 수 없는 곳들이 존재해 요구사항을 모두 충족하지 못한다고 합니다.
저는 이 상황을 ‘혁신가의 딜레마’라는 프리즘으로 봅니다. 기존 강자는 진화를 위해 시장의 일부를 희생하려 하지 않기 때문에, 혁신을 위해서는 새로운 플레이어가 등장해야 합니다.
그리고 Turso는 혁신하고 있습니다. v1.0에 도달하기도 전에, 이미 SQLite를 사용하는 개발자들이 겪어온 오래된 고충 대부분을 해결했습니다. 이 문제들은 제가 이전에 다룬 _Optimizing SQLite for servers_에서도 언급한 것들입니다:
여기에 더해, 빌림 검사기(borrow checker), 메모리 안전성, 그리고 두려움 없는 리팩터링을 가능하게 하는 깔끔한 추상화 같은 Rust의 장점들은 말할 필요도 없죠.
Turso의 기능 중 가장 오해받는 부분 중 하나는, Turso가 인프로세스(in-process) 데이터베이스로 사용되도록 설계되었지만, PostgreSQL처럼 네트워크형(networked) 데이터베이스로도 동작하도록 설계되었다는 점입니다. 즉, Turso를 만드는 회사는 클라우드 호스팅 솔루션을 판매할 수 있습니다.

개발자이자 창업가 입장에서, 이건 제 꿈의 데이터베이스입니다. 10개 프로젝트 중 9개는 단일 VM 이상을 절대 필요로 하지 않기 때문에 SQLite/Turso의 인프로세스 모델이 아주 잘 맞습니다. 하지만 트래픽을 얻고 스케일이 필요해지는 나머지 1개 프로젝트의 경우, 제가 이전에 다뤘듯 SQLite는 동시 쓰기를 지원하지 않기 때문에 잘 맞지 않습니다. 그리고 프로젝트를 스케일하는 와중에 애플리케이션을 SQLite에서 Postgres로 포팅하는 건, 여러분의 소중한 시간을 쓰기 좋은 일이 아닙니다.
그래서 대부분의 프로젝트는 “혹시 모르니까”라는 이유로 처음부터 PostgreSQL로 시작합니다. 이는 개발과 운영 모두에서 마찰을 늘립니다. 특히 확장을 관리하거나, 데이터베이스 버전을 업그레이드하거나, 백업을 관리해야 할 때 더 그렇습니다.
인프로세스에서 네트워크형으로 확장 가능한 데이터베이스가 절실히 필요합니다. 하지만 아주 많은 사람들은 ‘더 빠른 말’이 아니라 ‘자동차/기차’가 필요하다는 걸 모릅니다.
이 점은 에이전트(agents) 시대에 특히 더 사실입니다. 에이전트는 각자 샌드박스에서 작업하는데, 세션마다 새 데이터베이스를 띄우는 오버헤드는 원하지 않을 것입니다. 또한 로컬/임베디드 앱을 위한 임베디드 데이터베이스와, 백엔드를 위한 데이터베이스 둘 다 필요한 프로젝트에도 마찬가지로 유리합니다.
SQLite의 최고의 기능 중 하나는 커스텀 확장(extension)을 만들고 로드하기가 매우 쉽다는 점입니다. PostgreSQL 같은 네트워크형 데이터베이스와 달리(대개 클라우드 제공자가 관리하며 제한된 확장만 쓸 수 있는 경우가 많죠), SQLite 확장은 컨테이너에 그냥 넣어두면 되는 동적 라이브러리입니다. 빠르고 안전한 SQLite 확장을 Rust로 왜/어떻게 만드는지 알고 싶다면 이전 글 _Building SQLite extensions in Rust_를 참고하세요.
Turso는 이미 확장을 지원하며, Rust로 확장을 작성하기 위한 SDK도 제공합니다.
아래는 Turso용 확장을 만드는 일이 얼마나 쉬운지 보여주기 위해, https://github.com/tursodatabase/turso/blob/main/extensions/crypto/src/lib.rs에서 가져온 짧은 예시입니다.
rust#[scalar(name = "crypto_sha256", alias = "crypto_sha256")] fn crypto_sha256(args: &[Value]) -> Value { if args.len() != 1 { return Value::error(ResultCode::Error); } let Ok(hash) = sha256(&args[0]) else { return Value::error(ResultCode::Error); }; Value::from_blob(hash) }
Turso는 제가 오랫동안 기다려온 데이터베이스입니다! 저는 그들을 개인적으로 알지는 못하지만, 팀은 탄탄해 보입니다. 행운을 빌며, v1.0을 (참을성 있게/없게) 기다리고 있습니다.
분석 데이터와 시계열(time series)에는 DuckDB를, 트랜잭션 데이터에는 Turso를 쓰는 조합이라면, 모든 것을 클러스터링해서 100GB도 안 되는 데이터를 처리하던 ‘분산 시스템 광풍’의 여러 해를 지나 드디어 정상으로 돌아온 느낌입니다.
타이밍도 아주 좋습니다. 임베디드 데이터베이스는 AI 에이전트에 완벽하게 들어맞기 때문입니다. 그래서 그들이 제때 해내서 스스로 좋은 돈을 벌고, VC 자금이 들어간 회사에서 종종 있는 위험(회사를 팔아버리거나 프로젝트가 ‘엔시티피케이션(enshitification)’ 되는 것)을 피할 수 있길 바랍니다.
Rust로 백엔드 개발을 배우고 싶다면 제 글 Architecting and building medium-sized web services in Rust with Axum, SQLx and PostgreSQL을 참고하세요. 임베디드 개발을 배우고 싶다면 Introduction to embedded development with Rust: Overview of the ecosystem를 참고하세요.
응용 암호학, 보안 엔지니어링, 안전하고 프로덕션 레디한 Rust 코드를 작성하는 법 같은 ‘흑마법사’ 급 내용을 배우고 싶다면 제 책 **Black Hat Rust**를 살펴보세요. 그 안에서 여러분은 (무엇보다도) 종단간 암호화된 Remote Access Tool, 익스플로잇, 그리고 Rust로 웹 서버를 만들게 될 겁니다.