반응형
목차
Turso(libSQL)란? — 왜 필요한가, 정의
Turso(libSQL)의 특징
동작 방식 — 구성 요소와 데이터 흐름
구성 및 흐름도 — 단계별 처리
설치 방법
사용 방법 — 코드/설정과 운영 고려사항
자주 쓰는 명령어와 사례
활용 방안 — 대안 비교와 "언제 쓰면 안 되는가"
1. Turso(libSQL)란? — 왜 필요한가, 그리고 정의
왜 필요한가 — SQLite의 90년대식 제약 4가지
SQLite는 세상에서 가장 많이 배포된 데이터베이스다. 모든 iOS·Android 앱, 모든 브라우저, 모든 Mac·Windows에 들어 있다. 그러나 그 사용성의 대가로 네 가지 제약을 들고 다닌다.
① 단일 라이터(single writer) — 어느 순간에도 한 프로세스만이 DB 파일에 쓸 수 있다. WAL 모드로 동시 읽기는 가능하지만, 쓰기 동시성은 1이다.
② 네트워크 프로토콜이 없다 — SQLite는 라이브러리이다. 같은 머신의 같은 파일을 여는 코드만이 접근할 수 있다. 클라이언트–서버 모델이 없다.
③ 복제(replication)가 없다 — SQLite 자체에 replica 개념이 없다. litestream 같은 외부 도구로 WAL을 S3에 흘리는 단방향 백업이 사실상 표준이었다.
④ 멀티 테넌시·멀티 리전이 자연스럽지 않다 — DB 한 개당 한 파일이라 "사용자 1명당 1 DB"는 좋은 멘탈 모델이지만, 운영(로드밸런싱·접근 제어·동기화·백업)을 직접 짜야 한다.
"SQLite ergonomics는 좋다. 그러나 클라우드 시대의 4가지 요구(네트워크·복제·멀티 테넌시·엣지 0ms 읽기)를 못 채운다." Turso는 이 정확한 빈자리를 메우는 시도다.
기존 방식의 한계는 어떻게 부정되어 왔는가
접근 해결한 부분 그래도 남은 한계
리튠 SQLite + Litestream S3로 WAL 스트리밍 백업, point-in-time 복구 읽기 레플리카가 아님(복원 시점에만 복원), 양방향 복제 아님, 네트워크 프로토콜 없음
Cloudflare D1 Workers에서 SQL 호출 가능, 전 세계 엣지 배포 한 DB 사이즈·QPS 한도, 임베디드 레플리카 없음(엣지로 호출만), HTTP 한 모델
PostgreSQL/MySQL 서버리스 (Neon·PlanetScale) Connection pooling, branch, 자동 스케일 네트워크 RTT가 본질, "한 사용자당 1 DB" 모델이 비싸다
RDB + 캐시 레이어 (Redis, KeyDB) 읽기 latency 감소 SQL 일관성·트랜잭션·관계 손실, 읽기/쓰기 모델 분기
Turso(libSQL) + 임베디드 레플리카 SQLite 그대로 + 네트워크 + 양방향 복제 + 0ms 읽기 + 테넌트당 DB 읽기 eventual consistency, write는 primary로 forward, libSQL이 SQLite와 분기
정의 — Turso(libSQL)란 무엇인가
Turso는 SQLite를 포크한 libSQL을 코어로 쓰고, Rust 서버 sqld를 통해 네트워크 액세스를 제공하며, Hrana(WebSocket/HTTP) 프로토콜로 클라이언트와 통신하고, 임베디드 레플리카로 클라이언트 측에서 0ms 로컬 읽기를 제공하는 엣지 분산 SQL 데이터베이스다. 매니지드 서비스 이름은 Turso, 오픈소스 코어의 이름은 libSQL/sqld이며, 둘 다 MIT 라이선스 하에 개발된다(2024 이후 vector search·multi-tenant DB groups·schema migrations 자동화 등이 정식 기능).
역사 — ChiselStrike에서 libSQL로
2022 — ChiselStrike(서버리스 TS DB 시도)가 사명을 Turso로 바꾸고, SQLite 위로 방향을 튼다.
2023 — SQLite의 폐쇄적 컨트리뷰션 모델(외부 PR을 받지 않음)에 막혀, SQLite amalgamation을 포크해 libSQL을 발족. 동시에 Rust 서버 sqld를 공개한다.
2023말 — 임베디드 레플리카(Embedded Replica) 개념 발표 — 클라이언트에 진짜 SQLite 파일을 두고 frame 단위로 동기화한다.
2024 — libSQL Vector 정식 출시(F32_BLOB·vector_distance_cos), 한 계정 안 멀티 DB(테넌트당 1 DB)·schema migrations 자동화·Webhook 트리거 추가.
2025–2026 — Database Group·Replica Locations·WASM UDF·JS UDF·encryption-at-rest가 기본화, 매니지드와 self-host 양쪽이 안정화 단계로 진입.
2. Turso(libSQL)의 특징
축 특징 왜 중요한가
코어 엔진 libSQL (SQLite amalgamation 포크) SQLite 호환 SQL·트랜잭션·WAL을 그대로 유지하면서 외부 PR·확장을 받음
서버 구현 sqld (Rust 단일 바이너리) JVM·외부 코디네이터 없음. ./sqld 한 줄로 시작
네트워크 프로토콜 Hrana over WebSocket / HTTP / Postgres-wire(실험) 드라이버가 가볍다. 브라우저·Workers 같은 제한적 런타임에서도 직결
복제 단위 WAL frame (페이지) 스트리밍 SQL 문장이 아니라 물리적 페이지가 흐르므로 결정적·idempotent
임베디드 레플리카 클라이언트 측 진짜 SQLite 파일 읽기는 로컬(0ms RTT), 쓰기는 primary로 forward → "엣지에서 풀 SQL"
Vector 1급 시민 F32_BLOB(N), vector_distance_cos/l2/dot, ANN 인덱스 벡터 DB를 별도로 두지 않고도 SQL 안에서 임베딩 검색
멀티 테넌트 DB 한 계정 안에 수만~수십만 DB(테넌트당 1 DB) "per-user DB" 패턴을 자연스럽게 — schema·격리·백업 단위가 동시에 깔끔해짐
스키마 자동화 schema migrations(parent → child schema), Database Branch 스키마 변경을 모든 테넌트 DB에 안전하게 전파
UDF·확장 WASM UDF, JS UDF, SQLite extension 로딩 SQL 안에서 사용자 함수와 외부 라이브러리를 호출
저장 위치 로컬 디스크 + 옵션으로 S3 백업, replica는 SSD 권장 page-level WAL이라 백업·재해 복구가 단순
SDK JS/TS · Python · Rust · Go · PHP · Swift · Kotlin · C/C++ · .NET Bun·Cloudflare Workers·Vercel·Deno 같은 제한적 런타임에서도 동작
관측성 Prometheus endpoint, OpenTelemetry exporter, structured logs 표준 관측성 스택으로 합류
라이선스 MIT (libSQL · sqld 모두), 매니지드 Turso는 별도 SaaS 상업 사용 자유. SSPL/BSL의 함정 없음
위치 비교 Cloudflare D1·PlanetScale·Neon·CockroachDB·Litestream과 다른 축 "SQLite + 네트워크 + 임베디드 레플리카 + 멀티 테넌시"가 차별점
임베디드 레플리카의 진짜 의미 — "0ms 읽기"의 정확한 정의
클라이언트 디스크에 진짜 SQLite 파일이 존재한다. 어플리케이션은 SELECT를 로컬 파일에 던진다 → 네트워크 RTT 0.
primary 서버는 WAL frame이 추가될 때마다 그것을 fan-out한다. 임베디드 레플리카는 frame을 받아 자기 SQLite 파일에 그대로 적용한다.
INSERT/UPDATE/DELETE는 primary로 forward되어 적용되고, 결과로 새 frame이 만들어져서 다시 push된다 → 읽기는 자기 자신이 본 시점(read-your-own-writes)을 옵션으로 보장.
동기화 주기는 periodic sync(예: 5초) 또는 per-request sync(write 직후 read 일관성을 위해)를 선택한다 — 정확성 vs 트래픽의 트레이드오프.
3. Turso(libSQL) 동작 방식 — 구성 요소와 데이터 흐름
구성 요소
레이어 구성 요소 역할
코어 엔진 libSQL (forked SQLite amalgamation) SQL 파서·플래너·executor·B-tree·페이지 캐시·WAL — SQLite와 동일
서버 호스트 sqld (Rust) libSQL을 임베드, Hrana 프로토콜 종단, primary/replica 역할 결정
네트워크 프로토콜 Hrana over WebSocket · HTTP/JSON · Postgres wire(실험) SQL 문장·파라미터·row를 가볍게 직렬화. 한 connection 안에서 multiplex
복제 평면 WAL frame replicator primary의 WAL frame을 replica/embedded replica에 push, ACK·재전송 관리
임베디드 레플리카 client-side libSQL + 로컬 .db 파일 로컬 SELECT는 자기 파일에서 즉시 응답, write는 primary로 forward
관리 평면 turso CLI · Turso Console · Admin API DB·그룹·replica location·token·schema 자동화 진입점
스키마 매니저 parent schema → tenant DB 전파 한 schema 변경을 수만 개 테넌트 DB에 안전하게 적용
벡터 인덱스 libSQL Vector (DiskANN 계열) F32_BLOB(N) 컬럼과 ANN 인덱스로 SQL 안에서 ANN 검색
UDF 런타임 WASM 호스트 · JS(QuickJS) 호스트 SQL 호출 안에서 사용자 함수 실행 (격리·메모리 제한 내)
관측성 Prometheus exporter · OTel · structured logs 표준 관측성 스택으로 합류
데이터 흐름 — 한 트랜잭션이 디스크와 엣지에 도달하기까지
접속 — 클라이언트가 WebSocket(wss://) 또는 HTTP로 sqld에 접속, JWT 토큰으로 인증.
SQL 발사 — INSERT/UPDATE/DELETE면 primary로 forward, SELECT면 로컬 임베디드 레플리카(있으면) 또는 가까운 replica가 응답.
primary write — primary의 libSQL이 WAL에 새 frame을 append하고 커밋한다.
frame fan-out — replicator가 새 frame을 모든 replica·embedded replica에 push, 수신자는 자기 WAL에 같은 frame을 적용.
read-your-own-writes — 옵션으로, write 직후 클라이언트가 ACK된 frame index를 기다렸다가 다음 read를 수행 → 자기 쓰기를 즉시 본다.
점검 — Prometheus·OTel로 frame lag·primary connection·WAL size를 노출.
여기서 중요한 트레이드오프
장점 — 읽기 0ms RTT
임베디드 레플리카가 있으면 SELECT는 자기 디스크에서 즉시 반환. 글로벌 사용자에게도 ms 단위 응답.
장점 — SQLite 그대로의 ergonomics
풀 SQL, 트랜잭션, 단일 파일, 마이그레이션이 그대로. 새 멘탈 모델 없이 바로 시작 가능.
장점 — 테넌트당 DB
"사용자 한 명 = DB 한 개" 모델이 운영 단위(권한·백업·격리)와 맞물려 깔끔. 멀티테넌시의 잡다함이 사라진다.
단점 — eventual consistency
frame이 도착할 때까지 replica의 데이터는 stale. read-your-own-writes는 옵션이지만 latency 비용을 부른다.
단점 — write가 primary로 모임
Edge에서 SELECT는 빠르지만 INSERT는 1 RTT. write-heavy 글로벌 워크로드는 다른 카드(CockroachDB·YugabyteDB)가 낫다.
단점 — libSQL ↔ SQLite 분기
WAL 포맷 확장·user-defined types로 upstream과 호환은 점점 옅어진다. 다른 SQLite 도구와 100% 호환을 가정하면 함정.
4. Turso(libSQL) 구성 및 흐름도 — 단계별 설명과 실제 처리 흐름
전체 구조도 — primary, replica, embedded replica의 3-tier 토폴로지
┌──────────────────────────────────────────────┐
│ Application Code │
│ (Bun · Workers · Vercel · Node · Python) │
└─────────────┬────────────────────┬─────────────┘
│ SELECT (local) │ INSERT/UPDATE/DELETE
▼ │
┌─────────────────────────┐ │
│ Embedded Replica (libSQL)│ │ forward to primary
│ ./local.db (real SQLite) │ │
└───────────┬───────────────┘ │
▲ │
WAL frames │ push │
│ ▼
┌────────────────────────────────────────────────────┐
│ sqld — Replica node (region B) │
│ libSQL (read replica) ◀── frames ◀── primary │
└────────────────────────────────────────────────────┘
▲
│ WAL frame replication (Hrana)
│
┌────────────────────────────────────────────────────┐
│ sqld — Primary node (region A) │
│ ┌────────────┬─────────────┬──────────────────────┐ │
│ │ Hrana WS │ HTTP/JSON │ Postgres-wire (exp.) │ │
│ └─────┬──────┴──────┬──────┴──────────────┬───────┘ │
│ └─── command dispatcher ────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────┐ │
│ │ AuthZ / JWT / scope│ │
│ └─────────┬──────────┘ │
│ ▼ │
│ ┌────────────────────┐ │
│ │ libSQL (writer) │ WAL append + fsync │
│ └─────────┬──────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────┐ │
│ │ Disk: $DATA/dbs/{db}/data.db + .wal │ │
│ │ /dbs/{db}/wal-frames/... │ │
│ └──────────────────────────────────────┘ │
└─────────────────┬──────────────────────────────────┘
│ Prometheus / OTel
▼
┌──────────────────────┐
│ Grafana / Tempo / … │
└──────────────────────┘
한 SQL 호출이 따라가는 정확한 경로
① 임베디드 레플리카 SELECT — db.execute("SELECT ...") → 클라이언트 측 libSQL이 자기 local.db에서 결과를 반환. 네트워크 호출 없음.
② 임베디드 레플리카 INSERT — 클라이언트 라이브러리가 Hrana WebSocket으로 primary에 SQL을 forward → primary가 WAL frame을 만들고 ACK.
③ frame replication — primary가 frame을 fan-out → 모든 replica·embedded replica가 frame을 자기 WAL에 적용.
④ read-your-own-writes (옵션) — 클라이언트가 프로미스로 ACK된 frame_no를 받고, 다음 read에서 그 이상의 frame이 적용된 후 읽도록 보장.
⑤ schema 변경 — parent DB schema가 바뀌면, 모든 child(테넌트) DB에 schema migration이 큐잉되어 적용.
실제 처리 흐름 — "엣지에서 SQL을 0ms로" 한 시나리오
(0) Vercel Function이 부팅, 임베디드 레플리카가 한 번 동기화.
(1) 사용자가 페이지를 요청.
(2) Function이 SELECT * FROM products WHERE id = ?을 자기 디스크에서 실행 → 0ms.
(3) 같은 요청이 INSERT INTO views(...)를 호출 → primary로 forward, 1 RTT.
(4) primary가 frame을 push → 다음 요청부터는 다시 0ms read.
5. Turso(libSQL) 설치 방법
방법 A — Turso 매니지드 (가장 빠름)
# 1) CLI 설치 (macOS/Linux)
curl -sSfL https://get.tur.so/install.sh | bash
# Windows: scoop install turso-cli
# 2) 가입·로그인
turso auth signup
# 또는: turso auth login
# 3) DB 생성 (region 자동 선택)
turso db create my-app
# 4) 접속 정보
turso db show my-app --url # libsql://my-app-xxx.turso.io
turso db tokens create my-app --expiration 30d # JWT 발급
# 5) 셸로 즉시 SQL
turso db shell my-app
> CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT);
> INSERT INTO users(name) VALUES ('alice'), ('bob');
> SELECT * FROM users;
방법 B — sqld 단독 셀프호스트(Docker)
# 단일 노드 (primary)
docker run --rm -it \
-p 8080:8080 \
-v sqld-data:/var/lib/sqld \
-e SQLD_NODE=primary \
-e SQLD_HTTP_LISTEN_ADDR=0.0.0.0:8080 \
ghcr.io/tursodatabase/libsql-server:latest
# replica 노드 (다른 region에서)
docker run --rm -it \
-p 8080:8080 \
-v sqld-replica:/var/lib/sqld \
-e SQLD_NODE=replica \
-e SQLD_PRIMARY_URL=https://primary.example.com:8080 \
-e SQLD_HTTP_LISTEN_ADDR=0.0.0.0:8080 \
ghcr.io/tursodatabase/libsql-server:latest
방법 C — Kubernetes (StatefulSet 1개로 시작)
cat <<'YAML' | kubectl apply -f -
apiVersion: apps/v1
kind: StatefulSet
metadata: {name: sqld}
spec:
serviceName: sqld
replicas: 1
selector: {matchLabels: {app: sqld}}
template:
metadata: {labels: {app: sqld}}
spec:
containers:
- name: sqld
image: ghcr.io/tursodatabase/libsql-server:latest
ports:
- {name: http, containerPort: 8080}
env:
- {name: SQLD_NODE, value: primary}
- {name: SQLD_HTTP_LISTEN_ADDR, value: "0.0.0.0:8080"}
- {name: SQLD_DB_PATH, value: /var/lib/sqld/data.db}
volumeMounts:
- {name: data, mountPath: /var/lib/sqld}
volumeClaimTemplates:
- metadata: {name: data}
spec:
accessModes: [ReadWriteOnce]
resources: {requests: {storage: 50Gi}}
YAML
방법 D — 소스 빌드(개발자용)
git clone https://github.com/tursodatabase/libsql
cd libsql
cargo build --release -p sqld
./target/release/sqld --http-listen-addr 0.0.0.0:8080
실수 주의 데이터 디렉토리는 로컬 SSD(NVMe)에 둘 것. NFS/EFS 위 SQLite WAL은 fsync 시맨틱이 깨지기 쉬워 데이터 손상의 주요 원인이 된다. K8s에서도 local PV 또는 fast SSD 기반 StorageClass를 사용한다.
6. Turso(libSQL) 사용 방법 — 코드/설정과 운영 시 고려사항
JS/TS — 가장 짧은 클라이언트(원격 모드)
// npm i @libsql/client
import { createClient } from "@libsql/client";
const db = createClient({
url: "libsql://my-app-xxx.turso.io",
authToken: process.env.TURSO_AUTH_TOKEN!,
});
await db.execute(`CREATE TABLE IF NOT EXISTS users(
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
)`);
const r = await db.execute({
sql: "INSERT INTO users(name) VALUES (?) RETURNING id",
args: ["alice"],
});
console.log(r.rows[0]);
const list = await db.execute("SELECT id, name FROM users ORDER BY id");
console.log(list.rows);
JS/TS — 임베디드 레플리카 모드 (0ms 읽기의 핵심)
import { createClient } from "@libsql/client";
const db = createClient({
url: "file:./local.db", // 진짜 SQLite 파일
syncUrl: "libsql://my-app-xxx.turso.io", // primary
authToken: process.env.TURSO_AUTH_TOKEN!,
syncInterval: 60, // 60초마다 자동 동기화
});
// 처음 부팅 시 한 번 명시적으로 sync
await db.sync();
// 이후 SELECT는 전부 로컬에서 0ms
const recent = await db.execute(
"SELECT id, name FROM users ORDER BY id DESC LIMIT 50"
);
// INSERT는 자동으로 primary로 forward
await db.execute({
sql: "INSERT INTO users(name) VALUES (?)",
args: ["bob"],
});
// 자기 쓰기를 즉시 보고 싶으면 sync 한 번 더
await db.sync();
Python — 같은 모델
# pip install libsql-experimental
import libsql_experimental as libsql
# 임베디드 레플리카
conn = libsql.connect(
"local.db",
sync_url="libsql://my-app-xxx.turso.io",
auth_token=os.environ["TURSO_AUTH_TOKEN"],
)
conn.sync()
cur = conn.cursor()
cur.execute("CREATE TABLE IF NOT EXISTS notes(id INTEGER PRIMARY KEY, body TEXT)")
cur.execute("INSERT INTO notes(body) VALUES (?)", ("hello-turso",))
for row in cur.execute("SELECT id, body FROM notes ORDER BY id DESC LIMIT 10"):
print(row)
Vector — SQL 안에서 임베딩 검색
-- 1) 컬럼 정의
CREATE TABLE docs(
id INTEGER PRIMARY KEY,
body TEXT,
embed F32_BLOB(384) -- 384차원 임베딩
);
-- 2) ANN 인덱스 (DiskANN 계열)
CREATE INDEX docs_embed_idx
ON docs (libsql_vector_idx(embed));
-- 3) 임베딩 입력
INSERT INTO docs(body, embed)
VALUES ('hello world', vector('[0.13, -0.42, ... , 0.07]'));
-- 4) 가장 가까운 10개 (cosine distance)
SELECT id, body,
vector_distance_cos(embed, vector(?)) AS dist
FROM docs
ORDER BY dist ASC
LIMIT 10;
운영 시 고려사항
강점 SQLite의 모든 도구가 그대로 — DataGrip·DB Browser·sqlite3 CLI도 (대부분의 경우) 그대로 쓴다.
강점 관측성이 표준 — Prometheus exporter, OpenTelemetry, structured tracing이 사이드카 없이 노출.
주의 임베디드 레플리카의 sync 주기는 정확성과 트래픽의 직접 트레이드오프다. periodic 60s + write 후 즉시 sync가 합리적 기본.
주의 토큰의 scope를 좁게 발급하라. per-DB·per-action·per-expiration으로 끊어 발급, 회전(rotation)을 자동화.
주의 대형 트랜잭션은 임베디드 레플리카 모드에서 한 RTT가 길어질 수 있다 — batch는 primary 쪽에서 한 번에 처리.
함정 SQLite 호환을 100% 가정 금지 — libSQL의 vector·UDF·user-defined types는 SQLite에는 없다. 다른 SQLite 도구로 옮길 가능성을 미리 확인.
함정 write-heavy 글로벌 워크로드는 primary 단일 쓰기가 병목 — 멀티 리전 active-active 쓰기가 필요하면 다른 카드를 검토.
7. Turso(libSQL) 자주 쓰는 명령어와 사례
관리자가 자주 쓰는 turso CLI
명령 의미
turso auth signup / turso auth login 계정 가입·로그인 (브라우저로 OAuth)
turso db create my-app DB my-app 생성 (region 자동)
turso db create my-app --location nrt 도쿄 region으로 DB 생성
turso db list 모든 DB 나열
turso db show my-app --url 접속 URL 출력 (libsql://...)
turso db tokens create my-app --expiration 30d JWT 토큰 발급 (만기 30일)
turso db tokens invalidate my-app 모든 토큰 무효화 (사고 대응)
turso db shell my-app 대화형 SQL 셸
turso db replicate my-app fra 프랑크푸르트에 read replica 생성
turso db locations 지원 region 목록
turso db destroy my-app DB 영구 삭제 (확인 프롬프트)
turso group create prod --location ams "DB 그룹" 생성 (멀티 테넌트의 부모)
turso db create user-42 --group prod 그룹 안에 새 테넌트 DB 생성
turso db inspect my-app 크기·요청 수·최근 sync 상태
turso db dump my-app > my-app.sql 전 DB SQL 덤프
turso db restore my-app --from-dump my-app.sql 덤프로부터 복원
사례 1 — Bun 서버 + 임베디드 레플리카(0ms 읽기)
// bun add @libsql/client
import { serve } from "bun";
import { createClient } from "@libsql/client";
const db = createClient({
url: "file:./bun.db",
syncUrl: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN!,
syncInterval: 30,
});
await db.sync();
serve({
port: 3000,
async fetch(req) {
if (new URL(req.url).pathname === "/users") {
const r = await db.execute("SELECT id, name FROM users");
return Response.json(r.rows);
}
return new Response("ok");
},
});
사례 2 — Cloudflare Workers (HTTP 모드, 엣지)
import { createClient } from "@libsql/client/web";
export default {
async fetch(req: Request, env: Env) {
const db = createClient({
url: env.TURSO_DATABASE_URL,
authToken: env.TURSO_AUTH_TOKEN,
});
const r = await db.execute({
sql: "SELECT id, name FROM users WHERE id = ?",
args: [Number(new URL(req.url).searchParams.get("id"))],
});
return Response.json(r.rows[0] ?? null);
},
};
사례 3 — 테넌트당 DB(per-user database)
# 그룹 + parent schema
turso group create app-prod --location ams
turso db create schema-parent --group app-prod --type schema
# parent schema에 한 번 정의
turso db shell schema-parent <<'SQL'
CREATE TABLE notes(
id INTEGER PRIMARY KEY,
body TEXT NOT NULL,
ctime INTEGER DEFAULT (unixepoch())
);
SQL
# 사용자 가입 시마다 child DB 자동 생성
turso db create user-42 --group app-prod --schema schema-parent
turso db tokens create user-42 --expiration 1y > user-42.jwt
# 이후 user-42 사용자는 자기 DB만 보고 쓴다
사례 4 — 벡터 검색을 한 SQL로
-- 임베딩 1024차원, 1만 행 인덱스
CREATE TABLE docs(id INTEGER PRIMARY KEY, body TEXT, e F32_BLOB(1024));
CREATE INDEX docs_e_idx ON docs(libsql_vector_idx(e));
-- 의미 검색 + 키워드 필터를 한 쿼리에
SELECT id, body,
vector_distance_cos(e, vector(?)) AS sim
FROM docs
WHERE body LIKE ?
ORDER BY sim ASC
LIMIT 20;
사례 5 — schema migration을 모든 테넌트에 전파
# parent schema에 컬럼 하나 추가
turso db shell schema-parent <<'SQL'
ALTER TABLE notes ADD COLUMN tags TEXT;
SQL
# 자동 — 같은 그룹의 모든 child(테넌트) DB에 migration이 큐잉되어 적용
turso db inspect user-42 | jq .schema_version
사례 6 — 백업 전략
# 매니지드는 자동 PITR(Point-in-time Recovery)이 7~14일 보유
# 추가로 stream-level dump를 매일 S3에
DATE=$(date +%F)
turso db dump my-app | gzip | aws s3 cp - s3://my-bk/turso/my-app-${DATE}.sql.gz
# 셀프호스트의 경우 sqld 디렉토리는 append-friendly이라 rsync로 충분
rsync -a --delete /var/lib/sqld/ s3://my-bk/sqld/$(hostname)/
8. Turso(libSQL) 활용 방안 — 대안 비교와 "언제 쓰면 안 되는가"
유사 카테고리 비교 — 엣지/서버리스 SQL의 좌표계
제품 코어·구현 강점 포지셔닝과 차이
Turso(libSQL) libSQL(C, SQLite fork) + sqld(Rust) 임베디드 레플리카로 0ms 읽기, 테넌트당 DB, 풀 SQL+벡터 "SQLite ergonomics + 네트워크/복제/멀티 테넌시"가 차별점. MIT
Cloudflare D1 SQLite(원본) + Workers binding Workers 안에서 호출, 가까운 엣지 자동 라우팅 임베디드 레플리카 없음. HTTP 단일 모델, DB 사이즈·QPS 한도가 더 좁음
Litestream SQLite + WAL → S3 스트리밍 단방향 백업·DR이 단순, 의존성 거의 없음 네트워크 프로토콜·양방향 복제·서버 모드가 없음. "백업"이 본질
Neon (PostgreSQL) PostgreSQL + 페이지서버 + S3 풀 PG 호환, 분기(branch)·자동 스케일 네트워크 RTT 본질, "테넌트당 DB"는 비싼 모델, 임베디드 레플리카 없음
PlanetScale MySQL + Vitess(샤딩) 대규모 OLTP, 온라인 schema change 일급 거대 단일 DB에 강함. 엣지 0ms 읽기 모델 아님, 라이선스 변동 이력
CockroachDB / YugabyteDB 분산 SQL (Raft + MVCC) 멀티 리전 active-active, 강한 일관성 운영 표면적이 가장 큼. 한 노드만 띄우는 "작게 시작" 모델 아님
Supabase + PG + PgBouncer PostgreSQL 매니지드 + REST 풀 PG·RLS·realtime·storage 일체 "테넌트당 작은 DB" 패턴은 어색. 엣지 임베디드 레플리카 없음
SQLite + LiteFS (Fly) SQLite + FUSE + WAL replicator VM마다 진짜 SQLite 파일, lease primary 운영 대상이 Fly.io 중심, 다른 플랫폼으로 확장하기 어렵다
DuckDB / DuckLake OLAP 임베디드 + 레이크하우스 분석·집계·colocation 강력 OLTP·트랜잭션·실시간 read 모델이 아님 — 카테고리가 다름
같은 분야의 보완 도구
Drizzle ORM · Prisma — libSQL 드라이버를 1급 시민으로 지원, 타입 세이프 쿼리.
Hono · Elysia · Next.js · Astro · SvelteKit — 엣지 런타임 통합 예제가 가장 풍부.
Drizzle Kit · Atlas · sqlfluff — schema migration·lint·diff.
Sentry · OpenTelemetry Collector — slow query, replica lag, frame index 모니터링.
OpenAI/Cohere/Local Embeddings — libSQL Vector 컬럼 입력용 임베딩 생성기.
Litestream — sqld와 별개로, 셀프호스트 단일 노드의 S3 백업 보강 도구로 함께 쓸 수 있음.
언제 Turso(libSQL)를 써야 하는가
읽기 latency가 결정적 — 글로벌 사용자에게 0ms 읽기를 줘야 하는 데시보드·콘텐츠·프로필 페이지.
"한 사용자 = 한 DB" 모델이 자연스러운 SaaS — 메모/노트 앱, 1인 도구, 멀티 테넌트 백오피스.
Bun · Workers · Vercel · Deno 같은 엣지·서버리스 런타임에서 풀 SQL이 필요한 경우.
임베디드 시나리오에서 SQLite를 이미 쓰고 있고, 거기에 클라우드 백업·복제·서버 접근만 더하고 싶을 때.
SQL 안에서 임베딩 검색까지 한 시스템으로 처리하고 싶은 RAG 백엔드(소~중 규모).
운영 표면적을 작게 가져가야 하는 1인·소규모 팀 — Postgres·MySQL의 풀 클러스터 운영 부담을 피하고 싶을 때.
언제 Turso(libSQL)를 쓰면 안 되는가
전 세계에서 동시에 강한 일관성 쓰기가 필요한 글로벌 OLTP — primary 단일 쓰기가 병목. CockroachDB·Spanner가 적합.
한 DB의 사이즈가 거대(TB+)하고 분석성 쿼리(긴 OLAP)가 주력 — Neon·BigQuery·DuckLake·ClickHouse가 적합.
이미 Postgres 의존성이 깊다 — RLS·CTE·extensions(pgvector·PostGIS·TimescaleDB)에 의존하면 libSQL로의 호환은 부분적이다.
동시 라이터가 많은 핫 테이블(예: 글로벌 카운터·재고 차감) — 단일 primary로 모든 write가 모임. 대안은 Redis·DynamoDB·CRDT.
SQLite 호환을 1:1로 가정해야 하는 외부 시스템 — libSQL의 vector·UDF·user-defined types가 깨질 수 있다.
NFS/EFS·분산 파일시스템 위에 sqld를 두려는 시도 — fsync 시맨틱이 깨져 데이터 손상 위험.
"중요한 트레이드오프"를 한 문장으로
"Turso(libSQL)는 SQLite의 ergonomics를 지키되, 네트워크·복제·멀티 테넌시·엣지 0ms 읽기를 추가한다. 엣지 read latency가 비용의 주체였던 80% 워크로드에는 거의 공짜에 가까운 거래이고, 한 DB의 절대 크기·전 세계 동시 쓰기 일관성이 비용의 주체였던 워크로드에는 비싼 거래다."
반응형
'데이터베이스' 카테고리의 다른 글
| DB 변경도 Git처럼: Bytebase로 구현하는 데이터베이스 CI/CD (0) | 2026.05.10 |
|---|---|
| Redis 의 진정한 오픈소스 후계자, 인메모리 데이터스토어의 새로운 표준 Valkey 에 대해 알아보겠습니다. (0) | 2026.05.04 |
| 왜 PrestoSQL이 Trino가 되었을까? 특징부터 기본 실행까지 총 정리 (0) | 2026.04.30 |
| B-Tree (Balanced Tree) 에 대해 알아보겠습니다. (0) | 2026.01.07 |
| 데이터베이스에서 사용되는 인덱스(Index)에 대해 알아보겠습니다. (0) | 2026.01.06 |