본문 바로가기
데이터베이스

SQLite의 한계를 넘다: libSQL과 Turso가 여는 엣지 분산 데이터베이스의 미래

by forward error correction Circle 2026. 5. 28.
반응형
목차 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의 절대 크기·전 세계 동시 쓰기 일관성이 비용의 주체였던 워크로드에는 비싼 거래다."
반응형