onsistency Model & Scope
Choose between logical (SQL dumps) and physical (block-level/WAL/binlog) backups based on RPO requirements. Logical backups suit small-to-medium databases and schema-heavy workloads. Physical backups with continuous archiving are mandatory for sub-15-minute RPO and large datasets. Define scope: full backups, incremental/differential, and transaction log archiving.
Step 2: Design Storage Architecture
Backups must reside outside the primary failure domain. Use object storage with versioning enabled, server-side encryption, and immutable retention policies (WORM). Cross-region replication is recommended for disaster recovery. Separate IAM roles for backup execution, storage access, and restore operations enforce least privilege.
Step 3: Implement Backup Pipeline
The pipeline executes in three phases:
- Capture: Use database-native tools (
pg_dump, mysqldump, mongodump, or cloud provider APIs) with consistency flags (--single-transaction, --lock-tables=false, --oplog).
- Process: Compress (
zstd), compute checksums (sha256), encrypt (KMS envelope encryption), and tag with metadata (timestamp, database version, backup type).
- Upload: Stream to object storage with multipart uploads, retry logic, and idempotent keys to prevent duplicates.
Step 4: Add Validation & Restore Testing
Automated backup success is meaningless without restore validation. Schedule periodic dry-run restores to isolated environments. Verify checksums, row counts, schema integrity, and application connectivity. Fail the pipeline if validation thresholds are breached.
Step 5: Monitor & Alert
Instrument the pipeline with structured logs, metrics (duration, size, success rate, latency), and alerts on failure, retry exhaustion, or validation mismatch. Route alerts to incident management systems with runbook links.
Code Example: TypeScript Backup Orchestrator
import { exec } from 'child_process';
import { promisify } from 'util';
import { createHash } from 'crypto';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { Readable } from 'stream';
const execAsync = promisify(exec);
interface BackupConfig {
dbType: 'postgres' | 'mysql';
host: string;
port: number;
user: string;
password: string;
database: string;
bucket: string;
prefix: string;
kmsKeyId: string;
}
async function createBackupStream(config: BackupConfig): Promise<Readable> {
const dumpCmd = config.dbType === 'postgres'
? `PGPASSWORD=${config.password} pg_dump -h ${config.host} -p ${config.port} -U ${config.user} -Fc -d ${config.database}`
: `MYSQL_PWD=${config.password} mysqldump -h ${config.host} -P ${config.port} -u ${config.user} --single-transaction --routines --triggers ${config.database}`;
const { stdout } = await execAsync(dumpCmd, { maxBuffer: 1024 * 1024 * 512 });
return Readable.from(stdout);
}
async function computeChecksum(stream: Readable): Promise<string> {
const hash = createHash('sha256');
for await (const chunk of stream) {
hash.update(chunk);
}
return hash.digest('hex');
}
async function uploadToS3(stream: Readable, config: BackupConfig, checksum: string): Promise<void> {
const s3 = new S3Client({ region: process.env.AWS_REGION });
const key = `${config.prefix}/${new Date().toISOString()}-backup-${checksum.slice(0, 8)}.dump`;
await s3.send(new PutObjectCommand({
Bucket: config.bucket,
Key: key,
Body: stream,
ServerSideEncryption: 'aws:kms',
SSEKMSKeyId: config.kmsKeyId,
Metadata: {
'x-amz-meta-checksum': checksum,
'x-amz-meta-database': config.database,
'x-amz-meta-backup-type': 'full'
}
}));
}
export async function runBackup(config: BackupConfig): Promise<void> {
const stream = await createBackupStream(config);
const checksum = await computeChecksum(stream);
const resetStream = Readable.from(stream.read() || '');
await uploadToS3(resetStream, config, checksum);
console.log(`Backup completed. Checksum: ${checksum}`);
}
Architecture Decisions & Rationale
- Database-native tools over file copies: Ensures transactional consistency, handles schema/permissions, and supports incremental/WAL streaming.
- Checksum + Metadata tagging: Enables automated validation, prevents silent corruption, and supports audit trails.
- KMS envelope encryption: Decouples data encryption from key management, enables rotation without re-encrypting backups, and satisfies compliance requirements.
- Stream-based processing: Avoids disk I/O bottlenecks, reduces temporary storage requirements, and enables parallel upload chunks.
- Idempotent upload keys: Prevents duplicate artifacts during retries, ensuring deterministic storage lifecycle management.
Pitfall Guide
- Skipping restore drills: Backups are only valuable if they restore correctly. Schedule automated restore tests to isolated environments weekly. Validate row counts, index integrity, and application connectivity.
- Backing up without consistency guarantees: File-level copies or unquiesced dumps produce corrupted or partially committed states. Use
--single-transaction (MySQL), pg_dump -Fc, or WAL/binlog archiving for crash-consistent backups.
- Storing backups on the same host or availability zone: Shared failure domains negate backup value. Use cross-region object storage, immutable policies, and separate IAM roles.
- Missing encryption or KMS rotation: Unencrypted backups violate compliance and expose sensitive data. Implement envelope encryption, rotate keys annually, and audit access logs.
- No retention or lifecycle policy: Unbounded storage growth increases cost and compliance risk. Define tiered retention (hot/warm/cold), automate expiration, and align with legal hold requirements.
- Ignoring transaction logs for PITR: Full backups alone cannot recover to a specific point. Archive WAL/binlog continuously, verify log continuity, and test point-in-time recovery procedures.
- Alerting on backup start instead of completion: Operators waste time investigating false positives. Alert on failure, retry exhaustion, checksum mismatch, or validation drift. Include runbook links in alert payloads.
Best Practices from Production:
- Treat backups as versioned artifacts with immutable storage policies.
- Separate backup execution from database load using dedicated compute or read replicas.
- Implement structured logging with correlation IDs for end-to-end traceability.
- Enforce least-privilege IAM: backup runner, storage writer, and restore operator roles must be distinct.
- Measure backup health with SLOs: success rate >99.5%, validation pass rate 100%, RPO/RTO within policy.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Small startup (<10GB DB) | Logical backups + cron + S3 with lifecycle | Low operational overhead, sufficient for RPO 1β4h | Minimal ($5β15/mo storage + compute) |
| Mid-scale SaaS (10GBβ1TB) | Physical backups + WAL/binlog archiving + automated validation | Guarantees sub-15min RPO, handles high write throughput | Moderate ($50β200/mo storage + compute + validation env) |
| Regulated enterprise (>1TB, HIPAA/SOC2) | Multi-region physical backups + immutable WORM + automated restore drills + KMS rotation | Meets compliance, ensures disaster recovery, eliminates manual drift | High ($200β800/mo storage + cross-region replication + compliance tooling) |
Configuration Template
{
"backup": {
"database": {
"type": "postgres",
"host": "${DB_HOST}",
"port": 5432,
"user": "${DB_USER}",
"password": "${DB_PASSWORD}",
"name": "${DB_NAME}"
},
"storage": {
"provider": "s3",
"bucket": "${BACKUP_BUCKET}",
"prefix": "db-backups/prod",
"region": "${AWS_REGION}",
"kmsKeyId": "${KMS_KEY_ID}",
"retention": {
"hot": "30d",
"warm": "90d",
"cold": "365d",
"deleteExpired": true
}
},
"validation": {
"enabled": true,
"restoreTarget": "isolated-env",
"checks": ["checksum", "row-count", "schema-integrity"],
"schedule": "0 3 * * 0"
},
"observability": {
"metrics": ["duration_ms", "size_bytes", "success_rate"],
"alerts": {
"onFailure": true,
"onValidationMismatch": true,
"webhook": "${SLACK_WEBHOOK}"
}
}
}
}
Quick Start Guide
- Install dependencies:
npm install @aws-sdk/client-s3 child_process util crypto stream
- Set environment variables:
DB_HOST, DB_USER, DB_PASSWORD, DB_NAME, BACKUP_BUCKET, AWS_REGION, KMS_KEY_ID, SLACK_WEBHOOK
- Run dry execution:
node backup-runner.js --dry-run to verify connectivity, dump generation, and checksum computation without uploading
- Schedule pipeline: Add to cron or CI/CD scheduler with
0 2 * * * for nightly full backups, and configure WAL/binlog streaming for continuous archiving
- Validate: Trigger a manual restore to an isolated environment, verify row counts and schema integrity, and confirm alert routing works on simulated failure