0Pricing
SQL Academy · Lesson

Disaster Recovery Planning

RPO, RTO and runbooks.

Disaster Recovery Planning is a free SQL Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the SQL Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

What Is Disaster Recovery?

Disaster Recovery (DR) is the set of policies, tools, and procedures designed to enable the recovery of vital technology infrastructure and systems after a natural or human-induced disaster.

In the context of databases, DR planning ensures that your data remains safe and your systems can be brought back online within acceptable time limits after events such as hardware failure, accidental data deletion, ransomware attacks, or datacenter outages.

Recovery Point Objective (RPO)

RPO defines the maximum acceptable amount of data loss measured in time. If your RPO is 1 hour, you must be able to recover data up to at least 1 hour before the disaster occurred.

A shorter RPO demands more frequent backups or continuous replication. You can query your backup history to verify you are meeting your RPO target.

-- Check the last backup time and calculate data loss window
SELECT
  backup_id,
  backup_type,
  started_at,
  finished_at,
  EXTRACT(EPOCH FROM (NOW() - finished_at)) / 3600 AS hours_since_backup
FROM backup_log
WHERE status = 'SUCCESS'
ORDER BY finished_at DESC
LIMIT 5;

Recovery Time Objective (RTO)

RTO defines the maximum acceptable length of time that your system can be offline after a disaster. If your RTO is 4 hours, your database must be fully operational within 4 hours of a failure.

RTO drives decisions about standby servers, failover automation, and restore procedures. Tracking restore durations over time helps you forecast whether you can meet your RTO.

-- Track restore durations to validate RTO compliance
SELECT
  restore_id,
  triggered_at,
  completed_at,
  EXTRACT(EPOCH FROM (completed_at - triggered_at)) / 60 AS restore_minutes,
  CASE
    WHEN EXTRACT(EPOCH FROM (completed_at - triggered_at)) / 3600 <= 4
    THEN 'WITHIN RTO'
    ELSE 'RTO BREACHED'
  END AS rto_status
FROM restore_log
ORDER BY triggered_at DESC;

RPO vs RTO — The Key Difference

These two metrics are often confused. Here is the simplest way to remember them:

  • RPO = How much data can you afford to lose? (backward-looking, measured in time before disaster)
  • RTO = How long can you afford to be down? (forward-looking, measured in time after disaster)

Together they define your recovery window and directly influence your backup frequency, replication strategy, and infrastructure budget.

-- Store RPO and RTO targets per database in a DR configuration table
CREATE TABLE dr_config (
  db_name       VARCHAR(100) PRIMARY KEY,
  rpo_minutes   INT NOT NULL,
  rto_minutes   INT NOT NULL,
  tier          VARCHAR(20) CHECK (tier IN ('CRITICAL', 'HIGH', 'MEDIUM', 'LOW')),
  updated_at    TIMESTAMP DEFAULT NOW()
);

INSERT INTO dr_config (db_name, rpo_minutes, rto_minutes, tier) VALUES
  ('orders_db',    15,   60, 'CRITICAL'),
  ('analytics_db', 120, 240, 'MEDIUM'),
  ('archive_db',   480, 480, 'LOW');

Types of Backups

There are three primary backup strategies, each balancing speed and storage:

  • Full backup: A complete snapshot of the entire database. Slowest to create, fastest to restore.
  • Differential backup: Only changes since the last full backup. Moderate speed both ways.
  • Incremental backup: Only changes since the last backup of any type. Fastest to create, slowest to restore (multiple files needed).

Most DR strategies combine full weekly backups with daily incrementals to balance RPO and storage cost.

-- Log each backup with its type for audit and recovery planning
CREATE TABLE backup_log (
  backup_id   SERIAL PRIMARY KEY,
  db_name     VARCHAR(100) NOT NULL,
  backup_type VARCHAR(20) CHECK (backup_type IN ('FULL', 'DIFFERENTIAL', 'INCREMENTAL')),
  started_at  TIMESTAMP NOT NULL,
  finished_at TIMESTAMP,
  size_mb     NUMERIC(12, 2),
  status      VARCHAR(20) DEFAULT 'IN_PROGRESS'
);

INSERT INTO backup_log (db_name, backup_type, started_at, finished_at, size_mb, status) VALUES
  ('orders_db', 'FULL',        '2024-06-01 01:00:00', '2024-06-01 02:15:00', 45200, 'SUCCESS'),
  ('orders_db', 'INCREMENTAL', '2024-06-02 01:00:00', '2024-06-02 01:08:00',   320, 'SUCCESS'),
  ('orders_db', 'INCREMENTAL', '2024-06-03 01:00:00', '2024-06-03 01:07:00',   290, 'SUCCESS');

Point-in-Time Recovery (PITR)

Point-in-Time Recovery allows you to restore a database to any specific moment, not just the time of the last backup. This is achieved by replaying transaction logs (WAL in PostgreSQL) on top of a base backup.

PITR is essential when you need to recover from accidental data corruption or deletion that happened at a known time. You can restore to just before the damaging event occurred.

-- Record WAL archive events for PITR tracking
CREATE TABLE wal_archive_log (
  segment_name  VARCHAR(200) PRIMARY KEY,
  archived_at   TIMESTAMP DEFAULT NOW(),
  size_bytes    BIGINT,
  storage_path  TEXT
);

-- Find all WAL segments archived within a recovery window
SELECT
  segment_name,
  archived_at,
  ROUND(size_bytes / 1024.0 / 1024.0, 2) AS size_mb
FROM wal_archive_log
WHERE archived_at BETWEEN '2024-06-03 09:00:00' AND '2024-06-03 11:00:00'
ORDER BY archived_at;

Standby Databases and Replication

A standby (replica) database is a continuously updated copy of the primary database running on separate hardware. It serves two DR purposes:

  • Hot standby: Can accept read queries and fails over in seconds (near-zero RTO).
  • Warm standby: Kept in sync but not serving traffic; failover takes minutes.

Monitoring replication lag is critical — a lagging replica means your actual RPO is worse than expected.

-- Monitor replication lag on a PostgreSQL primary
SELECT
  client_addr,
  application_name,
  state,
  sent_lsn,
  replay_lsn,
  (sent_lsn - replay_lsn) AS lag_bytes,
  EXTRACT(EPOCH FROM (NOW() - reply_time)) AS seconds_since_reply
FROM pg_stat_replication
ORDER BY lag_bytes DESC;

Runbooks: Documenting Recovery Procedures

A runbook is a documented set of step-by-step instructions that an operator follows during a disaster recovery event. Without a runbook, even experienced DBAs make costly mistakes under pressure.

A good DR runbook includes: who to contact, what systems are affected, exact commands to run, expected outputs at each step, and rollback procedures if recovery fails. Storing runbook metadata in a database helps track versioning and audit usage.

-- Store runbook metadata in the database for audit tracking
CREATE TABLE runbook (
  runbook_id   SERIAL PRIMARY KEY,
  title        VARCHAR(200) NOT NULL,
  scenario     VARCHAR(100),
  version      VARCHAR(20) DEFAULT '1.0',
  last_tested  DATE,
  owner        VARCHAR(100),
  doc_url      TEXT
);

INSERT INTO runbook (title, scenario, version, last_tested, owner, doc_url) VALUES
  ('Full Database Restore from S3', 'total_loss',     '2.1', '2024-05-15', 'dba_team', 'https://wiki.internal/dr/full-restore'),
  ('Failover to Hot Standby',       'primary_down',   '1.4', '2024-04-20', 'dba_team', 'https://wiki.internal/dr/failover'),
  ('PITR to Specific Timestamp',    'data_corruption','1.2', '2024-03-10', 'dba_team', 'https://wiki.internal/dr/pitr');

Runbook Execution Logging

Every time a runbook is executed — whether in a real disaster or a drill — it should be logged. Execution logs let you measure how long recovery actually takes (validating your RTO), identify steps that are slow or error-prone, and demonstrate compliance to auditors.

-- Log each runbook execution for RTO validation and audit
CREATE TABLE runbook_execution (
  execution_id  SERIAL PRIMARY KEY,
  runbook_id    INT REFERENCES runbook(runbook_id),
  triggered_by  VARCHAR(100),
  is_drill      BOOLEAN DEFAULT FALSE,
  started_at    TIMESTAMP NOT NULL,
  completed_at  TIMESTAMP,
  outcome       VARCHAR(20) CHECK (outcome IN ('SUCCESS', 'PARTIAL', 'FAILED'))
);

-- Report average restore time per runbook
SELECT
  r.title,
  COUNT(*) AS executions,
  ROUND(AVG(EXTRACT(EPOCH FROM (e.completed_at - e.started_at)) / 60), 1) AS avg_minutes,
  MAX(EXTRACT(EPOCH FROM (e.completed_at - e.started_at)) / 60) AS max_minutes
FROM runbook_execution e
JOIN runbook r ON r.runbook_id = e.runbook_id
WHERE e.outcome = 'SUCCESS'
GROUP BY r.title;

DR Testing: Regular Drills

A DR plan that has never been tested is not a plan — it is a wish. Regular drills are mandatory to ensure your team can actually execute the runbooks within the defined RTO and that backups are genuinely restorable.

Scheduling and tracking drills in your database creates an audit trail and helps you surface runbooks that are overdue for testing.

-- Find runbooks that have not been drilled in over 90 days
SELECT
  r.runbook_id,
  r.title,
  r.scenario,
  MAX(e.completed_at) AS last_drill,
  CURRENT_DATE - MAX(e.completed_at::DATE) AS days_since_drill
FROM runbook r
LEFT JOIN runbook_execution e
  ON e.runbook_id = r.runbook_id
  AND e.is_drill = TRUE
  AND e.outcome = 'SUCCESS'
GROUP BY r.runbook_id, r.title, r.scenario
HAVING MAX(e.completed_at) IS NULL
    OR CURRENT_DATE - MAX(e.completed_at::DATE) > 90
ORDER BY days_since_drill DESC NULLS FIRST;

Backup Retention Policies

Retention policies define how long backups are kept. Keeping every backup forever wastes storage; deleting them too quickly violates your RPO and compliance requirements.

A common policy is: daily incrementals for 7 days, weekly fulls for 4 weeks, and monthly fulls for 12 months. You can enforce and audit retention with SQL queries against your backup log.

-- Identify backups that are outside their retention window and ready to purge
SELECT
  backup_id,
  db_name,
  backup_type,
  finished_at,
  CURRENT_DATE - finished_at::DATE AS age_days,
  CASE backup_type
    WHEN 'INCREMENTAL' THEN 7
    WHEN 'DIFFERENTIAL' THEN 28
    WHEN 'FULL'         THEN 365
  END AS retention_days,
  CASE
    WHEN (CURRENT_DATE - finished_at::DATE) >
         CASE backup_type
           WHEN 'INCREMENTAL' THEN 7
           WHEN 'DIFFERENTIAL' THEN 28
           WHEN 'FULL'         THEN 365
         END
    THEN 'PURGE'
    ELSE 'KEEP'
  END AS action
FROM backup_log
WHERE status = 'SUCCESS'
ORDER BY finished_at;

Quick Check

Test your understanding of RPO and RTO definitions.

Recap: Disaster Recovery Planning

In this lesson you explored the foundations of database disaster recovery planning:

  • RPO defines the maximum tolerable data loss in time; a shorter RPO requires more frequent backups or continuous replication.
  • RTO defines how quickly the system must be restored; a shorter RTO demands hot standbys and automated failover.
  • Backup types — full, differential, incremental — each offer different trade-offs between storage, creation speed, and restore speed.
  • PITR (Point-in-Time Recovery) uses transaction logs to restore to any precise moment, protecting against accidental changes.
  • Runbooks document every step of a recovery procedure; logging executions validates that your RTO is achievable in practice.
  • Regular DR drills are the only way to confirm that backups are restorable and that your team can meet RPO and RTO targets under real pressure.
  • Retention policies balance storage cost against compliance and recovery requirements.

A well-tested DR plan is one of the most valuable investments a database team can make.

Frequently asked questions

Is the “Disaster Recovery Planning” lesson free?

Yes — the full text of “Disaster Recovery Planning” is free to read here on the web, and the SQL Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the SQL Academy course, upgrade to CoddyKit PRO.

What will I learn in “Disaster Recovery Planning”?

RPO, RTO and runbooks. You practise SQL Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start SQL Academy?

No prior experience is required. SQL Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Disaster Recovery Planning” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this SQL Academy lesson?

Yes. Every SQL Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Logical vs Physical Backups
  2. Point-in-Time Recovery
  3. Testing Your Restores
  4. Disaster Recovery Planning
← Back to SQL Academy