{"record":{"id":"91af7d8a6d95f565","repo":"vitessio/vitess","slug":"beforeschema-differs","errorCode":null,"errorMessage":"BeforeSchema differs","messagePattern":"BeforeSchema differs","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/mysqlctl/schema.go","lineNumber":527,"sourceCode":"\n\t\t\t// let's see if the schema was already applied\n\t\t\tif change.AfterSchema != nil {\n\t\t\t\tschemaDiffs = tmutils.DiffSchemaToArray(\"actual\", beforeSchema, \"expected\", change.AfterSchema)\n\t\t\t\tif len(schemaDiffs) == 0 {\n\t\t\t\t\t// no diff between the schema we expect\n\t\t\t\t\t// after the change and the current\n\t\t\t\t\t// schema, we already applied it\n\t\t\t\t\treturn &tabletmanagerdatapb.SchemaChangeResult{\n\t\t\t\t\t\tBeforeSchema: beforeSchema,\n\t\t\t\t\t\tAfterSchema:  beforeSchema,\n\t\t\t\t\t}, nil\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif change.Force {\n\t\t\t\tlog.Warn(\"BeforeSchema differs, applying anyway\")\n\t\t\t} else {\n\t\t\t\treturn nil, errors.New(\"BeforeSchema differs\")\n\t\t\t}\n\t\t}\n\t}\n\n\tsql := change.SQL\n\n\t// The session used is closed after applying the schema change so we do not need\n\t// to worry about saving and restoring the session state here\n\tif change.SQLMode != \"\" {\n\t\tsql = fmt.Sprintf(\"SET @@session.sql_mode='%s';\\n%s\", change.SQLMode, sql)\n\t}\n\n\tif !change.AllowReplication {\n\t\tsql = \"SET sql_log_bin = 0;\\n\" + sql\n\t}\n\n\tif change.DisableForeignKeyChecks {\n\t\tsql = \"SET foreign_key_checks = 0;\\n\" + sql","sourceCodeStart":509,"sourceCodeEnd":545,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/mysqlctl/schema.go#L509-L545","documentation":"ApplySchemaChange optionally captures the schema before executing the DDL and compares it to the caller-supplied expected before-schema (change.BeforeSchema). If they differ, the DDL is refused with 'BeforeSchema differs' unless change.Force is set, protecting against applying a change on top of an unexpected schema state.","triggerScenarios":"Calling ApplySchemaChange with a non-nil BeforeSchema in tabletmanagerdatapb.SchemaChange whose contents don't match the tablet's actual current schema; concurrent DDL changed the schema between the caller's snapshot and execution; caller built BeforeSchema against a different tablet/keyspace.","commonSituations":"Race between two DDL workflows; applying a change generated on a replica to a tablet whose schema drifted (manual changes, failed prior migration); stale automation cache of the schema.","solutions":["Re-fetch the current schema (GetSchema) and supply it as BeforeSchema, then retry","Set change.Force = true only if the diff is understood and acceptable (a warning is logged instead)","Investigate schema drift: compare the diff reported in logs and reconcile (e.g. re-run online DDL or fix manually)","Ensure only one schema-change workflow targets the tablet at a time (check workflow/vreplication state)"],"exampleFix":"// before\nres, err := tm.ApplySchemaChange(ctx, &tms.SchemaChange{SQL: sql, BeforeSchema: staleSchema})\n// after\nbefore, err := tm.GetSchema(ctx, \"/\", nil, true)\nres, err := tm.ApplySchemaChange(ctx, &tms.SchemaChange{SQL: sql, BeforeSchema: before})","handlingStrategy":"retry","validationCode":"before, err := tm.GetSchema(ctx, \"/\", nil, true)\nif err != nil {\n    return err\n}\nif !schemaEqual(before, expectedBefore) {\n    return fmt.Errorf(\"tablet schema drifted from expectation; refresh before applying\")\n}","typeGuard":"func schemasMatch(a, b *tabletmanagerdatapb.SchemaDefinition) bool {\n    return reflect.DeepEqual(a.GetTableDefinitions(), b.GetTableDefinitions())\n}","tryCatchPattern":"res, err := tm.ApplySchemaChange(ctx, change)\nif err != nil && strings.Contains(err.Error(), \"BeforeSchema differs\") {\n    fresh, ferr := tm.GetSchema(ctx, \"/\", nil, true)\n    if ferr != nil { return ferr }\n    change.BeforeSchema = fresh\n    res, err = tm.ApplySchemaChange(ctx, change) // retry once with fresh snapshot\n}","preventionTips":["Always build BeforeSchema from a live GetSchema immediately before applying","Serialize schema changes through a single workflow/controller","Diff and reconcile drift before scheduling DDL","Use Force only after reviewing the logged diff"],"tags":["schema","ddl","validation","vttablet"],"backgroundTag":"schema-drift-detected","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}