vitessio/vitess · error

controller could not be initialized for stream %+v on tablet

Error message

controller could not be initialized for stream %+v on tablet %v

What it means

addController wraps a failure from newController (which builds a vdiff controller from a _vt.vdiff row) with this message. newController itself returns the underlying cause; this wrapper adds the stream row and tablet alias so operators know which vdiff and tablet failed during create/resume/retry.

Source

Thrown at go/vt/vttablet/tabletmanager/vdiff/engine.go:217

		}
		if err := vde.openLocked(ctx); err == nil {
			log.Info("VDiff engine: opened successfully")
			// Don't invoke cancelRetry because openLocked
			// will hold on to this context for later cancellation.
			vde.cancelRetry = nil
			vde.mu.Unlock()
			return
		}
		vde.mu.Unlock()
	}
}

// addController creates a new controller using the given vdiff record and adds it to the engine.
// You must already have the main engine mutex (mu) locked before calling this.
func (vde *Engine) addController(row sqltypes.RowNamedValues, options *tabletmanagerdata.VDiffOptions) error {
	ct, err := newController(row, vde.dbClientFactoryDba, vde.ts, vde, options)
	if err != nil {
		return fmt.Errorf("controller could not be initialized for stream %+v on tablet %v",
			row, vde.thisTablet.Alias)
	}
	vde.controllers[ct.id] = ct
	globalStats.mu.Lock()
	defer globalStats.mu.Unlock()
	globalStats.controllers[ct.id] = ct

	controllerCtx, cancel := context.WithCancel(vde.ctx)
	ct.cancel = cancel
	go ct.run(controllerCtx)
	return nil
}

func (vde *Engine) initControllers(qr *sqltypes.Result) error {
	if qr == nil || len(qr.Rows) == 0 {
		return nil
	}
	for _, row := range qr.Named().Rows {

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Look at the wrapped inner error from newController in the same log chain — it names the real cause.
  2. Validate the offending _vt.vdiff row (source, target, options columns) and delete/recreate the vdiff.
  3. Verify topo connectivity and that the source/target tablets exist: vtctldclient GetTablets.
  4. If resume keeps failing, delete the vdiff record and start a fresh VDiff Create.

Example fix

// before (resume of corrupt row)
vtctldclient vdiff --keyspace ks --workflow wf resume
// -> controller could not be initialized ...
// after
vtctldclient vdiff --keyspace ks --workflow wf delete all
vtctldclient vdiff --keyspace ks --workflow wf create
Defensive patterns

Strategy: try-catch

Validate before calling

// before resume/create, check topo and row sanity
err := topoCheck(keyspace) // tablets exist
row, _ := db.Query("SELECT options FROM _vt.vdiff WHERE vdiff_uuid=?", uuid)
json.Valid(row["options"]) // options column must be valid JSON

Try / catch

err := vde.PerformVDiffAction(ctx, req)
if err != nil && strings.Contains(err.Error(), "controller could not be initialized") {
  log.Error(err.Error()) // inspect wrapped cause
  // fall back: delete all + recreate vdiff
}

Prevention

When it happens

Trigger: handleCreateResumeAction or initControllers/retryVDiffs calling addController where the vdiff row has invalid/missing fields (bad JSON in options, invalid position, malformed source/target), or required tablet/topology lookups inside newController fail.

Common situations: Corrupt _vt.vdiff row after a manual edit; keyspace/tablet missing from topo during resume after a topology change; a vdiff created against a source keyspace that no longer exists; incompatible options JSON written by an older version.

Related errors


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