vitessio/vitess · error

VReplication streams must have named workflows for migration

Error message

VReplication streams must have named workflows for migration: shard: %s:%s

What it means

Thrown by readTabletStreams (the non-legacy, ReadVReplicationWorkflows-based path) when a workflow returned from the tablet has an empty name. The stream migrator only supports migrating named VReplication workflows; unnamed streams cannot be tracked, stopped, or recreated on the target shards, so the operation aborts.

Source

Thrown at go/vt/vtctl/workflow/stream_migrator.go:393

func (sm *StreamMigrator) readTabletStreams(ctx context.Context, ti *topo.TabletInfo, ids []int32, states []binlogdatapb.VReplicationWorkflowState, excludeFrozen bool) ([]*VReplicationStream, error) {
	req := &tabletmanagerdatapb.ReadVReplicationWorkflowsRequest{
		ExcludeWorkflows: []string{sm.ts.ReverseWorkflowName()},
		IncludeIds:       ids,
		IncludeStates:    states,
		ExcludeFrozen:    excludeFrozen,
	}

	res, err := sm.ts.TabletManagerClient().ReadVReplicationWorkflows(ctx, ti.Tablet, req)
	if err != nil {
		return nil, err
	}

	tabletStreams := make([]*VReplicationStream, 0, len(res.Workflows))

	for _, workflow := range res.Workflows {
		switch workflow.Workflow {
		case "":
			return nil, fmt.Errorf("VReplication streams must have named workflows for migration: shard: %s:%s",
				ti.Keyspace, ti.Shard)
		case sm.ts.WorkflowName():
			return nil, fmt.Errorf("VReplication stream has the same workflow name as the resharding workflow: shard: %s:%s",
				ti.Keyspace, ti.Shard)
		}

		for _, stream := range workflow.Streams {
			isReference, err := sm.blsIsReference(stream.Bls)
			if err != nil {
				return nil, vterrors.Wrap(err, "blsIsReference")
			}

			if isReference {
				sm.ts.Logger().Infof("readTabletStreams: ignoring reference table %+v", stream.Bls)
				continue
			}

			pos, err := replication.DecodePosition(stream.Pos)

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Identify the unnamed stream: `select id, workflow from _vt.vreplication` on the source shard primary and find rows with empty workflow.
  2. Name the stream explicitly (`update _vt.vreplication set workflow='<descriptive-name>' where id=<id>`) and rerun the migration.
  3. If the stream is obsolete, remove it via vtctldclient Workflow Remove or a DELETE on _vt.vreplication.
  4. Upgrade tablets to a Vitess version that always populates workflow names, then retry.

Example fix

// before
mysql> update _vt.vreplication set id=7 where ...; -- row has workflow=''
// after
mysql> update _vt.vreplication set workflow='legacy_kd_copy' where id=7;
Defensive patterns

Strategy: validation

Validate before calling

rows, _ := qr.Rows // select id, workflow from _vt.vreplication
for _, r := range rows {
    if r["workflow"].ToString() == "" {
        return fmt.Errorf("stream %d has no workflow name; name or delete it before migrating", id)
    }
}

Type guard

func hasWorkflowName(workflow string) bool {
    return strings.TrimSpace(workflow) != ""
}

Try / catch

if err := migrateStreams(ctx, cfg); err != nil && strings.Contains(err.Error(), "must have named workflows") {
    // locate unnamed stream in _vt.vreplication, name or remove it, retry
}

Prevention

When it happens

Trigger: Calling stream migration (MigrateStreams / MoveTables stream transfer) against a source shard primary whose _vt.vreplication rows (as surfaced by ReadVReplicationWorkflows) contain a workflow with an empty Workflow string.

Common situations: Streams created by very old Vitess versions or manual INSERTs into _vt.vreplication without a workflow value; legacy one-off streams predating mandatory workflow names; mixed-version clusters where the tabletmanager returns unnamed rows.

Related errors


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