vitessio/vitess · error

cannot update vreplication table, affected

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.

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.

Appendix: 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)