The new playbook for Evolutionary Database Development

Practice #1: DBAs collaborate closely with developers

Rule. The DBA collaborates with developers throughout the feature, not just at gate-review time. The collaboration is asynchronous, inline with the PR, the way other code reviewers participate.

Why is this a durable habit now? The schema diff and migration test results land on every PR automatically (see How the workflow runs in CI above). The DBA reviews a concrete artifact on their own schedule. The migration has already run against a real-data CI branch, so the DBA does not need to mentally simulate the change.

Mechanics:

  • The DBA is a CODEOWNER on schema-affecting paths (migrations/, db/, schema test directories).

  • The DBA reviews on the PR like any other reviewer, async.

  • Review focus shifts from will this break the database to is this the right design? Has it been implemented the right way? Topics: data integrity rules, indexing strategy, design cohesion, future extensibility, long-term maintainability.

Anti-pattern. Not including the DBA in the PR flow with there being legitimate artifacts to review.

The DBA role also gains new responsibilities in policy administration and governance at team scale. (Part three covers those.)

Practice #2: All database artifacts are version controlled with application code

Rule. Every SQL file, migration script and schema test lives in the same repository as the application code. The schema diff and migration test results join the artifact set as PR-time outputs.

Why is this a durable habit now? Branching adds two new artifacts to the set: the per-PR schema diff and the per-PR schema migration test results. Both are generated by CI from the actual branch state. Both land in the PR as concrete evidence about what was changed and how the change migration script performed.

Mechanics:

  • Migration files in a versioned directory (migrations/,  db/migration/, alembic/versions/,  framework-dependent).

  • Schema-affecting tests in the test tree alongside application tests.

  • Schema diff generated per PR by CI, posted as a PR comment.

  • Migration test results in the CI run summary and PR comment.

Anti-pattern. Generating the schema diff outside the PR flow (a separate dashboard the reviewer has to open). The artifact has to live where the review happens. Since the schema changes are tied to the code changes and breaking this dependency creates downstream problems for deployment.

Practice #3: All database changes are migrations

Rule. No manual ALTER TABLE against any environment. Every schema change is a versioned, checked-in migration script. Migrations are idempotent.

Why is this a durable habit now? The migration-as-artifact rule is unchanged from 2003. What is new is the authorship discipline of idempotency. The same migration runs against many branches over the life of a transition, so it has to behave the same way every time. A migration that fails on re-apply is a bug.

Mechanics:

  • Use migration frameworks like flyway, liquibase, Knex, Alembic or others, these frameworks keep track of which migrations have been run and which have not been, this allows the team to apply a command like flyway migrate which just applies the changes that have not been applied (by keeping track of the changes in a metadata table)

  • It’s better to split irreversible work across migrations. For example a migration that adds new columns and drops the source column in one shot makes rollback harder than they need to be, so using the expand first and contract later strategy, provides many more options after a deploy cycle confirms no live readers reference it.

  • The 2006 database refactoring catalog names which refactorings are reversible and which are not. This should enable you to plan your refactorings.

Anti-pattern. A migration that depends on the schema being in a specific intermediate state because of local changes made in the branch. The migration must apply correctly against any parent branch that includes prior migrations.

Practice #4: Everybody gets their own database instance

Rule. Every developer, every PR, every experiment, every test run gets its own Lakebase branch.

Why is this a durable habit now? The extra effort to create docker containers, to install local database servers, acquire licenses, hydrate empty databases with existing schema and test data is no longer required. Just a simple create-branch Lakebase command branches a 1TB database in the same one second as a 1MB database. No data is copied at creation; only modified pages consume storage. Per-developer, per-PR and per-experiment instances are routine.

Mechanics:

  • Per-developer branches: created on demand via databricks postgres create-branch or the SCM extension's branch-create flow.

  • Per-PR branches: created automatically by CI on PR open (pr.yml), destroyed on merge or close (merge.yml). See How the workflow runs in CI above for the PR and merge snippets.

  • Per-experiment branches: forked off staging or production for design exploration; discarded after the experiment.

Anti-pattern. Sharing a development database across the team "for convenience." The contention-driven serialization Part 1 named comes back the moment the branches collapse.

Where Jen's example extends. Her per-developer branch was forked off staging at feature start. The CI branch was forked off staging on PR open. Her A/B exploration branches (Practice #9) were forked off staging in parallel. Four branches across one feature, all in seconds, all isolated.

Practice #5: Developers continuously integrate database changes

Rule. Every PR runs through CI against a fresh Lakebase branch, with migrations applied and tests run against real Postgres.

Why is this a durable habit now? The CI pipeline has had migration discipline since 2010. What is new is per-pipeline isolation: each PR gets its own branch, so integration runs against real-shaped data without contention.

Mechanics:

  • CI creates a branch on PR open. See How the workflow runs in CI above for the PR snippet.

  • Migrations applied against the branch via lakebase-migrate apply.

  • Application tests run against the migrated branch through the ORM, no mocks.

  • Schema diff posted on the PR.

  • Branch destroyed on merge or close.

Anti-pattern. Running PR validation against shared staging. The serialization comes back; the per-PR isolation property is lost.

Practice #6: All database changes are database refactorings

Rule. Schema changes follow named refactoring patterns: Split Column, Rename Column, Move Column, Replace Type and so on. Each has explicit transition mechanics (keep old plus new in parallel, populate from old, swap readers, drop old).

Why is this a durable habit now? The 2006 catalog at databaserefactoring.com names 70+ refactorings with worked examples. What is new in 2026 is a cheap place to rehearse the transition mechanics: a developer branch absorbs the rehearsal; the CI branch verifies; production sees only the verified result.

Mechanics:

  • Identify the refactoring by name. Look it up in the catalog.

  • Apply the named transition mechanics on a per-developer branch. Validate against production-shaped data.

  • Open the PR. CI runs the migration on its own branch and posts the diff.

  • Merge once the diff and the test results land approval.

Anti-pattern. A one-off schema change that does not map to a named refactoring. The 70+ catalog covers the common cases; if you find yourself outside it, you are likely combining multiple refactorings into one migration and should split.

Where Jen's example extends. Her V87 migration is the Split Column refactoring: splitting inventory_code into location_code, batch_number and serial_number. The catalog page at databaserefactoring.com/SplitColumns.html names the transition mechanics. Her branch was the rehearsal space; the PR's CI run was the verification.

Practice #7: Developers can update their databases on demand

Rule. A developer can refresh their branch's database state on demand: reset to the parent's current state, fork a fresh branch off production, discard an experimental branch, share a branch with a teammate. All in seconds.

Why is this a durable habit now? "On demand" in 2026 means one second, isolated, against production-shaped data. None of these operations consult ops calendars or DBA queues.

Mechanics:

  • Reset: delete and recreate the branch from its parent.

  • Fork off production: databricks postgres create-branch --source production.

  • Discard: databricks postgres delete-branch.

  • Share: hand the branch endpoint to a teammate for a pairing session.

Anti-pattern. Treating any branch as durable beyond its purpose. The migration is the durable artifact; the branch is the workspace.

Practice #8: Destructive testing as a default option

Rule. When the blast radius of a destructive action is zero, destructive testing becomes a daily option rather than a quarterly exercise.

Why is this a durable habit now? A branch resets in one second. Anything you do to a branch can be undone by creating a fresh one off the same parent. Destructive tests stop needing ops calendars and approval gates.

Things that now fit inside a normal feature cycle:

  • "What happens if my migration fails halfway through the UPDATE statement?" Run it. Kill the process at 50%. Verify the rollback works. Reset.

  • "What happens if a backup-restore is mid-way through when a failover triggers?" Simulate the partial state. Verify the application's behavior. Reset.

  • "What is the actual time-to-recover for our DR runbook?" Run the runbook. Measure. Reset.

  • “Does this migration run successfully against production shape data or size” verify it before committing code or creating a PR.

Cultural effect. When reset costs nothing, teams stop treating the test database as a precious resource. Tests can be aggressive. Cleanup can be skipped, because the next branch starts fresh.

Where Jen's example extends. Before opening her PR, she took the production-shaped data on her branch and deliberately corrupted around one percent of the inventory_code values to look like edge cases: missing digits, embedded spaces, trailing whitespace. The kinds of artifacts that historical data accumulates. She ran her migration. Two rows failed her substring math. She fixed the script and re-ran. The branch absorbed the destructive test. Production never saw it.

Practice #9: A/B variant prototyping at the database level

Rule. When two designs are in contention, build them on parallel branches, compare against production-shaped data and keep the better solution.

Why is this a durable habit now? Per-branch cost is near zero. Exploring two schema designs no longer requires picking one in advance, and it no longer requires a provisioning process most teams will not undertake for an exploratory question.

Mechanics:

  • Create two branches off the same parent.

  • Apply Design A's migration to one branch, Design B's migration to the other.

  • Run the application against each. Measure what matters: query latency on the common read path, migration time at production volume, index footprint, lock contention under simulated load.

  • Pick the better solution. Discard the sub optimal solution. Document the decision in the PR description so the next person who has to extend the schema knows what was considered and why this design was picked.

Anti-pattern. Running A/B prototypes without writing down the decision and the reasoning. The branches are gone in a second; the design decision should be permanent.

Where Jen's example extends. She considered two designs for the location/batch/serial feature: three new columns on the existing inventory table, or a separate inventory_attributes lookup table keyed by inventory_id, anticipating that more attributes would be added later. She built both on parallel branches off staging. She ran the application's read path against each, measured query performance against production-shaped data and looked at how each migration would scale to production volumes.

The lookup-table version performed worse on the common read path because every inventory display required a join. She shipped the columns version, threw away the lookup-table branch and left a note in the PR description: Considered lookup-table version; rejected because the common read path becomes a join. Revisit if more than five attributes accumulate.