A ₹300 read‑only Postgres replica that made staging usable (and the scrubber that almost cost us privacy)

How I set up a cheap, read‑only Postgres replica on a ₹300 VPS to speed up staging queries, what it fixed, and the scrubber mistake that taught me where it fails.

Written by: Arjun Malhotra

A laptop on a wooden desk showing code in a dark-themed editor, with a coffee cup and notebook nearby
Photo by Glenn Carstens‑Peters on Unsplash

It was 9:12 a.m. and half the squad was in my Slack asking why every staging query was timing out. A data analyst wanted a 30‑row report. The frontend engineer wanted to reproduce a sorting bug. Our staging DB—an overloaded RDS instance—had the throughput of a sleeping dog. Every restore-from-snapshot took 45 minutes, and nobody wanted to wait.

I got tired of babysitting restores. So I built a cheap, read‑only replica on a ₹300/month VPS, scrubbed the data nightly, and tunneled queries to it. It took a long weekend and a mistake. Here’s what actually happened, how I did it, and the one failure that forced me to rethink assumptions.

Why a cheap replica, not more snapshots Snapshots are fine for exact-state testing, but they’re heavy:

A streaming or physical replica that’s scrubbed and mounted read‑only solves the common case: fast, safe, low‑risk reads for devs and analysts. It’s not perfect (you can’t do destructive writes), but for 80% of staging use—queries, explain plans, reproducing SELECT bugs—it’s huge.

What I actually ran: the stack and costs I did the minimum that works:

Total build time: ~6–8 hours spread over a weekend. Monthly cost: ₹300 for VPS + incidental bandwidth (₹200–₹500/month, depending on your provider and how many dumps you transfer).

Why it mattered Within a week:

Operational niceties that helped:

The failure: the scrubber that wasn’t enough After a month of bliss, we shipped a change that sent an SMS to users on certain triggers. During a load test against staging we accidentally hit a specific path that triggered real message sends. A few real numbers—users who’d never opted into test environments—got pinged. It was ugly.

Root cause: my scrubber focused on obvious PII columns (email, phone, name) but not derived fields or queued payloads. Copies of JSON blobs containing phone numbers existed in audit tables and Kafka‑backed staging queues. When my scrubber replaced the canonical phone in users table, those blobs remained untouched.

Lessons from the mistake:

The unavoidable tradeoffs

What changed after the incident

One takeaway If your pain point is “staging is slow for read debugging,” a cheap read‑only replica is the most practical fix I’ve used: cheap to run (₹300/month), fast to set up, and it reduces friction for day‑to‑day work. But don’t stop at column masking—scrub everything that can become a payload, and treat integrations as first‑class tests. The replica saved us dozens of wasted hours; the scrubber lapse reminded me that “safe” is a property you must test, not assume.