vitessio/vitess · error

invalid state found for vdiff table %s (multiple records) fo

Error message

invalid state found for vdiff table %s (multiple records) for vdiff_id %d on tablet %v

What it means

The same state lookup expects exactly one _vt.vdiff_table row per (vdiff_id, table). More than one row means duplicate state records, which would make resumed diff state ambiguous, so diff returns this error instead of picking one.

Source

Thrown at go/vt/vttablet/tabletmanager/vdiff/table_differ.go:547

	// We need to continue were we left off when appropriate. This can be an
	// auto-retry on error, or a manual retry via the resume command.
	// Otherwise the existing state will be empty and we start from scratch.
	query, err := sqlparser.ParseAndBind(sqlGetVDiffTable,
		sqltypes.Int64BindVariable(td.wd.ct.id),
		sqltypes.StringBindVariable(td.table.Name),
	)
	if err != nil {
		return nil, err
	}
	cs, err := dbClient.ExecuteFetch(query, -1)
	if err != nil {
		return nil, err
	}
	if len(cs.Rows) == 0 {
		return nil, fmt.Errorf("no state found for vdiff table %s for vdiff_id %d on tablet %v",
			td.table.Name, td.wd.ct.id, td.wd.ct.vde.thisTablet.Alias)
	} else if len(cs.Rows) > 1 {
		return nil, fmt.Errorf("invalid state found for vdiff table %s (multiple records) for vdiff_id %d on tablet %v",
			td.table.Name, td.wd.ct.id, td.wd.ct.vde.thisTablet.Alias)
	}
	curState := cs.Named().Row()
	mismatch := curState.AsBool("mismatch", false)
	dr := &DiffReport{}
	if rpt := curState.AsBytes("report", []byte("{}")); json.Valid(rpt) {
		if err = json.Unmarshal(rpt, dr); err != nil {
			return nil, err
		}
	}
	dr.TableName = td.table.Name

	// Scope executor goroutines to this single diff attempt, rather
	// than surviving until the controller context is canceled.
	execCtx, cancelExec := context.WithCancel(ctx)
	defer cancelExec()

	sourceExecutor := newPrimitiveExecutor(execCtx, td.sourcePrimitive, "source")

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Remove the duplicate rows, keeping one: DELETE FROM _vt.vdiff_table WHERE id=<dup id>;
  2. Or delete the whole vdiff and recreate it for a clean state: vdiff delete all + create.
  3. Add a guard against running concurrent vdiff actions for the same workflow to avoid duplicate inserts.

Example fix

// before (mysql on tablet)
SELECT id FROM _vt.vdiff_table WHERE vdiff_id=7 AND table_name='t1';
-- returns 2 rows
// after
DELETE FROM _vt.vdiff_table WHERE id=<duplicate id>;
Defensive patterns

Strategy: validation

Validate before calling

var n int
_ := db.QueryRow("SELECT COUNT(*) FROM _vt.vdiff_table WHERE vdiff_id=? AND table_name=?", id, table).Scan(&n)
if n > 1 { dedupeVDiffTableRows(id, table) }

Try / catch

if err != nil && strings.Contains(err.Error(), "multiple records") {
  // clean duplicates keeping the newest row, then retry
  dedupeVDiffTableRows(id, table)
  retryDiff()
}

Prevention

When it happens

Trigger: diff → state query returns len(cs.Rows)>1 for the table — duplicate _vt.vdiff_table rows sharing vdiff_id and table name, usually from manual inserts or an interrupted/idempotency bug during state creation.

Common situations: After manual schema surgery on _vt.vdiff_table; concurrent create/resume actions inserting state twice; restoring a tablet from a backup that duplicated rows.

Related errors


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