{"record":{"id":"99c620692c9c5613","repo":"golang-migrate/migrate","slug":"database-is-dirty-99c620","errorCode":null,"errorMessage":"database is dirty","messagePattern":"database is dirty","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"database/spanner/spanner.go","lineNumber":39,"sourceCode":"\n\tadminpb \"cloud.google.com/go/spanner/admin/database/apiv1/databasepb\"\n\t\"google.golang.org/api/iterator\"\n)\n\nfunc init() {\n\tdb := Spanner{}\n\tdatabase.Register(\"spanner\", &db)\n}\n\n// DefaultMigrationsTable is used if no custom table is specified\nconst DefaultMigrationsTable = \"SchemaMigrations\"\n\n// Driver errors\nvar (\n\tErrNilConfig      = errors.New(\"no config\")\n\tErrNoDatabaseName = errors.New(\"no database name\")\n\tErrNoSchema       = errors.New(\"no schema\")\n\tErrDatabaseDirty  = errors.New(\"database is dirty\")\n\tErrLockHeld       = errors.New(\"unable to obtain lock\")\n\tErrLockNotHeld    = errors.New(\"unable to release already released lock\")\n)\n\n// Config used for a Spanner instance\ntype Config struct {\n\tMigrationsTable string\n\tDatabaseName    string\n\t// Whether to parse the migration DDL with spansql before\n\t// running them towards Spanner.\n\t// Parsing outputs clean DDL statements such as reformatted\n\t// and void of comments.\n\tCleanStatements bool\n}\n\n// Spanner implements database.Driver for Google Cloud Spanner\ntype Spanner struct {\n\tdb *DB","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/golang-migrate/migrate/blob/01a9643f1475e75bb6d6224ddeaf9d8e2434ca8a/database/spanner/spanner.go#L21-L57","documentation":"spanner.ErrDatabaseDirty indicates the Spanner migrations table holds a version row with dirty=true: a previous migration was interrupted after partially applying. The driver returns this on Open and stops, requiring a human to inspect the schema and reset the state via migrate.Force. It prevents silently building on an inconsistent schema.","triggerScenarios":"A Spanner migration run crashed or timed out mid-statement; SetVersion was invoked with dirty=true and never resolved; concurrent migrate instances raced on the same database.","commonSituations":"Deployment killed during a long DDL batch (Spanner DDL can be slow), CI job cancelled mid-migration, network failure between applying statements and clearing the dirty flag.","solutions":["Query the migrations table (default 'SchemaMigrations') and inspect the last recorded version and dirty flag.","Manually apply or revert the partial migration, then call migrate.Force(version) to clear the dirty flag.","Re-run migrations from a single serialized runner to avoid repeat interruptions."],"exampleFix":"// before (migration keeps failing while dirty)\n// after repairing schema state:\nm, _ := migrate.New(\"spanner://projects/p/instances/i/databases/d\", \"file://migrations\")\nerr := m.Force(7) // mark version 7 as applied, dirty=false","handlingStrategy":"try-catch","validationCode":"var dirty bool\nerr := client.Single().Query(ctx, spanner.Statement{SQL: \"SELECT dirty FROM SchemaMigrations\"}).Do(func(r *spanner.Row) error { return r.Column(0, &dirty) })\n// resolve dirty state before running migrations if true","typeGuard":null,"tryCatchPattern":"if err := m.Up(); err != nil {\n    if errors.Is(err, spanner.ErrDatabaseDirty) {\n        // inspect SchemaMigrations, repair partial DDL, then:\n        if ferr := m.Force(lastGoodVersion); ferr != nil { return ferr }\n        return m.Up()\n    }\n    return err\n}","preventionTips":["Run Spanner migrations from a single serialized job; Spanner DDL can be slow, so allow generous timeouts.","Avoid killing deploy pods mid-migration (use graceful termination).","Alert on ErrDatabaseDirty and keep a documented repair runbook (Force + manual DDL).","Split large DDL batches into smaller migrations to shrink the dirty window."],"tags":["go","database","migration","spanner","go-migrate"],"backgroundTag":"migration-dirty-state","analyzedSha":"01a9643f1475e75bb6d6224ddeaf9d8e2434ca8a","analyzedAt":"2026-09-02T19:38:29.671Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}