FoundationDB Migrations
This guide documents migration behavior in FoundationDBDataStore and operational controls.
Preferred API:
- pass migration settings as
migrationConfiguration = MigrationConfiguration(...) - pass distributed lease tuning as
migrationLeaseConfiguration = FoundationDBMigrationLeaseConfiguration(...) - flat migration arguments on
FoundationDBDataStore.open(...)still work for compatibility
When migration is required
Section titled “When migration is required”On startup, the store compares stored model definitions with configured models.
No migration required (automatic):
- new model
- safe property additions
- compatible index additions (with backfill)
- relaxed validation
Migration required:
- incompatible type changes
- removals/renames without compatibility aliases
- stricter constraints that break compatibility
Handler contract
Section titled “Handler contract”All migration hooks receive MigrationContext<FoundationDBDataStore> and return MigrationOutcome:
SuccessPartialRetryFatal
Available hooks:
migrationExpandHandlermigrationHandler(Backfill)migrationVerifyHandlermigrationContractHandler
Runtime phases
Section titled “Runtime phases”Execution phases:
ExpandBackfillVerifyContract
Progress is persisted via MigrationState per model (phase/status/attempt/cursor/message).
Restart resumes from persisted state.
Current handler hooks:
Expand: runsmigrationExpandHandlerBackfill: runsmigrationHandlerVerify: runsmigrationVerifyHandlerContract: runsmigrationContractHandler
For a compatible index addition, versionUpdateHandler runs while the model remains fenced as Rebuilding. Writes through the same FoundationDBDataStore are allowed only after each transaction proves ownership of that exact rebuild fence. Other open stores remain blocked until the hook succeeds and the definition and Ready epoch publish together.
Dependency ordering and cycles
Section titled “Dependency ordering and cycles”Model migrations are ordered by dependency graph.
- Referenced models migrate first.
- A dependent handler does not start until each referenced model has completed finalization and definition publication.
- Dependency cycles are detected and rejected up front with
MigrationException.
Startup budget and background continuation
Section titled “Startup budget and background continuation”Use:
migrationConfiguration.migrationStartupBudgetMsmigrationConfiguration.continueMigrationsInBackground = true
Behavior:
- Startup can hand migration over to background.
- Pending models remain request-blocked until completion.
- Dependents waiting on a background migration remain blocked too; they do not delay opening the store or access to unrelated models.
- Startup clock recovery reads raw version metadata before resuming an unfinished schema rebuild. Request-level schema fencing remains active.
Operator APIs
Section titled “Operator APIs”pendingMigrations()migrationStatus(modelId)migrationStatuses()awaitMigration(modelId)pauseMigration(modelId)resumeMigration(modelId)cancelMigration(modelId, reason)
cancelMigration is terminal for the current store instance: the model stays blocked and you must reopen the store to resume from persisted state.
Operator semantics:
awaitMigration(modelId)returns normally when migration completes successfully.awaitMigration(modelId)completes exceptionally withMigrationExceptionif migration fails or is canceled.pauseMigration(modelId)only affects pending/background progress. It does not interrupt a currently running phase step.- While paused, the model remains request-blocked.
resumeMigration(modelId)allows the next background loop iteration to continue from the persistedMigrationState.- After
cancelMigration, the current store instance keeps the model blocked. Reopening the store resumes from the persisted phase/cursor.
Lease behavior (distributed)
Section titled “Lease behavior (distributed)”Default lease is FoundationDBMigrationLease.
- Lease key per model in metadata subspace
- owner token
- monotonically increasing fencing token, checked in every datastore mutation transaction submitted by a migration or version hook, including mutations for another registered model
- TTL via
migrationLeaseConfiguration.migrationLeaseTimeoutMs - heartbeat via
migrationLeaseConfiguration.migrationLeaseHeartbeatMs - automatic takeover after TTL expiry
Use custom migrationLease only if external orchestrator semantics are needed.
The fencing token protects FoundationDB transactions made through the datastore. It cannot roll back or fence arbitrary external side effects. Hooks that call external systems must be idempotent or use an external coordinator/fencing token appropriate to that system; do not rely on host wall-clock lease expiry alone for those effects.
Observability
Section titled “Observability”Retry thresholds:
migrationConfiguration.migrationRetryPolicy.maxAttemptsmigrationConfiguration.migrationRetryPolicy.maxRetryOutcomes
Status payload includes:
- state
- phase
- attempt
- last error
- cursor presence
- retry count
- ETA estimate
Metrics:
migrationMetrics(modelId)/migrationMetrics()
Audit:
- default sink:
migrationConfiguration.migrationAuditEventReporter(default line logger) - optional persisted audit store:
migrationConfiguration.persistMigrationAuditEvents = true - query persisted events:
migrationAuditEvents(modelId, limit)
Operational rollout pattern
Section titled “Operational rollout pattern”- Enable background continuation for long-running migrations.
- Set retry thresholds for bounded failure handling.
- Monitor runtime status/metrics.
- Intervene with pause/resume/cancel only when required.
- Keep persisted audit logs opt-in; use default reporter for baseline observability.
Minimal four-phase example
Section titled “Minimal four-phase example”FoundationDBDataStore.open( keepAllVersions = true, directoryPath = listOf("maryk", "app"), dataModelsById = mapOf(1u to Account), migrationConfiguration = MigrationConfiguration( migrationExpandHandler = { context -> // Prepare compatibility before data rewrite MigrationOutcome.Success }, migrationHandler = { context -> // Backfill existing rows MigrationOutcome.Success }, migrationVerifyHandler = { context -> // Validate rewritten data MigrationOutcome.Success }, migrationContractHandler = { context -> // Final cleanup after verification MigrationOutcome.Success } ))