Module 2 · Databases

Lesson 11 — Versioned migrations

Zero-downtime schema changes: how to evolve TicketFlow with real data inside.

Published
In this lesson
  1. Exercise 1 — The rename without downtime
  2. Exercise 2 — The dangerous NOT NULL, done safely
  3. Exercise 3 — CONCURRENTLY index
  4. Exercise 4 — Lesson 08's three improvements
  5. Submit

On a new branch (feat/safe-migrations) with a copy of your DB. Do not look at solutions.md before submitting.

Exercise 1 — The rename without downtime

Model: your event has title (if it's already called that, create a temporary twin field for the exercise).

  1. Add label = CharField(null=True) (EXPAND) and migrate.
  2. Write the data migration copying title → label in batches (paginated by pk), with a noop reverse.
  3. Change the code to write/read label (dual write: save to both). Demonstrate in the shell that both columns stay equal after creating 3 events.
  4. Simulate the SWITCH (the code now only uses label) and write the CONTRACT migration dropping title. What would running the old code right after this step break?

Exercise 2 — The dangerous NOT NULL, done safely

  1. Try AddField(field=CharField(max_length=16, null=False, default="")) on a table with rows: what does Django generate and what does PostgreSQL do?
  2. Do it the long way: add nullable → backfill with RunPython in batches → AlterField to NOT NULL. Verify each step's time mentally (or \timing on the SQL steps).
  3. Install django-migration-linter and run the lint on your app: does it flag dangerous operations in your history?

Exercise 3 — CONCURRENTLY index

  1. In a migration with atomic = False, use RunSQL to create a CONCURRENTLY index on a queried column (e.g. reservation.created_at).
  2. Try the same without CONCURRENTLY on a big table (or simulate with 100k rows): does it block writes while running? What happens if the CONCURRENTLY fails halfway (an INVALID index)?
  3. Write the check to detect INVALID indexes: SELECT * FROM pg_index WHERE NOT indisvalid;

Exercise 4 — Lesson 08's three improvements

Apply your noted improvements with safe migrations (one per improvement, green lint):

  1. NOT NULL with backfill where appropriate.
  2. Coherence CHECK (expires_at > created_at on reservation) — what happens to old rows violating it? (backfill/fix first).
  3. Unique slug on event with generation for the historical rows.

Submit

Paste migrations and outputs. Lesson 11 closes; next Lesson 12 — NoSQL.