vitessio/vitess · error

marshal primary health state: %w

Error message

marshal primary health state: %w

What it means

writePrimaryHealthState marshals the in-memory PrimaryHealthState to prototext before persisting it with REPLACE INTO primary_health. prototext.Marshal failure is unexpected for valid states but is wrapped and propagated so recordPrimaryHealthCheckAt (and its callers) surface it instead of silently skipping persistence.

Source

Thrown at go/vt/vtorc/inst/primary_health.go:277

		return nil, err
	}
	return state, nil
}

// writePrimaryHealthState persists the current health window for a tablet alias.
// It is a no-op for empty aliases or nil state, and it deletes the row if the
// state is already evictable.
func writePrimaryHealthState(tabletAlias string, state *primaryHealthState) error {
	if tabletAlias == "" || state == nil {
		return nil
	}
	if shouldEvictPrimaryHealthWindow(state) {
		return deletePrimaryHealthState(tabletAlias)
	}
	pb := toProtoPrimaryHealthState(state)
	data, err := prototext.Marshal(pb)
	if err != nil {
		return fmt.Errorf("marshal primary health state: %w", err)
	}
	query := `REPLACE INTO primary_health (alias, health_state, last_updated) VALUES (?, ?, DATETIME('now'))`
	_, err = db.ExecVTOrc(query, tabletAlias, string(data))
	return err
}

// deletePrimaryHealthState removes the persisted health window for a tablet alias.
// It is safe to call repeatedly or with an empty alias.
func deletePrimaryHealthState(tabletAlias string) error {
	if tabletAlias == "" {
		return nil
	}
	_, err := db.ExecVTOrc("delete from primary_health where alias = ?", tabletAlias)
	return err
}

// toProtoPrimaryHealthState converts the in-memory health window to its protobuf form.
// Timestamps are stored as Unix nanoseconds so ordering remains stable across reloads.

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Inspect the wrapped cause (%w) — prototext.Marshal errors usually indicate a programming/proto-definition bug
  2. Verify the vtorc binary and the vitess proto definitions come from the same version (no partial builds)
  3. Check what state recordPrimaryHealthCheckAt produced; log it before marshal to identify the bad field
  4. As a workaround, delete the health row so a fresh state window is built

Example fix

// before: error surfaces only at runtime
pb := toProtoPrimaryHealthState(state)
data, err := prototext.Marshal(pb)
// after: guard against invalid state before marshal
if state == nil {
    return fmt.Errorf("marshal primary health state: nil state for %s", tabletAlias)
}
Defensive patterns

Strategy: try-catch

Try / catch

err := recordPrimaryHealthCheckAt(ctx, alias, now)
if err != nil && strings.Contains(err.Error(), "marshal primary health state") {
    // indicates a proto/code version mismatch; report and rebuild state
    log.Error("primary health marshal failed", slog.String("alias", alias), slog.Any("error", err))
    _ = deletePrimaryHealthState(alias)
}

Prevention

When it happens

Trigger: writePrimaryHealthState (called from recordPrimaryHealthCheckAt) marshaling a state that fails prototext encoding — practically only when the state/proto contains something invalid or a code change introduces an unmarshalable field.

Common situations: Custom builds or version-skew where toProtoPrimaryHealthState produces a message the linked proto cannot encode; rarely hit in stock Vitess.

Related errors


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