Skip to main content
Zero is built on a three-tier architecture that enables real-time sync between PostgreSQL and client applications. Understanding this architecture is key to working effectively with Zero.

Overview

The Three Tiers

PostgreSQL: Source of Truth

PostgreSQL serves as the authoritative source of truth for all data in Zero applications.
  • Role: Primary database storing canonical application data
  • Changes: All mutations ultimately write to PostgreSQL
  • Replication: Uses PostgreSQL’s logical replication to stream changes
Zero doesn’t modify your PostgreSQL schema. You can use standard PostgreSQL tools, migrations (like Drizzle), and direct SQL queries alongside Zero.

zero-cache: Server-Side Replica

zero-cache is the sync server that maintains a SQLite replica of your PostgreSQL data. Key responsibilities:
  1. Replication: Continuously replicates changes from PostgreSQL using logical replication
  2. Query Processing: Transforms ZQL queries into SQL and maintains incremental views
  3. Authorization: Enforces permissions defined in your schema
  4. Client Sync: Manages WebSocket connections to clients and pushes updates
Implementation details: The replicator service continuously syncs data:
// From packages/zero-cache/src/services/replicator/incremental-sync.ts
export class IncrementalSyncer {
  async run(lc: LogContext) {
    const {watermark: initialWatermark} = getSubscriptionState(this.#replica);
    
    // Notify subscribers that replica is ready
    void this.#notifier.notifySubscribers();
    
    while (this.#state.shouldRun()) {
      const {replicaVersion, watermark} = getSubscriptionState(this.#replica);
      
      downstream = await this.#changeStreamer.subscribe({
        protocolVersion: PROTOCOL_VERSION,
        watermark,
        replicaVersion,
      });
      
      for await (const message of downstream) {
        const result = processor.processMessage(lc, message);
        if (result?.watermark && result?.changeLogUpdated) {
          void this.#notifier.notifySubscribers({state: 'version-ready'});
        }
      }
    }
  }
}
Why SQLite?
  • Fast query execution for client views
  • Embeddable: runs in the same process as zero-cache
  • Efficient indexes for incremental view maintenance
  • Low-latency reads compared to PostgreSQL round-trips

Client: Replicache + IndexedDB

Clients use Replicache to maintain a local cache in IndexedDB. Key features:
  1. Local-First: Reads are instant from IndexedDB
  2. Optimistic Updates: Mutations apply locally first, then sync to server
  3. Offline Support: Works without connectivity; syncs when reconnected
  4. Reactive Queries: UI updates automatically when data changes
Client initialization:
import {Zero} from '@rocicorp/zero';
import {schema} from './schema';

const zero = new Zero({
  server: 'https://your-zero-cache-url',
  schema,
  userID: currentUser.id,
});

Data Flow

Write Path (Mutations)

1

Client mutation

Client calls a mutator (e.g., zero.mutate.updateIssue(...)).
2

Optimistic update

Replicache applies the mutation to IndexedDB immediately and returns.
3

Server sync

Mutation is pushed to zero-cache over WebSocket.
4

Validation

zero-cache validates permissions and executes against PostgreSQL.
5

Replication

PostgreSQL replicates the change back to zero-cache’s SQLite replica.
6

Broadcast

zero-cache broadcasts the change to all subscribed clients.
7

Reconciliation

Clients receive the authoritative version and reconcile with their optimistic state.

Read Path (Queries)

1

Query subscription

Client subscribes to a query using useQuery() or similar.
2

Query transformation

zero-cache transforms the ZQL query into SQL for SQLite.
3

Initial results

Initial query results are pushed to the client.
4

Incremental updates

As data changes, zero-cache uses incremental view maintenance (IVM) to compute diffs.
5

Real-time sync

Only the changes are pushed to clients, not the entire result set.

Key Components

zero-client

The client library (@rocicorp/zero-client) that applications import:
// From packages/zero-client/src/client/zero.ts
export class Zero {
  readonly #replicache: ReplicacheImpl;
  readonly #schema: Schema;
  readonly #queryManager: QueryManager;
  readonly #connectionManager: ConnectionManager;
  
  // Creates query builders for each table in schema
  get query(): SchemaQuery {
    return createBuilder(this.#schema);
  }
  
  // Executes mutations with optimistic updates
  async mutate() {
    // Mutation logic
  }
}

ZQL Engine

The query language and incremental view maintenance engine:
  • Query Building: Type-safe query construction with method chaining
  • IVM Operators: Pipeline of operators (filter, join, take, etc.) that maintain incremental views
  • Change Processing: Efficiently computes view updates from database changes
See Queries for details.

Change Streamer

Manages the replication stream from PostgreSQL:
// Coordinates catchup and real-time streaming
export class ChangeStreamerImpl implements ChangeStreamerService {
  async subscribe(context: SubscriberContext): Source<Downstream> {
    // Stream changes from PostgreSQL logical replication
    // Handle subscriber catchup from stored changelog
    // Forward new changes in real-time
  }
}

Performance Characteristics

Read Performance

  • Client reads: ~1ms (IndexedDB)
  • No server round-trip for cached queries
  • Incremental updates: Only diffs are synced

Write Performance

  • Optimistic response: Immediate (< 1ms)
  • Server confirmation: 50-200ms typical
  • Broadcast to clients: 10-50ms after commit

Scalability

  • Clients per zero-cache instance: 10,000+
  • SQLite query performance: 1000s of queries/sec
  • PostgreSQL: Scales independently for writes

Deployment Patterns

Development

# Run zero-cache locally
npm run zero-cache-dev

# Or via Docker
docker run -p 4848:4848 rocicorp/zero-cache

Production

Deploy zero-cache as a long-running service:
# docker-compose.yml
services:
  zero-cache:
    image: rocicorp/zero-cache
    environment:
      - DATABASE_URL=postgresql://...
    ports:
      - "4848:4848"
Each zero-cache instance maintains its own SQLite replica. Clients must maintain session affinity to a single instance.

Next Steps

Sync Model

Learn how Zero keeps data in sync across tiers

Schema Definition

Define your tables and relationships

Queries

Build type-safe queries with ZQL

Permissions

Control access to your data

Build docs developers (and LLMs) love