Skip to content

✍️ 필사 모드: The SQLite Renaissance of 2026 — How a 24-Year-Old Single-File Database Became the Hottest Infrastructure (libSQL, Turso, LiteFS, D1, mvSQLite Deep Dive)

English
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

Prologue — Why Did a 24-Year-Old Database Get Hot Again

Look at a 2026 infrastructure stack and you see an odd tableau. On one side, Postgres 18 is polishing multi-master logical replication. On another, distributed NewSQL promises global consistency. But the loudest trend is neither of those — SQLite, the embedded library D. Richard Hipp shipped in 2000 as a single file, is back at the center of the stage.

SQLite is not a database. It is a library. That is the secret of this revival.

It sounds strange but reconsider. The two macro pressures on 2026 infrastructure are (a) compute moving to the edge and (b) local-first UX. Both have the same answer — "eliminate the network round-trip". And to eliminate the network, data has to be next to the code. What kind of database sits next to your code? A local file. SQLite has always been a local file.

This post dissects how a 24-year-old single-file database became 2026's most interesting infrastructure category. Exactly what is new — the libSQL fork and Turso's embedded replicas, Cloudflare D1, the now-sunset LiteFS, FoundationDB-backed mvSQLite, Litestream backup — and what topology made SQLite into an "edge database". And honestly, when SQLite is right and when it is absolutely wrong.


1. The SQLite-at-the-Edge Thesis — Why Now

Let us be clear. SQLite is not new. 1.0 shipped in 2000. It is in every Android, iOS, macOS, every browser, almost every IoT device. It is the most-deployed database on Earth. So why has it suddenly become a 2026 "trend"?

Three things happened simultaneously.

First, edge compute went mainstream. Cloudflare Workers, Deno Deploy, Vercel Edge Functions, Fly.io — code moved next to users. But the database? Still in us-east-1. A user in Seoul calls a function running in a Tokyo edge that then makes a 200ms round-trip to a Postgres in us-east-1. The whole point of the edge is gone.

Second, the local-first movement matured. Ink and Switch's 2019 essay seeded a wave that, by 2026, has crystallized into real products — parts of Linear, Figma, Notion. The promise: "works offline, instant, your data on your device." That requires data inside the client. The embedded SQL DB that fits in a client? SQLite.

Third, the myth that "scale-out is the answer" cracked. In the early 2020s every company chanted "distributed distributed distributed", but by 2026 nearly everyone has realized — most apps do not need distributed consistency. They fit on one node. And a single-node database makes operations almost embarrassingly simple.

The intersection of those three is SQLite. Local file, embedded, small, trustworthy. And the new tooling of the mid-2020s started patching SQLite's traditional weaknesses — distribution, replication, backup. That is the essence of the renaissance.


2. libSQL and Turso — The "Embedded Replica" Invention

If you have to pick the single most influential piece of this revival, it is Turso's embedded replica model.

libSQL — The Open Fork of SQLite

SQLite itself is open source but does not accept external contributions. The SQLite team's position is "the code is in the public domain, but we write it." That created an itch — people wanted to add features (pluggable storage backends, native replication, better concurrency) to SQLite but had no path.

So around 2022 to 2023 Turso (then ChiselStrike) created libSQL, a real fork. MIT-licensed, open to external contributions, and most importantly — adding a native network protocol (HTTP and WebSocket via "Hrana"), user-defined functions, and embedded replicas.

Embedded Replicas — The Model Itself Is New

Traditional model: app to network to DB. Every query round-trips.

Turso's embedded replica: there is a SQLite file inside your app. Reads happen on that local file — microseconds. Writes go to a remote primary, and the primary pushes change deltas (WAL frames) back to sync your local file in the background.

+------------------------+                    +------------------+
|       App (e.g. edge)  |                    |  Turso primary   |
|  +------------------+  |  Reads: local disk |  (global primary)|
|  | libSQL client    +--+   sub-millisecond  |                  |
|  |  + local .db     |                       |                  |
|  +--------+---------+                       |                  |
|           | Writes (HTTP / Hrana) --------->|                  |
|           | <-- WAL frames (background sync)|                  |
+------------------------+                    +------------------+

What is so new here? It is the first time the asymmetry of reads and writes is honestly embraced. Most web apps are 99 percent reads. If you can make reads sub-millisecond, you can afford the round-trip on writes. The classic "read replica" was always across the network. Turso's embedded replica brings that replica into the app process as a file on disk.

Code — Turso Embedded Replica Setup

// pnpm add @libsql/client
import { createClient } from '@libsql/client'

const db = createClient({
  // Local SQLite file (it really exists on disk)
  url: 'file:local.db',
  // Remote primary — writes go here
  syncUrl: 'libsql://my-db-myteam.turso.io',
  authToken: process.env.TURSO_AUTH_TOKEN,
  // Background sync cadence
  syncInterval: 60, // seconds
})

// First sync — pull remote state into the local file
await db.sync()

// Read — straight from the local file. No network.
const rows = await db.execute('SELECT * FROM posts WHERE published = 1')

// Write — goes remote, then the local file catches up on next sync
await db.execute({
  sql: 'INSERT INTO posts (title, body) VALUES (?, ?)',
  args: ['Hello', 'World'],
})

// You can also force-sync
await db.sync()

The implications are large, the largest being — reads are as fast as local file IO. A SQLite index lookup on SSD is in microseconds. No distributed DB's cache layer can beat that. The trade-off is consistency. The local replica only sees data as of the last sync. If you must see your own write immediately, you turn on read-your-writes mode (that one query round-trips to the primary), and workloads that need strong consistency should just use remote mode.

The Turso Platform Itself

Turso layered a managed service on top. A generous free tier (hundreds of millions of rows free), database branching (branch your DB like a Git branch), and multi-region primaries are available. Since 2024 Turso's biggest bet has been per-tenant databases — one DB per user. SQLite is light enough that hundreds of thousands of tiny DBs are feasible. A new model for multi-tenancy.


3. LiteFS — Fly's Distributed SQLite Experiment and Its Sunset

Fly.io tried a different answer in 2022 — LiteFS, a FUSE filesystem layer.

The LiteFS Idea

Mount a plain SQLite file on a FUSE filesystem, and LiteFS intercepts every transaction (WAL frame) and replicates it to other nodes. The app does not know it is in a distributed environment — it just opens a SQLite file. One node is the "primary" and all writes are routed there (the LiteFS Proxy handles this automatically). Other nodes receive replicas in near-real-time.

The biggest attraction was no app-code changes. Rails, Django, Phoenix — any framework that can open a SQLite file ran in distributed mode on LiteFS.

The Sunset

Then in mid-2024, Fly announced that LiteFS development was effectively paused, and through 2025 the signals got stronger that it was no longer recommended for new users (the phrase "maintenance mode" comes up frequently in official posts). Technically the cited reasons were the complexity of FUSE itself, the difficulty of multi-primary, and operational burden. Fly's managed SQLite vision moved in other directions (a stronger Fly Postgres, partnerships such as Tigris, and so on).

What to Take Away

LiteFS's sunset is not a failure of the idea. LiteFS was one of the cleanest answers to "how do you distribute SQLite" — app-transparent, single-primary, WAL-level replication. Even though it did not survive as a managed offering, libSQL and D1 inherit pieces of that design vocabulary — primary plus async replication, WAL-frame-level sync.

If you are starting a distributed SQLite project today, I would not recommend LiteFS. But those design docs are still worth reading.


4. Cloudflare D1 — The Managed Answer for Edge SQLite

Cloudflare's answer is different. D1 brings SQLite itself next to the Workers runtime.

The D1 Model

  • Storage unit: a SQLite database.
  • Hosting: on Cloudflare's global network, modeled as a primary region plus automatic read replicas.
  • Access: via Workers bindings (env.DB.prepare(...)). There is also an HTTP API, but the primary model is in-Workers calls.
  • Status: as of 2026 it is GA, with "Global Read Replication" placing read replicas on multiple continents automatically. Per-database size is in the single-digit GBs (around 10GB today), so it fits per-application or per-tenant models better than giant monolithic DBs.

Code Example — Workers + D1

// Assumes wrangler.toml has a D1 binding
// [[d1_databases]]
// binding = "DB"
// database_name = "blog"
// database_id = "..."

export interface Env {
  DB: D1Database
}

export default {
  async fetch(req: Request, env: Env): Promise<Response> {
    const url = new URL(req.url)
    const slug = url.pathname.slice(1)

    // prepare + bind — safe from SQL injection
    const stmt = env.DB.prepare(
      'SELECT title, body, published_at FROM posts WHERE slug = ?1 LIMIT 1'
    ).bind(slug)

    const row = await stmt.first<{ title: string; body: string; published_at: string }>()

    if (!row) return new Response('Not found', { status: 404 })

    return Response.json(row)
  },
}

The key is that env.DB is not a network handle but a binding the runtime injects. Workers and D1 run on the same infrastructure, and Cloudflare hides routing, pooling, and replication. From the developer's point of view, there is just "SQL inside the edge function."

Trade-offs

  • Write consistency: serialized on a single primary. No multi-region writes. No promise of global consistency.
  • Transactions: interactive transactions are limited. The batch API is preferred — bundle multiple statements into one call.
  • Size: single-digit GBs per DB. Larger data partitions across R2 plus D1 metadata.
  • Lock-in: D1 shines inside the Workers ecosystem. Elsewhere the appeal shrinks.

D1's real value is "if you use Workers, you get persistence with almost no friction". It is the most integrated implementation of SQLite-at-the-edge.


5. Other Paths for Distributed SQLite — mvSQLite and rqlite

The three above (libSQL/Turso, LiteFS, D1) are the mainstream, but there are more interesting experiments.

mvSQLite — Backed by FoundationDB

mvSQLite swapped SQLite's entire storage engine (VFS layer). Data is not on local disk but in FoundationDB. FoundationDB is the distributed transactional key-value store Apple uses, offering serializable transactions and horizontal scale.

Result: the SQL interface is plain SQLite, but the underlying storage is distributed transactional KV. The single-DB size cap is gone, and multiple nodes can simultaneously read and write the same DB. The trade-off is operations — FoundationDB itself is not trivial to operate.

mvSQLite does not see heavy production adoption, but its design vocabulary — keep SQLite's API and only distribute the storage — is powerful.

rqlite — SQLite on Top of Raft

rqlite puts the Raft consensus algorithm in front of several SQLite instances to get strong consistency. All writes go to the Raft leader, and once the log is replicated, the entries are applied to each node's SQLite.

Characteristics:

  • A CP system (CAP prioritizing consistency).
  • Cluster size is typically three to seven nodes.
  • Self-hosted rather than managed.
  • Exposed as HTTP.

rqlite shines for edge devices (IoT, industrial gateways), small clusters, and embedded distributed scenarios rather than for giant companies. It is the right answer for "too small to justify a real distributed DB but single-node SQLite is too risky."

The Comparison Matters — Not All the Same

SystemPrimary design axisWrite topologyConsistency
libSQL / TursoEmbedded replicas plus managedSingle primary, async replicationEventual (read-your-writes optional)
LiteFSFUSE-transparent replication (sunset)Single primary, async replicationEventual
Cloudflare D1Edge managed plus read replicasSingle primary, automatic read replicasEventual (reads), serialized (writes)
mvSQLiteFDB-backed distributed SQLMulti-writer, FDB consensusSerializable (FDB guarantee)
rqliteSQLite on RaftLeader writesLinearizable (Raft guarantee)
Plain SQLite plus LitestreamSingle node plus S3 backupSingle writer(Backup and restore only)

This table is the heart of the chapter — "SQLite-based" hides very different designs underneath. What you want to solve decides what you pick.


6. Litestream — The Durable Backup Beneath Everything

What if you do not want distribution? You still need backups. Litestream took that role.

What It Does

Litestream streams the SQLite WAL (Write-Ahead Log) to S3 (or compatible storage) in near-real-time. It runs as a sidecar process. The app does not know it is being backed up.

# One-line setup
litestream replicate /var/lib/app/data.db s3://my-bucket/backups/data.db

# Restore
litestream restore -o /var/lib/app/data.db s3://my-bucket/backups/data.db

Restore is PITR (Point-In-Time Recovery) — you can roll back to any past instant. RPO (Recovery Point Objective) is in seconds.

Why It Matters

Litestream broke the last justification for the "single-node SQLite is risky" objection. Disk dies, instance vanishes, and within minutes you restore from S3. The operating model is shockingly simple — one app, one SQLite file, one Litestream sidecar. Done.

After LiteFS's sunset, Fly and other PaaS providers have leaned more on Litestream. "Instead of complex distributed SQLite, single-node plus Litestream is better for 90 percent of cases" is the position.

The author (Ben Johnson at Fly.io) also created LiteFS, and he himself often says "Litestream is enough for most apps." That is an honest insight.


7. Bun SQLite and Native Runtime SQLite — Another Axis of the Revival

There are also changes at the runtime level.

Bun's bun:sqlite

Bun ships bun:sqlite as a built-in module. Synchronous API, very fast, zero dependencies.

import { Database } from 'bun:sqlite'

const db = new Database('app.db', { create: true })

db.exec(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY,
    email TEXT UNIQUE,
    created_at INTEGER DEFAULT (strftime('%s', 'now'))
  )
`)

// Prepared statement — compiled once even if called N times
const insert = db.prepare('INSERT INTO users (email) VALUES (?)')
const tx = db.transaction((emails: string[]) => {
  for (const e of emails) insert.run(e)
})
tx(['a@x.com', 'b@x.com', 'c@x.com'])

// Query
const all = db.query('SELECT id, email FROM users').all()
console.log(all)

Why is this new? In the Node.js world, using SQLite required an external package (better-sqlite3, node-sqlite3). Since Node.js 22+, the experimental node:sqlite ships in core. Runtimes are starting to treat SQLite as a first-class citizen.

What It Means

The implication of this trend — "there is a SQL DB inside my app" is no longer weird. No external dependencies, one-line import. It pairs naturally with the embedded-replica model above. The runtime knows SQLite, and the library does the sync on top.

Deno Too, and Everywhere Else

Deno also ships a standard SQLite module. WASM builds of SQLite (sql.js, wa-sqlite) run in browsers. Every runtime knows SQLite. That ubiquity makes SQLite an "exchange standard" — the same file can be read and written by the server, the client, and the edge.


8. Local-First Apps — The Paradigm SQLite Enables

If everything above is "the revival on the server side", the bigger wave is on the client. Local-first apps.

The Definition

To summarize Ink and Switch's definition, a local-first app satisfies these properties:

  1. Instant (no network round-trip).
  2. Works offline.
  3. Syncs across devices.
  4. Supports collaboration.
  5. Data ownership stays with the user.
  6. Security and privacy by default.
  7. Long-lived (data outlives the service).

That requires data persistent inside the client. The SQL engine inside? SQLite.

The Two Axes Are Different — "Local-First" and "CRDT"

A common misconception to flag — "local-first" is not the same as "CRDT". They are orthogonal.

  • Local-first is a promise about data topology — data lives on the client, the app works without a network.
  • CRDT (Conflict-free Replicated Data Type) is an algorithm to resolve conflicts when multiple clients edit the same data simultaneously.

For no-collaboration cases or single-user multi-device scenarios (a personal note app), simple last-write-wins or vector clocks suffice. CRDTs become necessary for multi-user concurrent editing (Figma, Linear, etc.).

SQLite is the persistence foundation for local-first. The CRDT engines (Automerge, Yjs) and SQL-native sync engines (ElectricSQL, Replicache, PowerSync) handle the sync on top.

SQL-Native Sync Engines

  • ElectricSQL — bidirectional sync between Postgres and SQLite. Changes accumulate inside SQLite and merge with Postgres in the background. Conflicts resolve via Rich-CRDT (a registered tree of operations).
  • Replicache (Rocicorp) — its own data model. The client uses IndexedDB (or SQLite WASM), and the server only has to implement push and pull endpoints.
  • PowerSync — Postgres-based, SQLite on the client. Competes with and resembles ElectricSQL.

What they have in common — SQLite on the client, Postgres on the server, sync layer in between. The SQLite revival is not just server-side; SQLite holds one end of this client-server topology as well.


9. When SQLite Is Right, and When It Is Wrong

By now this could read as SQLite triumphalism. It should not. Be honest.

When SQLite Is Right

  1. Read-heavy app whose traffic fits on one node. Most websites, most SaaS back-offices, almost all blog and docs sites. If "under 10k QPM and a single instance is enough", SQLite is often faster (no network round-trip).
  2. Edge-native apps. The Cloudflare Workers plus D1 or the Fly.io plus Litestream model. Data next to code is natural.
  3. Embedded and IoT. Industrial gateways, device-local logging. SQLite's day job.
  4. Local-first clients. Persistent storage for desktop, mobile, PWA apps.
  5. Per-tenant isolation. One DB per tenant. SQLite is light enough that hundreds of thousands are feasible. Design patterns from parts of Notion, Linear, and Turso.
  6. Scratch space for analytical queries. DuckDB fits better, but simple cases work with SQLite too.
  7. Isolated DBs for tests. In-memory SQLite instead of Postgres. Fast and clean.

When SQLite Is Wrong

  1. Global single primary plus simultaneous multi-region writes. If you need many regions writing to a single DB concurrently at sub-millisecond level, almost every SQLite-based system has a single primary. Multi-region writes are the domain of Spanner, CockroachDB, YugabyteDB, FoundationDB — real distributed systems.
  2. Write throughput beyond one core. SQLite has a single-writer lock on writes (even in WAL mode, transactions serialize). If you need tens of thousands of sustained writes per second, look elsewhere.
  3. OLTP that holds very long transactions. Long transactions block other writes. Postgres MVCC is much more graceful.
  4. A huge single DB. D1 caps in the single-digit GBs, libSQL and Turso scale to tens to hundreds of GB but TB-scale single DBs feel awkward. mvSQLite could be the answer, but at that point reconsider distributed OLTP.
  5. Complex analytical queries (OLAP). The SQLite optimizer is tuned for OLTP. Heavy analytics belong to DuckDB, ClickHouse, Snowflake.
  6. Multi-writer apps that strongly require data integrity. If you need real concurrent-write isolation levels, Postgres is the right answer.
  7. Full-text search heavy. SQLite's FTS5 is great but not Elasticsearch or Meilisearch or Typesense level.

Decision Tree

Start:
  Does it fit on a single node? (e.g. under 10k QPS, under 100GB)
    +-- Yes -> Need multi-region distribution?
              +-- No  -> SQLite plus Litestream (simplest)
              +-- Yes -> What is the read/write ratio?
                         +-- Read-heavy -> libSQL/Turso (embedded replicas)
                         +-- Balanced   -> D1 (on Workers) / Postgres plus read replicas

    +-- No  -> What is the workload?
                +-- Distributed OLTP -> CockroachDB / Spanner / YugabyteDB
                +-- OLAP / analytics -> ClickHouse / Snowflake / BigQuery
                +-- Multi-tenant     -> Seriously consider per-tenant SQLite

The most common answer in this tree is the first branch — most apps fit on one node. Accepting that fact unlocks a huge improvement in operational simplicity.


10. The Reality of Operations — What to Really Care About

Common pitfalls in operating SQLite-based systems.

WAL Mode by Default

PRAGMA journal_mode=WAL;. Concurrent read-write throughput improves dramatically. Almost every library defaults it on, but if you built the setup yourself, double check.

Trade-offs of PRAGMA synchronous

  • FULL (default): safest, slowest.
  • NORMAL: recommended in WAL mode. Very safe and much faster.
  • OFF: dangerous. Not recommended.

Backups Are Litestream or Equivalent, No Exception

The biggest risk of a single-node SQLite is disk or instance loss. Use Litestream to lock in a minute-scale RPO. EBS snapshots are a fine complement.

Migration and App Deploy Decoupled

SQLite migrations are usually fast, but ALTER TABLE on a large table takes a lock. For big changes use the new-table-plus-backfill-plus-swap pattern.

Sync Cadence for Embedded Replicas

For Turso embedded replicas, setting syncInterval too short (one second) wastes network and CPU. 30 to 60 seconds is a good starting point, and where immediate visibility after a write matters, use an explicit db.sync() or read-your-writes mode.

Metadata Management for Per-Tenant DBs

If you adopt one-DB-per-tenant, you need routing metadata for "which tenant DB is on which instance and region". That metadata itself lives in one central DB (typically Postgres or a larger SQLite).


Epilogue — The Revival Is Not an Accident

A 24-year-old library becoming the hottest infrastructure is not an accident. Three things happened simultaneously — edge compute became universal, the local-first movement matured, and the realization that "distributed may not be the answer" sank in.

One-line summary of this post: SQLite is not a new tool but an old one that enables a new topology. Turso's embedded replicas, Cloudflare D1's edge integration, Litestream's backup foundation, native SQLite in Bun and Node — they all point the same way. Put your data next to your code.

Checklist — Before Starting on a SQLite-Based System

  • Does it fit on a single node? (Estimate QPS, data size.)
  • Did you measure the read/write ratio? (90/10 or higher is very favorable.)
  • Is there a backup strategy? (Litestream or equivalent.)
  • Did you enable WAL mode and synchronous=NORMAL?
  • Did you decide on a migration pattern?
  • If you need multi-region, did you choose libSQL/Turso or D1?
  • Does the operational simplicity offset losing other DBs' distribution features?

Anti-Patterns

  • Refusing to start because "SQLite is a toy DB". Notion, Linear, Cloudflare and others run it in production.
  • Conversely, treating SQLite as a hammer for everything. Respect chapter 9's "wrong cases".
  • A single-node SQLite without backups. Disks die.
  • Expecting strong consistency from embedded replicas. The model itself is eventually consistent.
  • Adopting LiteFS in a new project. It is sunsetting. Look at libSQL or D1.
  • Calling db.sync() too often. 60 seconds plus explicit calls is usually a good mix.
  • Assuming all distributed SQLite systems give the same guarantees. Reread the table — rqlite is linearizable, Turso is eventual, D1 is serialized writes plus eventual reads.

Coming Up Next

The next two posts will cover sibling topics of the SQLite renaissance:

  1. Building local-first apps in practice — bidirectional Postgres-to-SQLite sync with ElectricSQL and PowerSync. Conflict resolution, offline queues, mobile clients.
  2. The 2026 map of databases — the SQLite renaissance, Postgres 18's new features, DuckDB's analytics invasion, the reality of NewSQL, and the trend of vector DBs being absorbed back into Postgres.

The fact that a 24-year-old single-file library sits in the most interesting place reaffirms one of our industry's lessons — simplicity becomes trendy again. If you wait long enough.


참고 / References

현재 단락 (1/231)

Look at a 2026 infrastructure stack and you see an odd tableau. On one side, Postgres 18 is polishin...

작성 글자: 0원문 글자: 23,089작성 단락: 0/231