|
| 1 | +# Show HN: Database-replicator – Zero-downtime database migrations to PostgreSQL |
| 2 | + |
| 3 | +**TL;DR:** We built an open-source CLI tool that replicates databases from PostgreSQL, MySQL, MongoDB, and SQLite to PostgreSQL with zero downtime. It's written in Rust, supports continuous sync via logical replication, and can optionally run on managed cloud infrastructure. [GitHub](https://github.com/serenorg/database-replicator) | [Crates.io](https://crates.io/crates/database-replicator) |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## The Problem We Were Solving |
| 8 | + |
| 9 | +At SerenAI, we're building PostgreSQL databases optimized for AI workloads. Our users kept asking the same question: "How do I migrate my existing database to SerenDB without downtime?" |
| 10 | + |
| 11 | +The existing options weren't great: |
| 12 | + |
| 13 | +**pg_dump/pg_restore** works, but requires downtime. For a 100GB database, you're looking at hours of your application being offline. Not acceptable for production systems. |
| 14 | + |
| 15 | +**AWS DMS and similar tools** are powerful but complex. They require provisioning infrastructure, configuring replication instances, and managing IAM roles. Overkill for most migrations. |
| 16 | + |
| 17 | +**Logical replication setup** is the right approach, but it's tedious. You need to configure `wal_level`, create publications, set up subscriptions, handle the initial snapshot, monitor lag, and verify data integrity. Each step has gotchas. |
| 18 | + |
| 19 | +We wanted something simpler: a single command that handles everything. |
| 20 | + |
| 21 | +## What We Built |
| 22 | + |
| 23 | +`database-replicator` is a Rust CLI that automates the entire migration process: |
| 24 | + |
| 25 | +```bash |
| 26 | +# Validate both databases meet prerequisites |
| 27 | +database-replicator validate --source $SOURCE --target $TARGET |
| 28 | + |
| 29 | +# Initial snapshot + set up continuous sync |
| 30 | +database-replicator init --source $SOURCE --target $TARGET --enable-sync |
| 31 | + |
| 32 | +# Monitor replication lag |
| 33 | +database-replicator status --source $SOURCE --target $TARGET |
| 34 | + |
| 35 | +# Verify data integrity with checksums |
| 36 | +database-replicator verify --source $SOURCE --target $TARGET |
| 37 | +``` |
| 38 | + |
| 39 | +### How It Works (PostgreSQL to PostgreSQL) |
| 40 | + |
| 41 | +For PostgreSQL sources, we use native logical replication, which is the gold standard for zero-downtime migrations: |
| 42 | + |
| 43 | +1. **Validate** - Check that the source has `wal_level = logical`, user has REPLICATION privilege, and target can create subscriptions. |
| 44 | + |
| 45 | +2. **Initial Snapshot** - Use `pg_dump` in directory format with parallel workers (auto-detected based on CPU cores). We dump globals, schema, and data separately for proper dependency ordering. |
| 46 | + |
| 47 | +3. **Create Publication** - Set up a publication on the source for all tables (or filtered tables if you're doing selective replication). |
| 48 | + |
| 49 | +4. **Create Subscription** - The target subscribes to the source. PostgreSQL handles the rest—every INSERT, UPDATE, and DELETE is streamed in real-time. |
| 50 | + |
| 51 | +5. **Monitor** - Track replication lag in bytes and time. When lag hits zero, you're ready to cut over. |
| 52 | + |
| 53 | +6. **Verify** - Compute MD5 checksums across all tables on both sides. Any discrepancy is flagged immediately. |
| 54 | + |
| 55 | +The entire flow handles edge cases: TCP keepalives for long-running operations behind load balancers, retry with exponential backoff for transient failures, and proper credential handling via `.pgpass` files (never exposed in process arguments). |
| 56 | + |
| 57 | +### Multi-Source Support |
| 58 | + |
| 59 | +Not everyone is migrating from PostgreSQL. We added support for: |
| 60 | + |
| 61 | +- **MySQL/MariaDB → PostgreSQL**: Schema translation, type mapping, one-time snapshot with optional periodic refresh |
| 62 | +- **MongoDB → PostgreSQL**: Documents stored as JSONB, preserving the flexible schema while gaining SQL queryability |
| 63 | +- **SQLite → PostgreSQL**: Perfect for graduating from a local SQLite database to a production PostgreSQL instance |
| 64 | + |
| 65 | +For non-PostgreSQL sources, we can't use logical replication (it's a PostgreSQL-specific feature), so these are snapshot-based migrations with optional scheduled refreshes. |
| 66 | + |
| 67 | +## Selective Replication |
| 68 | + |
| 69 | +Real migrations are rarely "replicate everything." You might want to: |
| 70 | + |
| 71 | +- Exclude large analytics tables that you'll backfill separately |
| 72 | +- Filter old data (only replicate orders from the last 90 days) |
| 73 | +- Skip tables with PII for a staging environment |
| 74 | + |
| 75 | +We support this through CLI flags or a TOML config file: |
| 76 | + |
| 77 | +```toml |
| 78 | +[databases.mydb] |
| 79 | +schema_only = ["large_analytics_table"] |
| 80 | + |
| 81 | +[[databases.mydb.table_filters]] |
| 82 | +table = "orders" |
| 83 | +where = "created_at > NOW() - INTERVAL '90 days'" |
| 84 | + |
| 85 | +[[databases.mydb.time_filters]] |
| 86 | +table = "events" |
| 87 | +column = "timestamp" |
| 88 | +last = "6 months" |
| 89 | +``` |
| 90 | + |
| 91 | +The filter predicates are pushed down to `pg_dump` and used in publication WHERE clauses (PostgreSQL 15+), so you're not transferring data you don't need. |
| 92 | + |
| 93 | +## The Cloud Execution Feature |
| 94 | + |
| 95 | +Here's where it gets interesting. Running a migration locally has drawbacks: |
| 96 | + |
| 97 | +- Your laptop needs to stay connected for hours (or days for large databases) |
| 98 | +- Network bandwidth is limited by your ISP upload speed |
| 99 | +- If your connection drops, you need to restart or resume from checkpoints |
| 100 | + |
| 101 | +For users migrating to SerenDB, we built a remote execution feature. Instead of running locally, the replication job runs on our AWS infrastructure: |
| 102 | + |
| 103 | +```bash |
| 104 | +export SEREN_API_KEY="your-api-key" # from console.serendb.com |
| 105 | +database-replicator init \ |
| 106 | + --source "postgresql://user:pass@your-rds.amazonaws.com:5432/db" \ |
| 107 | + --target "postgresql://user:pass@your-db.serendb.com:5432/db" |
| 108 | +``` |
| 109 | + |
| 110 | +When the target is a SerenDB database, we automatically provision an EC2 worker, run the migration there, stream progress back to your terminal, and clean up when done. Your laptop can disconnect—the job continues. |
| 111 | + |
| 112 | +The architecture: |
| 113 | +- API Gateway + Lambda for job coordination |
| 114 | +- SQS for reliable job queuing |
| 115 | +- EC2 workers with the replicator binary pre-installed |
| 116 | +- KMS encryption for credentials (never stored in plaintext) |
| 117 | +- DynamoDB for job state tracking |
| 118 | + |
| 119 | +For non-SerenDB targets, add `--local` and it runs on your machine like any other CLI tool. |
| 120 | + |
| 121 | +## Technical Decisions |
| 122 | + |
| 123 | +**Why Rust?** Performance and reliability. Database migrations are long-running operations where memory safety matters. We also get easy cross-compilation for Linux, macOS Intel, and macOS ARM. |
| 124 | + |
| 125 | +**Why shell out to pg_dump?** We considered implementing dump logic directly, but `pg_dump` is battle-tested across millions of databases. It handles edge cases we'd never think of. We wrap it with proper error handling, progress tracking, and retry logic. |
| 126 | + |
| 127 | +**Why logical replication over CDC tools?** PostgreSQL's built-in logical replication is robust and doesn't require additional infrastructure. It's what PostgreSQL itself uses for read replicas. For PostgreSQL-to-PostgreSQL migrations, there's no reason to add complexity. |
| 128 | + |
| 129 | +**Checkpoint system** - Long migrations need resume support. We write checkpoints to `.seren-replicator/` after each database completes. If interrupted, the next run picks up where it left off. Checkpoints include a fingerprint of the filter configuration—if you change filters, it starts fresh to prevent data inconsistency. |
| 130 | + |
| 131 | +## Limitations (Being Honest) |
| 132 | + |
| 133 | +- **PostgreSQL-only targets**: We replicate *to* PostgreSQL, not from it to other databases |
| 134 | +- **Logical replication requires PostgreSQL 10+** on the source (we recommend 12+) |
| 135 | +- **Large objects (BLOBs)** aren't replicated via logical replication—use `pg_dump` for those |
| 136 | +- **DDL changes** during replication need manual handling (logical replication doesn't capture schema changes) |
| 137 | +- **Sequences** need to be manually advanced after cutover |
| 138 | + |
| 139 | +## Getting Started |
| 140 | + |
| 141 | +Install from crates.io: |
| 142 | +```bash |
| 143 | +cargo install database-replicator |
| 144 | +``` |
| 145 | + |
| 146 | +Or download binaries from the [GitHub releases](https://github.com/serenorg/database-replicator/releases). |
| 147 | + |
| 148 | +Basic migration: |
| 149 | +```bash |
| 150 | +# Check prerequisites |
| 151 | +database-replicator validate \ |
| 152 | + --source "postgresql://user:pass@source:5432/mydb" \ |
| 153 | + --target "postgresql://user:pass@target:5432/mydb" |
| 154 | + |
| 155 | +# Run migration with continuous sync |
| 156 | +database-replicator init \ |
| 157 | + --source "postgresql://user:pass@source:5432/mydb" \ |
| 158 | + --target "postgresql://user:pass@target:5432/mydb" \ |
| 159 | + --enable-sync |
| 160 | + |
| 161 | +# Monitor until lag is zero |
| 162 | +database-replicator status \ |
| 163 | + --source "postgresql://user:pass@source:5432/mydb" \ |
| 164 | + --target "postgresql://user:pass@target:5432/mydb" |
| 165 | + |
| 166 | +# Verify checksums match |
| 167 | +database-replicator verify \ |
| 168 | + --source "postgresql://user:pass@source:5432/mydb" \ |
| 169 | + --target "postgresql://user:pass@target:5432/mydb" |
| 170 | + |
| 171 | +# Cut over your application to the new database |
| 172 | +# Then drop the subscription on target |
| 173 | +``` |
| 174 | + |
| 175 | +## What's Next |
| 176 | + |
| 177 | +We're working on: |
| 178 | +- **CDC for non-PostgreSQL sources** - Real-time sync from MySQL using binlog |
| 179 | +- **Progress percentage** - Better estimation of completion time |
| 180 | +- **Web dashboard** - Visual monitoring for remote jobs |
| 181 | + |
| 182 | +The tool is Apache 2.0 licensed. Contributions welcome—especially around MySQL CDC and additional source database support. |
| 183 | + |
| 184 | +--- |
| 185 | + |
| 186 | +[GitHub](https://github.com/serenorg/database-replicator) | [Documentation](https://github.com/serenorg/database-replicator#readme) | [SerenAI Console](https://console.serendb.com) |
| 187 | + |
| 188 | +Happy to answer questions about the architecture, PostgreSQL logical replication gotchas, or anything else. |
0 commit comments