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
- Remove the duplicate rows, keeping one: DELETE FROM _vt.vdiff_table WHERE id=<dup id>;
- Or delete the whole vdiff and recreate it for a clean state: vdiff delete all + create.
- 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
- Add a unique key on (vdiff_id, table_name) expectation — never manually duplicate rows.
- Serialize create/resume actions per workflow.
- After tablet restore, dedupe _vt.vdiff_table before running vdiff.
- Prefer recreate-over-repair when state rows are corrupt.
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
- too many vdiffs found (%d) for UUID %s keyspace %s and workf
- no state found for vdiff table %s for vdiff_id %d on tablet
- invalid state found for vdiff table %s for vdiff_id %d on ta
- unable to create vdiff for UUID %s on tablet %s (%w)
- no vdiff found for id %d on tablet %v
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/14ba9872f63e2564.
Report an issue: GitHub.