Local-First SQLite, Cloud-Connected with Turso Embedded Replicas

Jamie BartonJamie Barton
Cover image for Local-First SQLite, Cloud-Connected with Turso Embedded Replicas

Embedded Replicas transform how developers use SQLite in modern distributed applications, whether serverless, traditional server-based, or on-device. At its core, an embedded replica is a local SQLite file that maintains synchronization with your primary database hosted on Turso Cloud — enabling zero-latency network reads.

Unlike traditional database replication that requires complex server setups, Turso's Embedded Replicas operate with remarkable simplicity:

  • Local Reads: Queries execute directly against the local SQLite file
  • Remote Writes: Write operations forward to the primary database*
  • Frame-Based Sync: Changes propagate as discrete units called frames
  • WAL Integration: Leverages SQLite's Write-Ahead Log mechanism for efficient updates

Using Embedded Replicas provide a few key advantages:

  • Simplified Architecture: Eliminate complex caching layers — your app connects directly to a local SQLite file
  • Network Resilience: Continue serving data even when offline or during connectivity issues
  • Cost Efficiency: Reduce bandwidth and infrastructure costs by minimizing network calls

*Local writes are coming soon – join the beta.

#Architecture Overview

The Embedded Replicas architecture consists of three main components:

#1. The Primary Database

Your primary database is hosted on Turso Cloud, which runs a SQLite-compatible server (libSQL server) that:

  • Handles write operations and maintains the source of truth
  • Manages Write-Ahead Logging (WAL) for durability and local replication
  • Provides HTTP interface for client connections

#2. Embedded Replicas

These are local SQLite files that:

  • Store a complete copy of your database
  • Execute read queries with zero network latency
  • Maintain synchronization with the primary database
  • Apply changes as they occur in the primary

#3. Turso Cloud

Turso Cloud is the orchestration layer that:

  • Manages authentication and authorization
  • Handles database creation, deletion and point-in-time recovery
  • Coordinates synchronization between primary and embedded replicas
  • Provides analytics for monitoring database performance and usage

#Try it out

Use the simulator below to perform some write operations and observe the synchronization process between devices online with manual and automatic sync intervals.

Turso Cloud

Current Frame: 4
Manual Sync
Frame: 4Last Sync: Never
Read own writes:
Interval (5s)
Frame: 2Last Sync: Never
Read own writes:

#When to Sync

While all of the synching mechanics under the hood are taken care of for you, you should consider when you sync.

The easiest way to get started is to pass your remote database URL and local SQLite file path to createClient:

const db = createClient({
  url: 'file:local.db', // Local replica path
  syncUrl: 'libsql://your-db.turso.io', // Remote database URL
  authToken: 'your-auth-token',
  syncInterval: 60, // Optional: Auto-sync every 60 seconds
});

// Zero-latency reads - no sync needed before reading
const result = await db.execute('SELECT * FROM users');

Here you can see periodic sync enabled with syncInterval: 60. This means that the client will automatically sync with the remote database every 60 seconds.

Depending on your application needs, you may want to sync sooner with db.sync() such as when your application starts up or when connectivity is restored. But you should avoid calling sync before reading the database unless data freshness is absolutely critical, this way you benefit from the zero-latency reads.

#Deployment Considerations

Ensure your deployment environment has write access to the local path for the SQLite file. While not ideal for stateless serverless platforms like AWS Lambda, Vercel, or Netlify, Embedded Replicas are great for environments like VPS/VM deployments, containerized applications (Fly.io, Koyeb, Railway, Bunny), edge computing environments, and on-device applications.

When implementing embedded replicas in your application, the complexity of frame management and checkpointing is handled automatically. Simply configure your client with the appropriate URLs.

#Synchronization: Under the Hood

The operations of how synchronization works are abstracted into Turso Cloud and libSQL Clients so that you don't need to worry about the complexity. But, if you're interested in what goes on under the hood, let's take a deep dive.

#Frames and WAL

At the heart of the replication system lies SQLite's Write-Ahead Log (WAL) and Turso's frame-based synchronization.

#WAL Structure

  • Modified database pages
  • Checksums for integrity
  • Frame sequence information
  • Generation ID for version control

#Frame Lifecycle

  1. Primary database writes changes to WAL
  2. Changes bundle into frames with sequential numbers
  3. Replicas request frames they haven't yet received
  4. Frames apply locally in strict sequence

#Synchronization States

Turso Cloud tracks two critical numbers for each replica:

  • Frame Number: The last successfully applied and persisted frame
  • Generation ID: A version number that increments on replication restarts

These numbers ensure embedded replicas can resume sync from the correct position.

#The Sync Protocol

The synchronization process follows a specific flow:

#Initial Connection

  1. Replica → Primary: Connect with the last frame number and generation
  2. Primary → Replica: Validate generation

#Frame Retrieval

  1. Replica → Primary: Request frames since last_frame_no
  2. Primary → Replica: Send available frames and metadata

#Frame Application

  1. Replica validates frame integrity
  2. Replica applies frames sequentially
  3. Replica updates local frame number

#Checks

  1. Replica verifies checksums
  2. Replica confirms sequence continuity
  3. Replica updates sync status

#Example Scenarios

Let's explore some common scenarios to better understand how frame-based synchronization works in practice.

#1. Basic Schema Changes

This example demonstrates how frames are generated and synced when making schema changes:

// Initial sync – no changes yet; no frame number is assigned
let n = db.sync().await?.frame_no();

// Make some change to the database
let conn = db.connect()?;
conn.execute("CREATE TABLE user (id);", ()).await?;

// Sync after change – now we have frame number 1
let n = db.sync().await?.frame_no();

In this scenario, the initial sync returns no frame numbers since no changes have occurred. After creating a table, the subsequent sync shows frame number 1, indicating that the schema change was captured and synced.

#2. Multiple Operations and Frame Generation

This example illustrates how multiple operations generate and sync different frames:

// Create table and perform initial sync
conn.execute("CREATE TABLE users (id)", ()).await.unwrap();

let rep = db.sync().await.unwrap();
assert_eq!(rep.frame_no(), Some(1));
assert_eq!(rep.frames_synced(), 2);

// Insert data triggers more frames to be generated
conn.execute("INSERT INTO users (id) VALUES (randomblob(4096))", ());

let rep = db.sync().await.unwrap();
assert_eq!(rep.frame_no(), Some(4));
assert_eq!(rep.frames_synced(), 3);

// If no new changes occur, no new frames are synced
let rep = db.sync().await.unwrap();
assert_eq!(rep.frame_no(), Some(4));
assert_eq!(rep.frames_synced(), 0);

This sequence shows how:

  • The initial table creation generates 2 frames
  • A large insert generates 3 additional frames
  • When no changes occur, sync reports 0 frames synced while maintaining the last frame number

#3. Snapshot-Based Sync

Sometimes replicas may fall significantly behind the primary database, or you might need to initialize a new replica with a large existing database. In these cases, syncing frame-by-frame would be inefficient.

This is where snapshots come in:

  • Snapshots represent the complete state of the database at a specific frame number
  • They allow replicas to "catch up" without processing every intermediate frame
  • Particularly useful for initial replica setup or recovery after long disconnections

#Real-World Use Cases

Embedded Replicas shine in scenarios where data access needs to be both fast and resilient:

#AI-Powered Applications

Access vector embeddings and AI training data with zero latency while keeping data in sync with your central database.

#Mobile Applications

Deliver responsive experiences regardless of network conditions, with automatic background synchronization when connectivity is available.

#Edge Computing

Deploy databases closer to users for faster access times while maintaining a single source of truth in your primary database.

See how Turso CDN uses Embedded Replicas to provide Edge Replicas on Fly.io.

#Offline Writes (in BETA)

The current implementation forwards writes to the primary database, but local offline writes are in beta. This feature will enable:

  • Full offline operation capability
  • Local-first data persistence
  • Background synchronization when connectivity returns
  • Conflict resolution strategies

Join the offline writes beta program today

#Try Embedded Replicas Today

Embedded Replicas are available now in Turso Cloud. Get started with a free account and experience the benefits of local-first SQLite performance with cloud synchronization.

scarf