golang-migrate/migrate · critical
database is dirty
Error message
database is dirty
What it means
ErrDatabaseDirty indicates the migrations table contains a row whose 'dirty' flag is true, meaning a previous migration failed partway (applied but not recorded, or recorded but not applied). The Cassandra driver returns this from Open/WithInstance and refuses to run further migrations until the state is manually resolved. It protects the schema from inconsistent concurrent or partial application.
Source
Thrown at database/cassandra/cassandra.go:34
)
func init() {
db := new(Cassandra)
database.Register("cassandra", db)
}
var (
multiStmtDelimiter = []byte(";")
DefaultMultiStatementMaxSize = 10 * 1 << 20 // 10 MB
)
var DefaultMigrationsTable = "schema_migrations"
var (
ErrNilConfig = errors.New("no config")
ErrNoKeyspace = errors.New("no keyspace provided")
ErrDatabaseDirty = errors.New("database is dirty")
ErrClosedSession = errors.New("session is closed")
)
type Config struct {
MigrationsTable string
KeyspaceName string
MultiStatementEnabled bool
MultiStatementMaxSize int
}
type Cassandra struct {
session *gocql.Session
isLocked atomic.Bool
// Open and WithInstance need to guarantee that config is never nil
config *Config
}
View on GitHub (pinned to 01a9643f14)
Solutions
- Inspect the schema_migrations table in the keyspace and determine whether the last migration actually applied.
- Fix the schema manually (apply or roll back the partial migration), then force the version: migrate.Force(version) with the correct non-dirty version.
- Re-run migrations; if the failure keeps recurring, run migrations from a single serialized process (avoid concurrent deployers).
Example fix
// before (retrying while dirty just fails again)
// after resolving the state manually:
m, _ := migrate.New("cassandra://host/keyspace", "file://migrations")
err := m.Force(3) // set version 3, dirty=false, after repairing schema Defensive patterns
Strategy: try-catch
Validate before calling
var version, dirty bool
err := session.Query("SELECT dirty FROM " + migrationsTable).Scan(&dirty)
// if dirty is true, resolve before running migrations Try / catch
if err := m.Up(); err != nil {
if errors.Is(err, cassandra.ErrDatabaseDirty) {
// inspect schema_migrations, repair schema, then:
if ferr := m.Force(lastGoodVersion); ferr != nil { return ferr }
return m.Up()
}
return err
} Prevention
- Run migrations from a single serialized runner (no concurrent deployers).
- Wrap migrations in a job that is not killed mid-run (graceful shutdown windows).
- Alert on ErrDatabaseDirty so the state is repaired promptly.
- Keep migrations small so a crash window is minimal.
When it happens
Trigger: A previous migration run crashed or was killed mid-migration; SetVersion was called with dirty=true and never cleared; two migrate processes raced and one left the version row dirty.
Common situations: Deploy pod killed during migration, network drop between applying DDL and updating the version row, developer interrupting a long migration locally.
Related errors
AI-assisted analysis of golang-migrate/migrate@01a9643f14 (2026-09-02).
Data as JSON: /api/errors/93d31554019e08f5.
Report an issue: GitHub.