vitessio/vitess · error

row stream send error

Error message

row stream send error: %w

What it means

The initial VStreamRowsResponse (fields, pk fields, gtid) could not be delivered because the underlying send function failed. safeSend returned an error that was not a context cancellation (those produce 'row stream ended'), so it is wrapped as 'row stream send error'.

Solutions

  1. Check tablet logs for the underlying send error cause (broken pipe, connection reset) and inspect network/connectivity between the tablet and the VStream consumer.
  2. Retry the VStream/workflow; copy-phase resumes from checkpoint.
  3. If it recurs, verify the receiver (vtgate/vtctld) is healthy and that no proxy/load balancer is killing long-lived gRPC streams (idle timeouts).
Defensive patterns

Strategy: retry

Try / catch

err := rowStream(ctx, ...)
if err != nil && strings.Contains(err.Error(), "row stream send error") {
    // underlying send failed (network/receiver gone): backoff and retry the stream
    time.Sleep(backoff)
    retryStream(ctx)
}

Prevention

When it happens

Trigger: streamQuery's first safeSend of fields/pkfields/gtid fails — the send callback passed to Stream errors, typically because the receiving gRPC stream to the caller is broken.

Common situations: Network interruption between vtgate/vtctld and the tablet during copy phase start, receiver gone (workflow cancelled concurrently), gRPC stream reset.

Related errors


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

Appendix: source

Thrown at go/vt/vttablet/tabletserver/vstreamer/rowstreamer.go:405

			Name:    rs.plan.Table.Fields[pk].Name,
			Type:    rs.plan.Table.Fields[pk].Type,
			Charset: rs.plan.Table.Fields[pk].Charset,
			Flags:   rs.plan.Table.Fields[pk].Flags,
		}
	}

	charsets := make([]collations.ID, len(rs.plan.Table.Fields))
	for i, fld := range rs.plan.Table.Fields {
		charsets[i] = collations.ID(fld.Charset)
	}

	err = safeSend(rs.ctx, &binlogdatapb.VStreamRowsResponse{
		Fields:   rs.plan.fields(),
		Pkfields: pkfields,
		Gtid:     gtid,
	})
	if err != nil {
		return fmt.Errorf("row stream send error: %w", err)
	}

	// streamQuery sends heartbeats as long as it operates
	heartbeatCtx, stopHeartbeats := context.WithCancel(rs.ctx)
	defer stopHeartbeats()
	heartbeatInterval := rowStreamertHeartbeatInterval
	go func() {
		heartbeatTicker := time.NewTicker(heartbeatInterval)
		defer heartbeatTicker.Stop()

		for {
			select {
			case <-heartbeatCtx.Done():
				return
			case <-heartbeatTicker.C:
				_ = safeSend(heartbeatCtx, &binlogdatapb.VStreamRowsResponse{Heartbeat: true})
			}
		}

View on GitHub (pinned to 01a25a7d17)