BBMM Technologies
← All articles
7 min readdata, migrations, local-first, schema

Versioning Local Data: Migrations Without Data Loss

By Mykhailo Boichuk · Co-founder & Vice-President

In short

Local data migrations are harder than server migrations because old data lives on many devices at many app versions, and you cannot run them all at once. Safe practice means an explicit schema version stamped on the data, ordered migrations that run forward step by step, validation before destructive steps, and a recovery path when a migration fails. The guiding rule is to never lose data you cannot regenerate.

Why local migrations differ from server ones

On a server you migrate one database under your control, on your schedule, with a backup taken first. On the device, the same data lives in thousands of separate stores, each at whatever app version the user last installed. A person may skip several releases and then update, so a migration must handle a jump across multiple schema versions. There is no single moment when all data is migrated, and you cannot inspect most of it.

Because of this, the migration code must be self-describing and defensive. The data has to carry its own schema version, and the app must be able to bring any older version forward to the current one without assuming an intermediate state was ever applied.

Stamp a version and migrate forward in steps

The reliable pattern stamps an explicit schema version onto the stored data and defines a sequence of ordered, single-step migrations. On launch, the app reads the stored version and applies each migration in order until the data matches the current schema. Each step does one transformation and assumes only the version immediately before it.

  • Store the schema version with the data, not only in the app.
  • Write small single-step migrations rather than one large conditional.
  • Make each migration idempotent so a retry after a crash is safe.

Validate before anything destructive

Some migrations drop columns, merge records, or change formats in ways that cannot be undone. Before any destructive step, validate that the transformation produced what you expect, and where feasible keep the old data until the new shape is confirmed written. A migration that fails halfway should leave recoverable state, not a corrupted store.

The cardinal rule of local migration is to never discard data the user cannot regenerate. When in doubt, keep the old copy until the new one is verified, and accept the temporary storage cost.

Plan for failure on the device

Migrations fail. The device runs out of space mid-write, the app is killed, or the data is in a state the code did not anticipate. A resilient design wraps each migration in a transaction where the storage layer supports it, logs the failure for diagnosis without sending personal data, and offers a recovery path rather than leaving the app unusable. Testing should include upgrades from several old versions, not only from the immediately previous one.

Key takeaways

  • Local data lives on many devices at many versions, so migrations must handle multi-version jumps.
  • Stamp an explicit schema version onto the stored data itself.
  • Apply ordered single-step migrations that are idempotent and crash-safe.
  • Validate before destructive steps and keep old data until the new shape is confirmed.
  • Test upgrades from several old versions, not just the previous one.

Frequently asked questions

Why are on-device migrations harder than server migrations?
Old data lives on many devices at many app versions, you cannot migrate them all at once, and a user may jump across several schema versions in a single update.
How do you avoid losing data during a local migration?
Stamp a schema version on the data, run ordered idempotent migrations, validate before destructive steps, and keep the old copy until the new shape is verified.
What happens if a migration fails mid-way on a device?
A resilient design wraps it in a transaction, leaves recoverable state, logs the failure without personal data, and offers a recovery path rather than breaking the app.

About the author

Mykhailo Boichuk

Co-founder & Vice-President

Mykhailo is an engineer who builds native applications and the systems behind them. He concentrates on macOS and iOS performance, local-first data architecture, and the synchronization problems that come with offline-capable software.