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.
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.