{"record":{"id":"14ba9872f63e2564","repo":"vitessio/vitess","slug":"invalid-state-found-for-vdiff-table-s-multiple-r","errorCode":null,"errorMessage":"invalid state found for vdiff table %s (multiple records) for vdiff_id %d on tablet %v","messagePattern":"invalid state found for vdiff table (.+?) \\(multiple records\\) for vdiff_id (.+?) on tablet (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/vttablet/tabletmanager/vdiff/table_differ.go","lineNumber":547,"sourceCode":"\t// We need to continue were we left off when appropriate. This can be an\n\t// auto-retry on error, or a manual retry via the resume command.\n\t// Otherwise the existing state will be empty and we start from scratch.\n\tquery, err := sqlparser.ParseAndBind(sqlGetVDiffTable,\n\t\tsqltypes.Int64BindVariable(td.wd.ct.id),\n\t\tsqltypes.StringBindVariable(td.table.Name),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcs, err := dbClient.ExecuteFetch(query, -1)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(cs.Rows) == 0 {\n\t\treturn nil, fmt.Errorf(\"no state found for vdiff table %s for vdiff_id %d on tablet %v\",\n\t\t\ttd.table.Name, td.wd.ct.id, td.wd.ct.vde.thisTablet.Alias)\n\t} else if len(cs.Rows) > 1 {\n\t\treturn nil, fmt.Errorf(\"invalid state found for vdiff table %s (multiple records) for vdiff_id %d on tablet %v\",\n\t\t\ttd.table.Name, td.wd.ct.id, td.wd.ct.vde.thisTablet.Alias)\n\t}\n\tcurState := cs.Named().Row()\n\tmismatch := curState.AsBool(\"mismatch\", false)\n\tdr := &DiffReport{}\n\tif rpt := curState.AsBytes(\"report\", []byte(\"{}\")); json.Valid(rpt) {\n\t\tif err = json.Unmarshal(rpt, dr); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tdr.TableName = td.table.Name\n\n\t// Scope executor goroutines to this single diff attempt, rather\n\t// than surviving until the controller context is canceled.\n\texecCtx, cancelExec := context.WithCancel(ctx)\n\tdefer cancelExec()\n\n\tsourceExecutor := newPrimitiveExecutor(execCtx, td.sourcePrimitive, \"source\")","sourceCodeStart":529,"sourceCodeEnd":565,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vttablet/tabletmanager/vdiff/table_differ.go#L529-L565","documentation":"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.","triggerScenarios":"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.","commonSituations":"After manual schema surgery on _vt.vdiff_table; concurrent create/resume actions inserting state twice; restoring a tablet from a backup that duplicated rows.","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."],"exampleFix":"// before (mysql on tablet)\nSELECT id FROM _vt.vdiff_table WHERE vdiff_id=7 AND table_name='t1';\n-- returns 2 rows\n// after\nDELETE FROM _vt.vdiff_table WHERE id=<duplicate id>;","handlingStrategy":"validation","validationCode":"var n int\n_ := db.QueryRow(\"SELECT COUNT(*) FROM _vt.vdiff_table WHERE vdiff_id=? AND table_name=?\", id, table).Scan(&n)\nif n > 1 { dedupeVDiffTableRows(id, table) }","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"multiple records\") {\n  // clean duplicates keeping the newest row, then retry\n  dedupeVDiffTableRows(id, table)\n  retryDiff()\n}","preventionTips":["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."],"tags":["vdiff","duplicate-rows","state","mysql"],"backgroundTag":"vdiff-duplicate-state-rows","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}