Skip to content

swarajbachu/mirrordb

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MirrorDB

Git for your database. Instant, isolated copies of any Postgres database in under 5 seconds.

MirrorDB continuously replicates your Postgres database via logical replication, then creates instant copy-on-write branches using btrfs/ZFS snapshots. Each branch is a fully writable, isolated Postgres instance — perfect for testing migrations, letting coding agents work against real data, or exploring schema changes without risk.

Performance

Metric Value
Branch creation time ~49ms snapshot + ~3s Postgres startup
Branch storage overhead 0 bytes until you write (copy-on-write)
50 branches of 100GB DB ~100GB total, not 5TB
Test suite 26 tests, all passing
Codebase 8 crates, ~3,500 lines of Rust

How It Works

Your Postgres (Neon, RDS, Supabase, self-hosted)
    |
    |  Logical replication (continuous WAL streaming)
    v
MirrorDB Engine (Linux + btrfs/ZFS + Postgres 16)
    |
    |  btrfs subvolume snapshot (~50ms, any size)
    v
Branch "dev-test" (writable, isolated, full copy)
Branch "feat-xyz" (writable, isolated, full copy)
    |
    |  Connection proxy routes by branch name
    v
psql postgres://mirrordb:pass@localhost:6432/dev-test
  1. Connect your database: mirrordb connector create postgresql <url> --name prod
  2. Wait for initial sync (one-time, depends on data size)
  3. Create branches instantly: mirrordb branch create dev-test -c prod
  4. Connect and use: psql postgres://...@localhost:6432/dev-test
  5. See what changed: mirrordb branch diff dev-test
  6. Clean up: mirrordb branch delete dev-test

Try It Yourself

# 1. Start the dev environment
docker compose up -d

# 2. Run the test suite
cargo test --workspace

# 3. Demo: instant btrfs branching
docker exec mirrordb-engine bash -c "
  btrfs subvolume create /mirrordb/replicas/demo &&
  chown postgres:postgres /mirrordb/replicas/demo &&
  su - postgres -c '/usr/lib/postgresql/16/bin/initdb -D /mirrordb/replicas/demo' &&
  su - postgres -c \"/usr/lib/postgresql/16/bin/pg_ctl start -D /mirrordb/replicas/demo -o '-p 5500' -l /mirrordb/replicas/demo/logfile\"
"
sleep 2

# Create test data
docker exec mirrordb-engine su - postgres -c "psql -p 5500 -c \"
  CREATE TABLE users (id serial PRIMARY KEY, name text, email text);
  INSERT INTO users (name, email) VALUES
    ('Alice', 'alice@co.com'), ('Bob', 'bob@co.com'),
    ('Carol', 'carol@co.com'), ('Dave', 'dave@co.com');
\""

# Show source data
docker exec mirrordb-engine su - postgres -c "psql -p 5500 -c 'SELECT * FROM users;'"

# INSTANT BRANCH (~49ms)
docker exec mirrordb-engine su - postgres -c "psql -p 5500 -c 'CHECKPOINT;'"
time docker exec mirrordb-engine btrfs subvolume snapshot \
  /mirrordb/replicas/demo /mirrordb/branches/my-branch

# Start branch Postgres
docker exec mirrordb-engine bash -c "
  chown -R postgres:postgres /mirrordb/branches/my-branch &&
  rm -f /mirrordb/branches/my-branch/postmaster.pid &&
  sed -i 's/^#port = 5432/port = 5501/' /mirrordb/branches/my-branch/postgresql.conf &&
  su - postgres -c \"/usr/lib/postgresql/16/bin/pg_ctl start -D /mirrordb/branches/my-branch -o '-p 5501' -l /mirrordb/branches/my-branch/logfile\"
"
sleep 2

# Branch has all the data
docker exec mirrordb-engine su - postgres -c "psql -p 5501 -c 'SELECT * FROM users;'"

# Modify branch — source stays safe
docker exec mirrordb-engine su - postgres -c "psql -p 5501 -c \"INSERT INTO users (name, email) VALUES ('BranchOnly', 'new@test.com');\""
docker exec mirrordb-engine su - postgres -c "psql -p 5501 -c 'SELECT * FROM users;'"
docker exec mirrordb-engine su - postgres -c "psql -p 5500 -c 'SELECT * FROM users;'"
# ^ Source unchanged. Full isolation.

Architecture

+-------------------------------------------------------------+
|  mirrordb-cli          User-facing CLI (clap)               |
|  mirrordb-server       REST API + orchestration (axum)      |
|  mirrordb-proxy        PG-aware connection router (tokio)   |
|-------------------------------------------------------------|
|  mirrordb-replication  WAL streaming + pgoutput decoder     |
|  mirrordb-snapshot     btrfs/ZFS snapshot strategy          |
|  mirrordb-branch       Postgres instance lifecycle          |
|-------------------------------------------------------------|
|  mirrordb-core         Domain types, engine manager, config |
|  mirrordb-store        Metadata persistence (SQLx)          |
+-------------------------------------------------------------+

Key Components

WAL Streaming Engine (mirrordb-replication): Connects to the source database using Postgres's logical replication protocol. Creates a publication and replication slot, performs initial data sync via pg_dump/pg_restore, then streams WAL changes in real-time. Includes a hand-rolled pgoutput binary protocol decoder that parses Relation, Insert, Update, Delete, and Truncate messages.

Snapshot Strategy (mirrordb-snapshot): Abstracts over the copy-on-write filesystem (btrfs for development, ZFS for production). Creates instant snapshots of replica data -- regardless of database size, a snapshot takes ~50ms because it only creates metadata pointers, not data copies.

Connection Proxy (mirrordb-proxy): Postgres protocol-aware TCP proxy that listens on a single port and routes connections by database name. Parses the PG StartupMessage to extract the database parameter (= branch name), looks up the branch's port, and enters bidirectional TCP relay. Handles SSL negotiation and supports auto-suspend/resume of idle branches.

Branch Manager (mirrordb-branch): Manages Postgres instances inside the engine container. Creates branches by configuring postgresql.conf for a unique port, starting pg_ctl, and creating isolated credentials. Supports stop/resume for suspend functionality.

Engine Container: A Docker container running Ubuntu + btrfs + Postgres 16. All replica and branch data lives on a btrfs filesystem inside it, enabling instant CoW snapshots. In production, this would be a Linux server with ZFS.

Why Instant? (Copy-on-Write)

Traditional cloning copies every byte:

500GB database --> 500GB copy --> takes 30+ minutes

MirrorDB uses btrfs/ZFS copy-on-write:

500GB database --> pointer copy --> takes 50ms
Branch writes 10MB --> only 10MB of new storage used
Total: 500GB + 10MB, not 1TB

The branch shares all data blocks with the replica. Only blocks that the branch modifies get copied (copy-on-write). This is why 50 branches of a 100GB database use roughly 100GB of disk, not 5TB.

Test Suite

cargo test --workspace

running 12 tests                          # mirrordb-core
test branch::tests::connectable_states ... ok
test branch::tests::valid_branch_transitions ... ok
test branch::tests::invalid_branch_transitions ... ok
test connector::tests::valid_transitions ... ok
test connector::tests::invalid_transitions ... ok
test id::tests::typed_ids_are_unique ... ok
test id::tests::display_includes_prefix ... ok
test id::tests::parse_round_trip ... ok
test port::tests::allocates_lowest_free_port ... ok
test port::tests::release_makes_port_available ... ok
test port::tests::mark_allocated_reserves_port ... ok
test branch::tests::credentials_generation ... ok
test result: ok. 12 passed

running 4 tests                           # mirrordb-proxy
test protocol::tests::build_startup_message_roundtrip ... ok
test protocol::tests::parse_startup_params_basic ... ok
test protocol::tests::parse_startup_params_empty ... ok
test protocol::tests::build_error_response_has_correct_format ... ok
test result: ok. 4 passed

running 10 tests                          # mirrordb-replication
test pgoutput::tests::decode_relation_message ... ok
test pgoutput::tests::decode_insert_message ... ok
test pgoutput::tests::decode_update_without_old_tuple ... ok
test pgoutput::tests::decode_update_with_old_key_tuple ... ok
test pgoutput::tests::decode_delete_message ... ok
test pgoutput::tests::decode_truncate_message ... ok
test pgoutput::tests::decode_null_column ... ok
test pgoutput::tests::decode_begin_and_commit_tags ... ok
test pgoutput::tests::decode_empty_message_fails ... ok
test pgoutput::tests::decode_unknown_tag_is_ok ... ok
test result: ok. 10 passed

26 tests, 0 failures

Tech Stack

  • Language: Rust
  • HTTP: axum
  • CLI: clap
  • Docker: bollard
  • Database: tokio-postgres, SQLx
  • Replication protocol: pgwire-replication + custom pgoutput decoder
  • Filesystem: btrfs (dev) / ZFS (production)

Project Structure

mirrordb/
  crates/
    mirrordb-core/           Domain types, EngineManager, PortAllocator, config
    mirrordb-store/          SQLx metadata store (connectors, replicas, branches)
    mirrordb-replication/    WAL streaming, pgoutput decoder, sync engine
    mirrordb-snapshot/       btrfs/ZFS snapshot strategy
    mirrordb-branch/         Postgres instance lifecycle
    mirrordb-proxy/          PG protocol-aware connection proxy
    mirrordb-server/         REST API (axum), orchestrates everything
    mirrordb-cli/            CLI binary (clap)
  docker/
    Dockerfile.engine        Ubuntu + btrfs + Postgres 16
    engine-entrypoint.sh     btrfs filesystem setup
    seed.sql                 Test data for development
  docker-compose.yml         Dev environment

Development

Prerequisites

  • Rust 1.88+
  • Docker Desktop
  • PostgreSQL client tools (pg_dump, pg_restore, psql)

Setup

# Start the development environment
docker compose up -d

# Build
cargo build --workspace

# Run tests (26 tests)
cargo test --workspace

# Start the server
cargo run -p mirrordb-server

# In another terminal
cargo run -p mirrordb-cli -- status
cargo run -p mirrordb-cli -- connector list
cargo run -p mirrordb-cli -- branch list

Docker Services

Service Port Purpose
metadata-db 5480 MirrorDB's own metadata store
source-db 5481 Simulated user's database (wal_level=logical)
engine 5500-5600 btrfs engine (replicas + branches)

License

Apache-2.0

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors