Skip to content

tools/ddb_migration: add zero-downtime migration toolkit + one-click demo - #164

Draft
chauhansarthak wants to merge 1 commit into
awslabs:mainfrom
chauhansarthak:feat/ddb-migration-toolkit
Draft

tools/ddb_migration: add zero-downtime migration toolkit + one-click demo#164
chauhansarthak wants to merge 1 commit into
awslabs:mainfrom
chauhansarthak:feat/ddb-migration-toolkit

Conversation

@chauhansarthak

Copy link
Copy Markdown

Summary

Adds tools/ddb_migration/ — a general-purpose toolkit for migrating an Amazon DynamoDB table to a new table without taking writes offline. Built on three native DynamoDB features (Export to S3 + Streams + conditional writes) with a single-attribute (_migration_ts) conflict-resolution scheme.

Use cases: MRSC adoption on Global Tables, cross-account migrations, schema changes, billing-mode transitions on large tables, single-table consolidation.

Architecture

Three overlapping phases:

  1. Capture & bulk copy — enable Streams, export to S3, start the stream-replay Lambda, run backfill.py (writes with _migration_ts=0).
  2. Catch-up — Lambda replays live writes with _migration_ts = ApproximateCreationDateTime. Conditional attribute_not_exists(#pk) OR #ts < :ts ensures newer wins.
  3. Convergence & cutoverconvergence_check.py blocks until iterator age, DLQ, and Scan COUNT all agree.

REMOVE events become _tombstone=True items so the in-flight backfill cannot resurrect deletes; cleanup.py enables DynamoDB TTL post-cutover to auto-expire them.

Files

Path Purpose
lambda/stream_replay.py Streams replay Lambda; honors TRANSFORM_MODULE, structured JSON logging
scripts/backfill.py Parallel S3-export → target loader with iterator-age circuit breaker, --dry-run
scripts/convergence_check.py Pre-cutover gate: iterator age (with idle-grace), DLQ depth, Scan COUNT (excluding tombstones)
scripts/cleanup.py Post-cutover: TTL-based tombstone expiry, _migration_ts removal
scripts/verify_cutover.py Sample-based source ↔ target parity check
transform.py Shared per-item transform (customize for schema changes)
deploy.sh / teardown.sh Idempotent provisioning + ordered cleanup of Lambda, IAM role, DLQ, SNS, alarms (with --alarm-actions)
iam/policies.json Reference IAM templates including cross-account variants
demo/ One-click demo (gated behind DDB_MIGRATION_DEMO_CONFIRM=yes)
tests/ 37 moto-backed unit tests, ~70% line coverage

Test plan

  • make install && make test — 37 tests pass with moto
  • bash -n syntax check on deploy.sh, teardown.sh, demo/run_demo.sh
  • python -m py_compile on every .py file
  • End-to-end demo run in a dev account (us-east-1):
    • 10,000 seed items + ~436 live mutations during migration
    • Backfill correctly skipped 10,000 (Lambda already had newer _migration_ts for every item — conflict resolution working as designed)
    • Source=10,274 / target_live=10,274 (tombstones excluded) / drift=0.0000
    • verify_cutover.py --sample-size 500 → 500 matched, 0 missing, 0 diverged
    • teardown.sh CONFIRM=yes cleanly removed all provisioned resources

Notes

  • Glue ETL job for tables >100 GiB is intentionally out of scope; backfill.py parallelizes within one host. README documents the limitation.
  • The toolkit follows the layout conventions of sibling tools (bulk_executor, dax_calculator): tool-level NOTICE, Makefile, pytest.ini, requirements.txt + requirements-dev.txt, no per-file license headers.

By submitting this PR I confirm that my contribution is licensed under Apache 2.0.

…demo

General-purpose tooling for migrating an Amazon DynamoDB table to a new table
without taking writes offline. Built on Streams + S3 Export + conditional
writes with a single-attribute (_migration_ts) conflict-resolution scheme.

Components:
  - lambda/stream_replay.py    Streams replay with newer-wins conditional puts
                               and tombstones for REMOVE events. TRANSFORM_MODULE
                               env var honored for pluggable transforms.
  - scripts/backfill.py        Parallel S3-export -> target loader with iterator-
                               age circuit breaker (configurable cadence) and
                               --dry-run.
  - scripts/convergence_check.py  Pre-cutover gate: iterator age, DLQ depth,
                               and Scan COUNT drift. Exits non-zero on failure.
  - scripts/cleanup.py         Post-cutover: TTL-based tombstone expiry and
                               _migration_ts attribute removal.
  - scripts/verify_cutover.py  Sample-based source <-> target parity check.
  - transform.py               Shared customizable per-item transform.
  - deploy.sh / teardown.sh    Idempotent provisioning of Lambda, IAM role,
                               DLQ, SNS topic, and CloudWatch alarms (with
                               actions). Cross-account branch via TARGET_ACCOUNT
                               + TARGET_ROLE_ARN.
  - iam/policies.json          Reference IAM templates including cross-account.
  - demo/                      One-click demo: seed -> live-write -> migrate ->
                               verify, gated behind DDB_MIGRATION_DEMO_CONFIRM.
  - tests/                     35 moto-backed unit tests; 69% line coverage.

Conflict-resolution invariant: every target write carries _migration_ts.
Backfill writes use 0 (lowest priority); stream replay uses the source event
ApproximateCreationDateTime. The condition
  attribute_not_exists(#pk) OR #ts < :ts
ensures newer timestamps always win, regardless of arrival order.

REMOVE events become _tombstone=True items so the in-flight backfill cannot
resurrect deletions. Post-cutover cleanup.py sets a TTL attribute so DynamoDB
auto-expires the tombstones after the validation period.
@tebanieo

tebanieo commented May 29, 2026

Copy link
Copy Markdown
Contributor

Hello! Thanks for your contribution!

Please see this comment.

I will review the PR and discuss with the team where is the best place to put this contribution.

@tebanieo
tebanieo self-requested a review May 29, 2026 16:24
@tebanieo tebanieo self-assigned this May 29, 2026
@tebanieo tebanieo added the enhancement New feature or request label May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants