vitessio/vitess · error

cannot update vreplication table, affected %v rows

Error message

cannot update vreplication table, affected %v rows

What it means

Thrown by writeRecoveryPosition when the UPDATE of the _vt.vreplication row executes successfully but affects 0 rows instead of 1 — meaning no row exists for this player's uid. The player then rolls back the enclosing transaction (via the caller), so neither data nor position is committed.

Source

Thrown at go/vt/binlog/binlogplayer/binlog_player.go:531

//     with it, and set ReplicationLagSeconds to now() - transaction_timestamp
//   - otherwise (the statements are probably filtered out), we leave
//     transaction_timestamp alone (keeping the old value), and we don't
//     change ReplicationLagSeconds
func (blp *BinlogPlayer) writeRecoveryPosition(tx *binlogdatapb.BinlogTransaction) error {
	position, err := DecodePosition(tx.EventToken.Position)
	if err != nil {
		return err
	}

	now := time.Now().Unix()
	updateRecovery := GenerateUpdatePos(blp.uid, position, now, tx.EventToken.Timestamp, blp.blplStats.CopyRowCount.Get(), false)

	qr, err := blp.exec(updateRecovery)
	if err != nil {
		return fmt.Errorf("error %v in writing recovery info %v", err, updateRecovery)
	}
	if qr.RowsAffected != 1 {
		return fmt.Errorf("cannot update vreplication table, affected %v rows", qr.RowsAffected)
	}

	// Update position after successful write.
	blp.position = position
	blp.blplStats.SetLastPosition(blp.position)
	if tx.EventToken.Timestamp != 0 {
		blp.blplStats.ReplicationLagSeconds.Store(now - tx.EventToken.Timestamp)
	}
	return nil
}

func (blp *BinlogPlayer) setVReplicationState(state binlogdatapb.VReplicationWorkflowState, message string) error {
	if message != "" {
		blp.blplStats.History.Add(&StatsHistoryRecord{
			Time:    time.Now(),
			Message: message,
		})
	}

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Check if the vreplication row still exists: `SELECT id, state FROM _vt.vreplication WHERE id=<uid>`; if deleted intentionally, stop the player — the workflow is gone.
  2. If deleted accidentally, recreate the workflow/stream to insert a new row and resync.
  3. Avoid deleting vreplication rows while streams are active; cancel the workflow first and wait for state=Stopped.
  4. Investigate concurrent automation (shard pruning scripts) racing with active streams.

Example fix

// before: deleting row while stream is live
//   DELETE FROM _vt.vreplication WHERE id=1; -- row gone, update hits 0 rows
// after: stop the stream first, then remove
//   vtctldclient VReplicationExec <keyspace> "update _vt.vreplication set state='Stopped' where id=1"
//   # then delete after the player exits
Defensive patterns

Strategy: validation

Validate before calling

// before each stream start (and periodically), confirm the row exists
var n int
if err := targetDB.QueryRow(
    "SELECT COUNT(*) FROM _vt.vreplication WHERE id = ?", uid,
).Scan(&n); err != nil || n != 1 {
    return fmt.Errorf("vreplication row id=%d missing; workflow was removed or never created", uid)
}

Try / catch

err := binlogplayer.ApplyBinlogEvents(ctx, blp)
if err != nil && strings.Contains(err.Error(), "cannot update vreplication table") {
    // row deleted concurrently — do NOT blindly restart; check workflow intent
    log.Error("_vt.vreplication row vanished mid-stream; workflow was likely deleted")
    return err
}

Prevention

When it happens

Trigger: The _vt.vreplication row with id=blp.uid was deleted concurrently (stream cancelled externally, workflow removed) while the player was still applying events, or the row was never inserted for this uid.

Common situations: Operator ran `vtctldclient Workflow ... delete` or VReplicationExec delete while the stream was mid-transaction; target keyspace re-sharded/pruned old vreplication rows; race between workflow removal and tablet shutdown.

Related errors


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