Skip to main content
emnode / learn
Site Reliability

Enable DynamoDB point-in-time recovery

A bad deploy or a fat-fingered delete can corrupt a DynamoDB table in seconds. PITR lets you rewind to any second in the last 35 days — but it's off by default on every table.

12 min·10 sections·AWS

Last reviewed

DynamoDB point-in-time recovery: the basics

Why a fully-managed, multi-AZ table still needs a recovery story

DynamoDB replicates every write synchronously across three Availability Zones, so the data is exceptionally safe from hardware failure. But durability is not recoverability. If your application writes garbage, a migration script overwrites the wrong items, or someone calls DeleteItem in a loop against production, DynamoDB faithfully and durably stores the damage across all three AZs. The platform has no idea your data is now wrong — it just keeps it safe.

Point-in-Time Recovery (PITR) is the answer. When enabled, DynamoDB continuously captures incremental backups of the table, letting you restore it to any single second within a rolling 35-day window. It's the primary defense against the failures that actually happen in production: accidental writes and deletes, bad deploys that corrupt data, and logic bugs that quietly mangle items for hours before anyone notices. It is OFF by default, per table.

The check flags any table with PITR disabled. That's almost always an oversight — a table created by an early prototype, a CDK stack where the flag was never set, or a service that grew from "throwaway" to "load-bearing" without anyone revisiting its recovery posture. A table with PITR off has no rewind button at all; the only recovery is whatever on-demand backups happen to exist, which for most disabled tables is none.

In this lesson you'll learn how DynamoDB PITR works under the hood — continuous incremental backups, the rolling 35-day window, and the crucial detail that a restore always creates a brand-new table and never overwrites the source. You'll see why that means you have to repoint your application or swap table names after recovery, how PITR differs from on-demand backups and AWS Backup (recent-and-granular versus long-term-and-compliance, and why you usually want both), what it actually costs, and how to roll it out across every production table with tags and automation so coverage never drifts. You'll get the AWS CLI commands to find PITR-disabled tables, enable PITR, and perform a real point-in-time restore.

Fun fact

The migration that ran twice

A payments team shipped a data migration that normalised a currency field across a 90 GB DynamoDB table. A retry in their deploy pipeline ran the migration a second time against already-migrated rows, double-applying the transform and quietly corrupting roughly 2.1 million items. Nobody noticed for six hours, until reconciliation reports went sideways. Because PITR had been enabled on the table for years, the on-call engineer restored a copy to the exact second before the first migration job started, diffed it against production, and surgically repaired the affected items. Total recovery: about 90 minutes. The team later admitted the PITR flag had been set once, in the original CDK stack, by an engineer who had since left — and never thought about again until the night it saved them.

Enabling PITR in action

Priya owns the platform reliability cadence at a logistics company. A continuity scan flags 18 DynamoDB tables with PITR disabled. Most are obviously fine — dev-scratch, loadtest-temp, a couple of ephemeral session caches. But two stop her cold: orders-prod and customer-addresses-prod, both clearly load-bearing, both with no recovery path beyond "hope."

She pulls the continuous-backups status to confirm. orders-prod comes back PointInTimeRecoveryStatus: DISABLED — exactly as flagged. The table is 64 GB. At roughly $0.20 per GB-month that's about $13 a month to give a customer-facing orders table a 35-day rewind window. She doesn't even bother routing that through a cost conversation; the number is a rounding error against the exposure.

She enables PITR on both production tables in a single change, then notes the one thing the team needs to internalise: a PITR restore never overwrites the live table — it creates a new one. So the runbook isn't "restore the table," it's "restore to orders-prod-recovered, validate it, then either repoint the service or swap the names." She adds that step to the incident runbook before closing the finding, because the worst time to learn the restore-to-new-table mechanic is at 3am mid-incident.

First, check the continuous-backups status of a table to confirm whether PITR is actually on.

$ aws dynamodb describe-continuous-backups --table-name orders-prod --query 'ContinuousBackupsDescription.PointInTimeRecoveryDescription'
{
"PointInTimeRecoveryStatus": "DISABLED"
}
# No EarliestRestorableDateTime, no LatestRestorableDateTime — there is no rewind window at all.
# A 64 GB customer-facing table with zero recovery path beyond on-demand backups (of which there are none).

PITR status for the table — DISABLED means there is no point-in-time recovery window whatsoever.

Enable PITR. It takes effect immediately and the 35-day window starts accumulating from this moment forward.

$ aws dynamodb update-continuous-backups --table-name orders-prod --point-in-time-recovery-specification PointInTimeRecoveryEnabled=true
{
"ContinuousBackupsDescription": {
"ContinuousBackupsStatus": "ENABLED",
"PointInTimeRecoveryDescription": {
"PointInTimeRecoveryStatus": "ENABLED",
"EarliestRestorableDateTime": "2026-05-26T14:02:11+00:00",
"LatestRestorableDateTime": "2026-05-26T14:02:11+00:00"
}
}
}
# Earliest == Latest right now; the window grows to a rolling 35 days from here. No backfill of the past.

PITR enabled. The rewind window starts now and rolls forward to 35 days — it does not retroactively cover yesterday.

DynamoDB PITR under the hooddeep dive

When PITR is enabled, DynamoDB continuously captures incremental backups of the table behind the scenes — there's no schedule to manage and no performance impact on your reads or writes, because the work happens in the storage layer, not on your provisioned or on-demand throughput. The result is a rolling window: at any moment you can restore to any single second between EarliestRestorableDateTime (up to 35 days ago) and LatestRestorableDateTime (about five minutes ago). The window is not configurable — it's always 35 days — and it starts accumulating only from the moment you enable PITR. Enabling it today does nothing for a deletion that happened yesterday.

The single most important operational fact is that a restore never touches the source table. restore-table-to-point-in-time creates a brand-new table with the data as it existed at the target timestamp; your live table is left exactly as it is. This is a deliberate safety property — recovery can't make a bad situation worse — but it means recovery is a two-step process. You restore to a new table (e.g. orders-prod-recovered), validate it, and then either repoint the application at the new table or delete the corrupted original and restore again under the original name. There is no in-place rewind.

PITR is distinct from on-demand backups and from AWS Backup. PITR is for recent, granular, second-level recovery within 35 days — the everyday "oops" defense. On-demand backups (and AWS Backup, which orchestrates them with retention policies, cross-Region copies, and vault locking) are for long-term retention and compliance — keeping a quarter-end snapshot for seven years, or a copy in another Region for DR. They're complementary: PITR handles the bad deploy you catch this week; AWS Backup handles the auditor who asks for last year's data. Most production tables should have both.

# Restore a corrupted table to the exact second before a bad migration started.
# This creates a NEW table; the live (corrupted) table is untouched.
aws dynamodb restore-table-to-point-in-time \
  --source-table-name orders-prod \
  --target-table-name orders-prod-recovered \
  --restore-date-time 2026-05-26T02:14:00+00:00

# Validate the restored copy, then cut over. Two common patterns:
#   1. Repoint the service's table name at orders-prod-recovered, or
#   2. Delete the corrupted original and restore again under the original name.
aws dynamodb describe-table --table-name orders-prod-recovered \
  --query 'Table.{Items:ItemCount,Status:TableStatus}'

What is the impact of a table without PITR?

The direct impact is unrecoverable data loss from the failures that actually happen. A migration script that runs twice, a deploy that rolls back and replays writes, a fat-fingered DeleteItem loop against the wrong table, a logic bug that quietly corrupts items for hours — every one of these is recoverable in 90 minutes with PITR and potentially unrecoverable without it. The recovery cost without PITR is hours to weeks of engineering reconstructing data from upstream sources, logs, or event streams, assuming those even exist and are complete.

The second-order impact is incident behaviour under pressure. The team with PITR runs one restore-table-to-point-in-time call, validates the new table, and cuts over — a contained, rehearsed procedure. The team without it improvises a recovery at 3am from whatever fragments they can find, in exactly the state where mistakes are most expensive and most likely. The presence of PITR changes a panic into a runbook.

The third impact is regulatory and contractual. SOC 2, PCI DSS, and most enterprise SLAs require demonstrable backup and recovery for systems of record. A customer-facing database with no point-in-time recovery is an audit finding the moment someone asks the question, and it undercuts uptime and data-integrity commitments you've made to customers. The audit and breach-disclosure costs dwarf the cost of the feature.

On the cost side, PITR is roughly $0.20 per GB-month of table size — note that it scales with how much data the table holds, not how many reads and writes it serves. A 64 GB table is about $13 a month; a 500 GB table about $100. Restores incur a separate one-time charge based on the size of the restored data. None of these numbers move a budget. The asymmetry is the whole point: a small, predictable, data-proportional cost caps an open-ended exposure.

How do you roll out PITR safely across the estate?

PITR rollout is a four-step loop that runs at every reliability cadence: inventory what's unprotected, enable it on everything that matters, prove the recovery path actually works, and make it the default so coverage never drifts.

1. Inventory every table by PITR status and data class

Pull every DynamoDB table across every Region and account and record its PITR status, size in GB, and a data classification — production, business-critical, ephemeral. Scratch, load-test, and session-cache tables genuinely don't need it; tables behind customer-facing services do. Without this step you'll either over-protect throwaway tables or, far worse, miss a load-bearing one hiding behind an innocuous name.

2. Enable PITR on every production and business-critical table

Turn it on with a single update-continuous-backups call per table — it's instant, has no throughput impact, and the 35-day window starts accumulating immediately. Remember it is not retroactive: enabling it today protects you going forward, not against yesterday's mistake. There's no reason to stage this slowly; the cost is data-proportional and small, and there's no downside to having a rewind window you never use.

3. Verify recovery, not just that PITR is enabled

An enabled flag is a hypothesis, not a tested recovery. At least quarterly, run restore-table-to-point-in-time against a production table into a scratch table and validate the data. This is where teams learn the restore-creates-a-new-table mechanic, find the IAM and KMS permissions they're missing on the restore path, and rehearse the cut-over (repoint the app, or delete-and-restore under the original name) somewhere other than a live incident.

4. Make PITR the default so new tables arrive protected

Bake PointInTimeRecoveryEnabled: true into the table module in your IaC (CDK/Terraform) so every new production table ships with it on. Add an AWS Config rule (dynamodb-pitr-enabled) to flag any table that slips through within minutes, and pair PITR with AWS Backup for long-term and cross-Region retention where compliance requires it. The inventory only stays short if new tables stop arriving unprotected.

# Find every table with PITR disabled across the account, then enable it on the ones that matter.
for t in $(aws dynamodb list-tables --query 'TableNames[]' --output text); do
  STATUS=$(aws dynamodb describe-continuous-backups --table-name "$t" \
    --query 'ContinuousBackupsDescription.PointInTimeRecoveryDescription.PointInTimeRecoveryStatus' \
    --output text)
  echo "$t  pitr=$STATUS"
done

# Enable PITR on a production table (instant, no throughput impact).
aws dynamodb update-continuous-backups \
  --table-name orders-prod \
  --point-in-time-recovery-specification PointInTimeRecoveryEnabled=true

# Quarterly: prove the recovery path by restoring to a scratch table and validating.
aws dynamodb restore-table-to-point-in-time \
  --source-table-name orders-prod \
  --target-table-name orders-prod-restore-test \
  --use-latest-restorable-time

Quick quiz

Question 1 of 5

A migration ran twice and corrupted millions of items in orders-prod three hours ago. PITR has been enabled on the table for months. What's the right recovery move?

You've completed Enable DynamoDB point-in-time recovery. You now know why a multi-AZ, highly-durable table still needs a rewind button, how PITR's continuous backups and rolling 35-day window work, the crucial restore-to-a-new-table mechanic that shapes your recovery runbook, how PITR complements on-demand backups and AWS Backup, and the four-step inventory-enable-verify-default loop that keeps coverage at 100%. The next time a continuity scan flags a table with PITR disabled, you'll have a defensible path from "flagged" to "protected" in minutes.

Back to the library