vitessio/vitess · error

BeforeSchema differs

Error message

BeforeSchema differs

What it means

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.

Source

Thrown at go/vt/mysqlctl/schema.go:527

			// let's see if the schema was already applied
			if change.AfterSchema != nil {
				schemaDiffs = tmutils.DiffSchemaToArray("actual", beforeSchema, "expected", change.AfterSchema)
				if len(schemaDiffs) == 0 {
					// no diff between the schema we expect
					// after the change and the current
					// schema, we already applied it
					return &tabletmanagerdatapb.SchemaChangeResult{
						BeforeSchema: beforeSchema,
						AfterSchema:  beforeSchema,
					}, nil
				}
			}

			if change.Force {
				log.Warn("BeforeSchema differs, applying anyway")
			} else {
				return nil, errors.New("BeforeSchema differs")
			}
		}
	}

	sql := change.SQL

	// The session used is closed after applying the schema change so we do not need
	// to worry about saving and restoring the session state here
	if change.SQLMode != "" {
		sql = fmt.Sprintf("SET @@session.sql_mode='%s';\n%s", change.SQLMode, sql)
	}

	if !change.AllowReplication {
		sql = "SET sql_log_bin = 0;\n" + sql
	}

	if change.DisableForeignKeyChecks {
		sql = "SET foreign_key_checks = 0;\n" + sql

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Re-fetch the current schema (GetSchema) and supply it as BeforeSchema, then retry
  2. Set change.Force = true only if the diff is understood and acceptable (a warning is logged instead)
  3. Investigate schema drift: compare the diff reported in logs and reconcile (e.g. re-run online DDL or fix manually)
  4. Ensure only one schema-change workflow targets the tablet at a time (check workflow/vreplication state)

Example fix

// before
res, err := tm.ApplySchemaChange(ctx, &tms.SchemaChange{SQL: sql, BeforeSchema: staleSchema})
// after
before, err := tm.GetSchema(ctx, "/", nil, true)
res, err := tm.ApplySchemaChange(ctx, &tms.SchemaChange{SQL: sql, BeforeSchema: before})
Defensive patterns

Strategy: retry

Validate before calling

before, err := tm.GetSchema(ctx, "/", nil, true)
if err != nil {
    return err
}
if !schemaEqual(before, expectedBefore) {
    return fmt.Errorf("tablet schema drifted from expectation; refresh before applying")
}

Type guard

func schemasMatch(a, b *tabletmanagerdatapb.SchemaDefinition) bool {
    return reflect.DeepEqual(a.GetTableDefinitions(), b.GetTableDefinitions())
}

Try / catch

res, err := tm.ApplySchemaChange(ctx, change)
if err != nil && strings.Contains(err.Error(), "BeforeSchema differs") {
    fresh, ferr := tm.GetSchema(ctx, "/", nil, true)
    if ferr != nil { return ferr }
    change.BeforeSchema = fresh
    res, err = tm.ApplySchemaChange(ctx, change) // retry once with fresh snapshot
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/91af7d8a6d95f565. Report an issue: GitHub.