본문 바로가기
WIKI 기술 지식 베이스

레퍼런스

원문 보기 위키 갱신

Turso의 Rust 레퍼런스예요. crate 세 개가 각각 어떤 상황에 맞는지, 연결·쿼리·트랜잭션·동기화·암호화 API를 하나씩 살펴볼게요.

출처: 문서

본문

Turso는 세 가지 Rust crate를 제공해요:

turso turso_serverless libsql
Use case 로컬/임베디드 데이터베이스, sync 원격 Turso 데이터베이스 (over-the-wire) 원격 libSQL 데이터베이스, 기존 libSQL 코드베이스
Engine Turso (rewrite) Turso (rewrite) libSQL (SQLite fork)
Concurrent writes Yes (MVCC) Yes (MVCC) Not supported
Sync push/pull (local-first) — Embedded Replicas (쓰기는 클라우드 primary로)
C compiler 불필요 불필요 core, replication, encryption 피처에 필요

새 프로젝트를 시작하나요? 로컬/임베디드 용도나 sync에는 turso를 쓰세요. 원격 접근에는 엔진에 맞는 crate를 고르면 돼요: Turso 데이터베이스에는 turso_serverless, libSQL 데이터베이스에는 remote 피처를 켠 libsql(C 컴파일러 불필요)이요.

turso

로컬·임베디드 용도예요. 동시 쓰기(MVCC)와 비동기 I/O를 지원하는 Turso Database 엔진 기반이에요.

Installing

cargo add turso tokio --features tokio/full

Connecting

use turso::Builder;

let db = Builder::new_local("app.db").build().await?;
let conn = db.connect()?;

인메모리 데이터베이스도 지원해요:

let db = Builder::new_local(":memory:").build().await?;

Querying

conn.execute(
    "CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL
    )",
    (),
).await?;

conn.execute("INSERT INTO users (name) VALUES (?)", ("Alice",)).await?;

let mut rows = conn.query("SELECT * FROM users", ()).await?;
while let Some(row) = rows.next().await? {
    let id: i64 = row.get(0)?;
    let name: String = row.get(1)?;
    println!("User: {} {}", id, name);
}

Prepared Statements

let mut stmt = conn.prepare("SELECT * FROM users WHERE id = ?1").await?;
let mut rows = stmt.query([42]).await?;

Transactions

let tx = conn.transaction().await?;

tx.execute("INSERT INTO users (name) VALUES (?1)", ["Alice"]).await?;
tx.execute("INSERT INTO users (name) VALUES (?1)", ["Bob"]).await?;

tx.commit().await?;

Encryption

로컬 데이터베이스를 저장 시점(at rest)에 암호화할 수 있어요:

use turso::{Builder, EncryptionOpts};

let db = Builder::new_local("encrypted.db")
    .experimental_encryption(true)
    .with_encryption(EncryptionOpts {
        cipher: "aegis256".to_string(),
        hexkey: "b1bbfda4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327".to_string(),
    })
    .build()
    .await?;

지원 암호: aegis256, aegis256x2, aegis128l, aegis128x2, aegis128x4, aes256gcm, aes128gcm.

암호화된 데이터베이스는 표준 SQLite 데이터베이스로는 읽을 수 없어요 — 반드시 Turso Database 엔진으로 열어야 해요.

참고로, Turso Cloud 데이터베이스도 bring-your-own-key로 암호화할 수 있어요 — 자세히 보기.

Sync (Push and Pull)

클라우드 sync가 있는 로컬 데이터베이스용이에요. 모든 읽기와 쓰기는 로컬에서 일어나고, push()로 변경을 클라우드에 보내고 pull()로 원격 변경을 가져와요.

sync 피처를 활성화하세요:

cargo add turso --features sync
use turso::sync::Builder;

let db = Builder::new_remote("app.db")
    .with_remote_url(&std::env::var("TURSO_DATABASE_URL")?)
    .with_auth_token(&std::env::var("TURSO_AUTH_TOKEN")?)
    .bootstrap_if_empty(true)  // Download schema on first sync (default)
    .build()
    .await?;

let conn = db.connect().await?;

첫 실행 때 로컬 데이터베이스가 원격에서 자동으로 bootstrap돼요. 전체 내용은 Turso Sync에서 확인하세요.

Push and Pull

// Push local writes to Turso Cloud
db.push().await?;

// Pull remote changes (returns true if changes were applied)
let changed = db.pull().await?;

Checkpoint

sync 상태를 유지하면서 로컬 WAL을 압축해 디스크 사용량을 제한해요:

db.checkpoint().await?;

Stats

let stats = db.stats().await?;
println!("Network received: {} bytes", stats.network_received_bytes);
println!("Network sent: {} bytes", stats.network_sent_bytes);
println!("WAL size: {} bytes", stats.main_wal_size);

libsql (Remote)

remote 피처를 켠 libsql crate로 Turso Cloud에 over-the-wire 접근할 수 있어요. 순수 Rust HTTP를 사용하고, C 컴파일러가 필요 없어요.

Installing

cargo add libsql --features remote

Connecting

use libsql::Builder;

let url = std::env::var("TURSO_DATABASE_URL")?;
let token = std::env::var("TURSO_AUTH_TOKEN")?;

let db = Builder::new_remote(url, token).build().await?;
let conn = db.connect()?;

Querying

let mut rows = conn.query("SELECT * FROM users WHERE id = ?1", [1]).await?;

libsql (libSQL)

libsql crate는 오늘날 Turso Cloud를 구동하는 SQLite 오픈소스 포크인 libSQL 기반이에요. 프로덕션에서 검증이 끝났고, 기존 libsql 기반 코드베이스를 다룰 때 맞는 선택이에요.

참고로, libsql Embedded Replicas에서는 읽기가 로컬에서 일어나고 쓰기는 클라우드 primary로 전송된 뒤 복제본에 반영돼요. Embedded Replicas는 완전히 지원돼요. sync가 필요한 새 프로젝트에는 turso crate의 turso::sync를 추천해요 — 퀵스타트를 보세요.

Embedded Replicas

Turso Cloud 데이터베이스에서 로컬 SQLite 파일로 sync되는 Embedded Replicas를 쓸 수 있어요. 읽기는 로컬로 실행되고, 쓰기는 클라우드 primary로 전송된 후 복제본에 반영돼요:

use libsql::Builder;

let url = std::env::var("TURSO_DATABASE_URL")?;
let token = std::env::var("TURSO_AUTH_TOKEN")?;

let db = Builder::new_remote_replica("local.db", url, token)
    .build()
    .await?;
let conn = db.connect()?;

Manual Sync

db.sync().await?;

Sync Interval

use std::time::Duration;

let db = Builder::new_remote_replica("local.db", url, token)
    .sync_interval(Duration::from_secs(300))
    .build()
    .await?;

Read Your Own Writes

기본적으로 push() 이후의 다음 pull()은 서버가 내 변경을 완전히 따라잡을 때까지 기다렸다가 반환해요 — 항상 자기 쓰기를 읽는 것을 보장하죠. 더 안전하지만 훨씬 느려요, 서버가 모든 대기 중 변경을 처리해야 하거든요. eventual consistency 읽기를 견딜 수 있다면 끄면 pull이 훨씬 빨라져요:

let db = Builder::new_remote_replica("local.db", url, token)
    .read_your_writes(false)
    .build()
    .await?;

Encryption

새 프로젝트에서는 로컬 암호화에 turso crate를 추천해요 — Turso Database 엔진 기반이라 성능이 좋고 동시 쓰기를 지원하거든요.

SQLite 파일에 암호화를 켜려면, 생성자에 암호화 키 값을 인자로 넘기면 돼요.

참고로, 암호화된 데이터베이스는 원시 데이터로 보이고 표준 SQLite 데이터베이스로는 읽을 수 없어요. 모든 작업에 libSQL 클라이언트를 써야 해요 — 자세히 보기.

Conditional compilation

libsql crate는 조건부 컴파일 피처를 지원해요:

Feature Description
remote HTTP 전용 클라이언트, 순수 Rust. C 컴파일러 불필요.
core 로컬 데이터베이스 전용. C 컴파일러 필요.
replication core에 임베디드 복제본 지원을 합침. C 컴파일러 필요.
encryption 저장 시 암호화. cmake 필요. 기본 비활성화.

Simple query

conn.execute("SELECT * FROM users", ()).await?;
conn.execute("SELECT * FROM users WHERE id = ?1", [1]).await?;

Placeholders

Positional

conn.execute("SELECT * FROM users WHERE id = ?1", libsql::params![1]).await?;

Named

conn.execute("INSERT INTO users (name) VALUES (:name)", libsql::named_params! { ":name": "Iku" }).await?;

Deserialization

use libsql::{de, Builder};

#[derive(Debug, serde::Deserialize)]
struct User {
    name: String,
    age: i64,
}

let mut stmt = conn.prepare("SELECT * FROM users WHERE id = ?1").await?;
let row = stmt.query([1]).await?.next().await?.unwrap();
let user = de::from_row::<User>(&row)?;

Batch Transactions

conn.execute_batch(r#"
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL
  );

  INSERT INTO users (name) VALUES ('Alice');
  INSERT INTO users (name) VALUES ('Bob');
"#).await?;

Interactive Transactions

Default

let tx = conn.transaction().await?;

tx.execute("INSERT INTO users (name) VALUES (?1)", ["Alice"]).await?;
tx.execute("INSERT INTO users (name) VALUES (?1)", ["Bob"]).await?;

tx.commit().await?;

Advanced control

use libsql::TransactionBehavior;

let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate).await?;

tx.execute("UPDATE users SET age = age + 1 WHERE id = ?1", [1]).await?;

tx.commit().await?;

더 알아보기 (Learn more)