vitessio/vitess · error

cannot update progress with a nil diff report

Error message

cannot update progress with a nil diff report

What it means

tableDiffer.updateTableProgress is invoked with dr (*DiffReport) = nil while trying to persist per-table vdiff progress. Progress updates require a valid diff report to marshal to JSON and write to the _vt.vreplication message/progress state, so a nil report is rejected explicitly instead of panicking later in json.Marshal.

Source

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

		// If the collation is nil or unknown, use binary collation to compare as bytes.
		collationID = col.collation
		if collationID == collations.Unknown {
			collationID = collations.CollationBinaryID
		}
		c, err = evalengine.NullsafeCompare(sourceRow[compareIndex], targetRow[compareIndex], td.wd.collationEnv, collationID, nil)
		if err != nil {
			return 0, err
		}
		if c != 0 {
			return c, nil
		}
	}
	return 0, nil
}

func (td *tableDiffer) updateTableProgress(dbClient binlogplayer.DBClient, dr *DiffReport, lastRow []sqltypes.Value) error {
	if dr == nil {
		return errors.New("cannot update progress with a nil diff report")
	}

	var err error
	var query string
	rpt, err := json.Marshal(dr)
	if err != nil {
		return err
	}

	if lastRow == nil {
		query, err = sqlparser.ParseAndBind(sqlUpdateTableNoProgress,
			sqltypes.Int64BindVariable(dr.ProcessedRows),
			sqltypes.StringBindVariable(string(rpt)),
			sqltypes.Int64BindVariable(td.wd.ct.id),
			sqltypes.StringBindVariable(td.table.Name),
		)
		if err != nil {
			return err

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Ensure the diff pass always produces a non-nil DiffReport before calling updateTableProgress
  2. Guard the caller: skip the progress update when dr == nil instead of calling it
  3. Check for early-return paths in diff() that leave the report nil (e.g. zero rows compared) and set a default empty report

Example fix

// before
td.updateTableProgress(dbClient, dr, lastRow)
// after
if dr != nil {
    td.updateTableProgress(dbClient, dr, lastRow)
}
Defensive patterns

Strategy: type-guard

Type guard

func hasDiffReport(dr *DiffReport) bool { return dr != nil }
if !hasDiffReport(dr) { skipProgressUpdate() }

Try / catch

if err := td.updateTableProgress(dbClient, dr, lastRow); err != nil {
    log.Warn("progress update failed", slog.Any("error", err))
}

Prevention

When it happens

Trigger: The differ's diff() loop (or tests) calls updateTableProgress with a nil DiffReport pointer, typically when no report was produced for the table or a nil is passed from an anonymous helper.

Common situations: A vdiff run where the table produced no comparison result before a progress update was attempted; programming changes in custom/test code (TestUpdateTableProgressDropsQueryEcho) calling updateTableProgress directly.

Related errors


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