vitessio/vitess · error

schemas are different: %s: %v, %s: %v

Error message

schemas are different:
%s: %v, %s: %v

What it means

DiffSchema compares two SchemaDefinition protos from two tablets. If exactly one side is nil, the comparison cannot proceed and this error records that one tablet reported no schema at all (the two inputs are fundamentally different). Note the message prints the whole proto values, which may be '<nil>'.

Source

Thrown at go/vt/mysqlctl/tmutils/schema.go:221

				if strings.HasPrefix(line, "CREATE TABLE `") {
					lines[i] = strings.Replace(line, "CREATE TABLE `", "CREATE TABLE `{{.DatabaseName}}`.`", 1)
				}
			}
			sqlStrings = append(sqlStrings, strings.Join(lines, "\n"))
		}
	}

	return append(sqlStrings, createViewSQL...)
}

// DiffSchema generates a report on what's different between two SchemaDefinitions
// including views, but Vitess internal tables are ignored.
func DiffSchema(leftName string, left *tabletmanagerdatapb.SchemaDefinition, rightName string, right *tabletmanagerdatapb.SchemaDefinition, er concurrency.ErrorRecorder) {
	if left == nil && right == nil {
		return
	}
	if left == nil || right == nil {
		er.RecordError(fmt.Errorf("schemas are different:\n%s: %v, %s: %v", leftName, left, rightName, right))
		return
	}
	if left.DatabaseSchema != right.DatabaseSchema {
		er.RecordError(fmt.Errorf("schemas are different:\n%s: %v\n differs from:\n%s: %v", leftName, left.DatabaseSchema, rightName, right.DatabaseSchema))
	}

	leftIndex := 0
	rightIndex := 0
	for leftIndex < len(left.TableDefinitions) && rightIndex < len(right.TableDefinitions) {
		// extra table on the left side
		if left.TableDefinitions[leftIndex].Name < right.TableDefinitions[rightIndex].Name {
			if !schema.IsInternalOperationTableName(left.TableDefinitions[leftIndex].Name) {
				er.RecordError(fmt.Errorf("%v has an extra table named %v", leftName, left.TableDefinitions[leftIndex].Name))
			}
			leftIndex++
			continue
		}

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Fix the underlying schema fetch on the nil side (check tablet health, re-run GetSchema)
  2. Initialize/restore the empty tablet from a healthy source
  3. Guard the call: only invoke DiffSchema when both SchemaDefinitions are non-nil, and surface the fetch error instead

Example fix

// before
left, _ := getSchema(ctx, tabletA)
tmutils.DiffSchema("a", left, "b", right, er)
// after
left, err := getSchema(ctx, tabletA)
if err != nil {
    er.RecordError(fmt.Errorf("cannot fetch schema from %s: %v", "a", err))
    return
}
tmutils.DiffSchema("a", left, "b", right, er)
Defensive patterns

Strategy: type-guard

Validate before calling

if left == nil || right == nil {
    return vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, "cannot diff schema: one side is nil (left=%v right=%v)", leftName, rightName)
}

Type guard

func bothSchemasPresent(l, r *tabletmanagerdatapb.SchemaDefinition) bool {
    return l != nil && r != nil
}

Try / catch

left, err := getSchema(ctx, leftTablet)
if err != nil {
    return vterrors.Wrapf(err, "cannot fetch schema from %s", leftName)
}
er := concurrency.AllErrorRecorder{}
tmutils.DiffSchema(leftName, left, rightName, right, &er)
if er.HasErrors() {
    return er.Error()
}

Prevention

When it happens

Trigger: DiffSchema(leftName, nil, rightName, right, er) or vice versa — e.g. one vttablet's GetSchema RPC failed or returned an empty response before the diff.

Common situations: A tablet is down or unreachable so its schema fetch returned nil; a fresh tablet has not been initialized; the RPC error was swallowed upstream and nil was passed to DiffSchema.

Related errors


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