vitessio/vitess · error

unexpected event on table %s

Error message

unexpected event on table %s

What it means

vplayer received a RowEvent for a table that has no entry in vp.tablePlans — the plan map built from the workflow's filter rules when the stream started. Since there is no table plan, the row change cannot be transformed/applied to the target, so the event is rejected as unexpected.

Source

Thrown at go/vt/vttablet/tabletmanager/vreplication/vplayer.go:411

				"vreplication: malformed row change with no row images in event for table %s", tableName)
		case change.GetBefore() != nil && change.GetAfter() == nil:
			insertsOnly = false
		case change.GetBefore() == nil && change.GetAfter() != nil:
			deletesOnly = false
		default: // Update-shaped (both images).
			deletesOnly, insertsOnly = false, false
		}
	}
	return deletesOnly, insertsOnly, nil
}

func (vp *vplayer) applyRowEvent(ctx context.Context, rowEvent *binlogdatapb.RowEvent) error {
	if err := vp.updateFKCheck(ctx, rowEvent.Flags); err != nil {
		return err
	}
	tplan := vp.tablePlans[rowEvent.TableName]
	if tplan == nil {
		return fmt.Errorf("unexpected event on table %s", rowEvent.TableName)
	}
	applyFunc := func(sql string) (*sqltypes.Result, error) {
		start := time.Now()
		qr, err := vp.query(ctx, sql)
		vp.vr.stats.QueryCount.Add(vp.phase, 1)
		vp.vr.stats.QueryTimings.Record(vp.phase, start)
		return qr, err
	}

	if vp.batchMode && len(rowEvent.RowChanges) > 1 {
		deletesOnly, insertsOnly, err := bulkApplicableShapes(rowEvent.TableName, rowEvent.RowChanges)
		if err != nil {
			return err
		}
		// If we have multiple delete row events for a table with a single PK column
		// then we can perform a simple bulk DELETE using an IN clause.
		if deletesOnly && tplan.MultiDelete != nil {
			_, err := tplan.applyBulkDeleteChanges(rowEvent.RowChanges, applyFunc, vp.vr.dbClient.maxBatchSize)

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Confirm the workflow filter rules cover exactly the tables being streamed, and that the event table should really be included
  2. If the table is new on the source, restart/re-create the workflow so tablePlans are rebuilt including it
  3. Check for table name case or keyspace mismatches between source and filter rules
  4. If the event should be excluded, tighten the filter rules (no overly broad patterns) and restart the stream
Defensive patterns

Strategy: validation

Validate before calling

// Before starting/restarting the workflow, confirm every streamed table matches a filter rule
for _, tbl := range sourceTables {
    if !matchesAnyRule(workflowFilter, tbl) && !explicitlyExcluded(tbl) {
        // tighten or broaden rules so tablePlans cover exactly the intended set
    }
}

Try / catch

if err := applyEvent(ctx, ev); err != nil && strings.Contains(err.Error(), "unexpected event on table") {
    // restart workflow to rebuild tablePlans including/excluding that table
}

Prevention

When it happens

Trigger: A RowEvent arrives (after updateFKCheck succeeds) whose rowEvent.TableName is absent from vp.tablePlans — typically because the table was created/altered on the source after the stream began, or the filter/keyspace/table mismatch means the plan was never built for it.

Common situations: DDL on the source adding a new table mid-workflow that the filter unintentionally matches (e.g. broad /.* rule); keyspace or table name case-mismatch; source shard reconfigured so events from an unfiltered table leak in; stale stream state after filter changes without restarting the workflow.

Related errors


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