<- all tokdocs

Stop Writing Database Migrations: Use Event Sourcing Instead

Watch on TikTok

View on TikTok ->

A principal software architect at TwinHealth makes a direct case against traditional database migrations. His alternative: store data as events with JSON payloads, eliminate migration files entirely, and deploy code in any order without downtime. The approach draws on event sourcing principles and solves real scaling problems that most teams treat as unavoidable cost.

The Standard Migration Problem

Most backend teams follow the same pattern. You write a migration file that changes the database schema, and that file runs automatically when you deploy. It works fine when you have a small team and a single deployment target.

The problems show up at scale. Migrations can require downtime because the schema change has to finish before the application can use the new structure. If you have a distributed system with multiple pods or instances, you cannot guarantee they all switch over at the same instant. Two engineers working on conflicting migrations can produce a broken state that is painful to untangle. The speaker ran into all of these at Heartbeat Health, his previous startup, where a DevOps engineer asked a simple question: why do we need to do migrations at all?

The Event-Based Alternative

The answer they landed on was event sourcing with JSON payloads. Instead of storing data in rigid SQL columns that require schema changes, they stored every change as an event in a simple table. Each event carries a JSON document as its payload.

Take a ticketing system as an example. Instead of a tickets table with columns for title, description, assignee, and comments, you have a ticket_events table. One event says "create ticket with this title." Another says "set description." Another says "assign this user." Each is a row with a JSON body. The table schema itself never changes, so there is nothing to migrate.

Adding a new field means adding a new key to your JSON. No migration file. No downtime. No coordination across engineers.

Forward and Backward Compatibility

The real engineering discipline in this approach is compatibility. Two rules govern the system:

  1. Old code reading new events must ignore fields it does not recognize. If you add a priority field to ticket events, older code that does not know about priorities should keep working without errors.

  2. New code reading old events must handle missing fields gracefully by inserting meaningful defaults. If the new code expects a priority field and the old event does not have one, it defaults to something sensible like "medium."

This is the same contract that powers Protocol Buffers, Avro, and most well-designed API versioning schemes. It is a proven pattern, not an experimental idea.

Handling Breaking Changes

Sometimes you genuinely cannot maintain backward compatibility. The speaker's solution: create a new event type with its own schema, and dual-write both the old and new event types during the transition. If you need to roll back, the old events are still there. Old code keeps reading the old events. New code reads the new ones.

You keep dual-writing until you are certain nothing in production depends on the old format. There is no rush. The only cost is a small amount of extra storage, which is trivially cheap compared to the cost of a botched migration causing downtime.

Why This Matters for Kubernetes and Horizontal Scaling

The speaker highlights a specific scenario: deploying to a multi-pod Kubernetes cluster. During a rolling deployment, some pods run the old code and some run the new code simultaneously. Traditional migrations make this dangerous because the schema change affects all pods at once but the code rollout is gradual.

With event sourcing, each pod can run a different code version and still read and write correctly. Pods get replaced over time, and eventually everything runs the new version. No coordination step required.

What the Video Does Not Cover

The speaker focuses on the benefits but leaves out some practical realities worth noting. Event sourcing adds complexity in querying and reporting. You often need materialized views or projections to reconstruct the current state of an entity from its event stream, which introduces its own operational overhead. Event stores can also grow large over time, and you may need a compaction or snapshotting strategy. These are solvable problems, but they are not free.

The approach also works best for write-heavy, event-driven systems like the healthcare platforms the speaker has built. For a simple CRUD app with a small team, traditional migrations with a tool like Alembic or Flyway may still be the simpler path.

Key Takeaways

  • Traditional database migrations cause downtime, conflict between engineers, and break rolling deployments in distributed systems.
  • Event sourcing with JSON payloads eliminates schema migrations by storing changes as events in a fixed table structure.
  • Forward and backward compatibility are the core discipline: old code ignores unknown fields, new code provides defaults for missing fields.
  • Breaking changes are handled by dual-writing both old and new event types during a transition period.
  • This pattern is especially valuable for Kubernetes deployments and any system with horizontal scaling where pods run different code versions simultaneously.

Resources

Published June 11, 2026. Writeup generated from a favorited TikTok.