SpacetimeDB Versioning and Migration Strategies: A Complete Technical Guide
SpacetimeDB handles schema evolution through module versioning, offering automatic migrations for additive changes via the ponder_migrate planner and incremental migration patterns for complex breaking changes.
SpacetimeDB versioning and migration strategies center on the concept of module versioning, where each republish compares the new ModuleDef against the stored definition to generate a migration plan. According to the clockworklabs/SpacetimeDB source code, the runtime in crates/schema/src/auto_migrate.rs implements two complementary approaches: automatic migrations for safe schema changes and incremental migrations for complex transformations.
Understanding SpacetimeDB Module Versioning
When you invoke spacetime publish, the CLI loads your module's ModuleDef and retrieves the existing definition from the database. The runtime compares these definitions to determine what has changed. This comparison happens in the migration planner, which produces a MigratePlan enum that can be either Manual or Auto.
The migration process preserves existing client connections during automatic updates, ensuring that active subscriptions remain functional. Only reducers that disappear or change signatures may trigger runtime errors for stale clients.
Automatic Migrations for Schema Evolution
Automatic migrations handle simple, additive schema changes that the engine can apply without data loss. The planner function ponder_migrate in crates/schema/src/auto_migrate.rs builds an AutoMigratePlan that executes during the publish operation.
How Automatic Migrations Work
The automatic migration flow follows four stages:
- Definition comparison – The runtime diffs the new
ModuleDefagainst the stored version. - Plan generation –
ponder_migrateanalyses differences and produces aMigratePlan. - Policy enforcement – A
MigrationPolicy(compatible or break-clients) validates the plan viaMigrationPolicy::permits_planand may require aMigrationTokenfor breaking changes. - Execution – The runtime applies safe schema modifications while keeping active subscriptions alive.
Allowed Changes in Automatic Migrations
The SpacetimeDB automatic migration system categorizes schema changes into three groups:
Always allowed (non-breaking):
- New tables
- New indexes
- Adding or removing
Auto Incannotations - Toggling table visibility
- New reducers
- Removing
Uniqueconstraints
Potentially breaking (requires migration token):
- Adding a new column with a default value
- Changing or removing existing reducers
- Making a public table private
- Removing primary-key annotations
- Removing indexes used by join subscriptions
Forbidden (must use incremental migration):
- Dropping tables
- Removing or modifying existing columns
- Adding columns without defaults
- Inserting columns in the middle of a table
- Adding
UniqueorPrimary Keyconstraints to existing columns - Changing a table's scheduling flag
Handling Breaking Changes with Migration Tokens
When your schema change falls into the "potentially breaking" category, the CLI requires a migration token to proceed. Generate the token using the database identity and module hashes:
spacetime publish mydb \
--migration-token "$(spacetime token generate --db mydb --old-hash <old> --new-hash <new>)"
The token's hash is computed from the database identity and module hashes as implemented in crates/schema/src/auto_migrate.rs (lines 159-167). This satisfies the MigrationPolicy::BreakClients path, allowing the publish to proceed while acknowledging that existing clients may experience runtime errors if they depend on changed reducers.
Incremental Migration Strategy for Complex Changes
For schema changes that violate automatic migration rules—such as dropping tables, removing columns, or changing column types—SpacetimeDB provides the incremental migration pattern. This approach avoids downtime and maintains backward compatibility during the transition.
The Four-Step Incremental Pattern
The incremental migration strategy follows a systematic approach:
- Introduce a new table – Create a table with the target schema (e.g.,
character_v2) alongside the existing table. - Dual-write – Update reducers to write to both the old and new tables, ensuring legacy data stays synchronized.
- Lazy migration – When clients access data, check the new table first. If the record is missing, read from the old table, convert the data, insert it into the new table, and optionally update the old table.
- Deprecation – Once all clients have upgraded and data is fully migrated, remove the legacy tables and redundant code.
This pattern leverages SpacetimeDB's hot-swap capability to roll out schema changes without interrupting active connections.
Incremental Migration Code Example
Consider migrating a Character table to add an alliance field that cannot be automatically migrated:
// Legacy table remains for backward compatibility
#[spacetimedb::table(name = character, public)]
pub struct Character {
#[primary_key] player_id: Identity,
nickname: String,
level: u32,
}
// New table with desired schema
#[spacetimedb::table(name = character_v2, public)]
pub struct CharacterV2 {
#[primary_key] player_id: Identity,
nickname: String,
level: u32,
alliance: Alliance,
}
#[derive(SpacetimeType, Clone, Copy)]
pub enum Alliance { Good, Neutral, Evil }
// Lazy migration helper
fn find_character(ctx: &ReducerContext) -> CharacterV2 {
if let Some(c) = ctx.db.character_v2().player_id().find(ctx.sender) {
return c; // Already migrated
}
let old = ctx.db.character().player_id().find(ctx.sender).unwrap();
ctx.db.character_v2().insert(CharacterV2 {
player_id: old.player_id,
nickname: old.nickname,
level: old.level,
alliance: Alliance::Neutral,
})
}
All reducers (create_character, rename_character, level_up_character, etc.) should be rewritten to operate on CharacterV2 while maintaining dual-write logic to the legacy character table for backward compatibility.
Migration Workflow Best Practices
When managing SpacetimeDB versioning and migration strategies in production environments, follow these guidelines:
- Validate before deploying – Always run
spacetime publish --dry-runto preview migration plans and catch forbidden changes early. - Use automatic migrations for additive changes – New tables, indexes, and columns with defaults should leverage the automatic path to minimize code complexity.
- Reserve incremental migrations for structural changes – When dropping columns, changing types, or removing tables, use the dual-write pattern to maintain availability.
- Generate tokens for breaking changes – When removing reducers or changing public table visibility, obtain a migration token to satisfy the
MigrationPolicy::BreakClientsrequirement. - Monitor client compatibility – Track which client versions are active before removing legacy tables or reducers to ensure all users have migrated.
Summary
SpacetimeDB versioning and migration strategies provide two complementary approaches for evolving your database schema:
- Automatic migrations handle additive changes through the
ponder_migrateplanner incrates/schema/src/auto_migrate.rs, allowing new tables, indexes, and columns with defaults without downtime. - Incremental migrations support complex structural changes via dual-write patterns and lazy data migration, enabling you to add non-default columns, drop tables, or modify types while maintaining backward compatibility.
- Migration tokens authorize potentially breaking changes like reducer removal or table visibility changes through the
MigrationPolicysystem.
Frequently Asked Questions
What types of schema changes can SpacetimeDB handle automatically?
SpacetimeDB automatically migrates additive changes including new tables, new indexes, adding or removing Auto Inc annotations, toggling table visibility, adding new reducers, and removing Unique constraints. You can also add columns with default values, though this requires a migration token as it may break existing clients. All automatic migrations execute through the ponder_migrate function in crates/schema/src/auto_migrate.rs without interrupting active connections.
How do I perform a breaking change in SpacetimeDB without losing data?
For breaking changes that violate automatic migration rules—such as dropping tables, removing columns, or changing column types—use the incremental migration pattern. Create a new table with your updated schema, implement dual-write logic in your reducers to populate both tables, and use lazy migration to convert data when clients access it. Once all clients have upgraded and data is migrated, remove the legacy table. This approach leverages SpacetimeDB's hot-swap capability to maintain availability throughout the transition.
What is a migration token and when do I need one?
A migration token is a cryptographic hash that authorizes potentially breaking schema changes in SpacetimeDB. You need one when making changes classified as "potentially breaking" by the MigrationPolicy system, such as adding columns with defaults, changing or removing reducers, making public tables private, or removing primary-key annotations. Generate the token using spacetime token generate with your database identity and module hashes, then pass it to spacetime publish with the --migration-token flag to satisfy the MigrationPolicy::BreakClients path.
Can I rename a column in SpacetimeDB using automatic migration?
No, renaming a column is not supported by SpacetimeDB's automatic migration system. The migration planner in crates/schema/src/auto_migrate.rs treats column removal or modification as forbidden changes that would result in data loss. To rename a column, you must use the incremental migration pattern: create a new table with the updated schema including the renamed column, implement dual-write logic to populate both tables, migrate existing data to the new table, and eventually remove the old table once all clients have upgraded.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →