vitessio/vitess · critical

can't parse FORMAT_DESCRIPTION_EVENT: %v, event data: %#v

Error message

can't parse FORMAT_DESCRIPTION_EVENT: %v, event data: %#v

What it means

A FORMAT_DESCRIPTION_EVENT was received but ev.Format() failed to decode it. Since the format description event defines how all subsequent events are parsed, vstreamer aborts parsing with the decode error and raw event data attached.

Source

Thrown at go/vt/vttablet/tabletserver/vstreamer/vstreamer.go:533

// parseEvent parses an event from the binlog and converts it to a list of VEvents.
// The bufferAndTransmit function must be passed if the event is a TransactionPayloadEvent
// as for larger payloads (> ZstdInMemoryDecompressorMaxSize) the internal events need
// to be streamed directly here in order to avoid holding the entire payload's contents,
// which can be 10s or even 100s of GiBs, all in memory.
func (vs *vstreamer) parseEvent(ev mysql.BinlogEvent, bufferAndTransmit func(vevent *binlogdatapb.VEvent) error) ([]*binlogdatapb.VEvent, error) {
	if !ev.IsValid() {
		return nil, fmt.Errorf("can't parse binlog event: invalid data: %#v", ev)
	}

	// We need to keep checking for FORMAT_DESCRIPTION_EVENT even after we've
	// seen one, because another one might come along (e.g. on log rotate due to
	// binlog settings change) that changes the format.
	if ev.IsFormatDescription() {
		var err error
		vs.format, err = ev.Format()
		if err != nil {
			return nil, fmt.Errorf("can't parse FORMAT_DESCRIPTION_EVENT: %v, event data: %#v", err, ev)
		}
		vs.eventGTID = nil
		return nil, nil
	}

	// We can't parse anything until we get a FORMAT_DESCRIPTION_EVENT that
	// tells us the size of the event header.
	if vs.format.IsZero() {
		// The only thing that should come before the FORMAT_DESCRIPTION_EVENT
		// is a fake ROTATE_EVENT, which the primary sends to tell us the name
		// of the current log file.
		if ev.IsRotate() {
			return nil, nil
		}
		return nil, fmt.Errorf("got a real event before FORMAT_DESCRIPTION_EVENT: %#v", ev)
	}

	// Strip the checksum, if any. We don't actually verify the checksum, so discard it.

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Verify the source MySQL version is supported by this vitess release (check mysqlctl/flavor support) and the binlog format settings (binlog_format=ROW).
  2. Check binlog file integrity on mysqld; if the FDE is corrupt, flush/purge logs (FLUSH LOGS / PURGE BINARY LOGS) and restart the stream from a valid GTID position.
  3. If binlog encryption is enabled, either disable it or ensure vitess supports the encryption scheme for that MySQL flavor.
  4. If the version should be supported, file a bug including the error text (it embeds the raw event data) and MySQL version.
Defensive patterns

Strategy: fallback

Validate before calling

// verify flavor/version support before streaming
ver := mysqlctl.ParseVersionString(serverVersion)
if !supportedBinlogVersion(ver) {
    return fmt.Errorf("MySQL %s binlog format not supported by this vitess release", serverVersion)
}

Try / catch

err := vs.streamLog(ctx)
if err != nil && strings.Contains(err.Error(), "can't parse FORMAT_DESCRIPTION_EVENT") {
    // fall back: flush logs and restart from a fresh binlog file / known-good GTID
    flushLogsAndRestartFromGTID(ctx)
}

Prevention

When it happens

Trigger: ev.IsFormatDescription() is true but Format() returns an error — unsupported binlog version/format descriptor layout, truncated FDE payload, or corruption in the FDE.

Common situations: Streaming binlogs from a MySQL version/major release whose binlog format vitess doesn't support; corrupted FDE after crash; binlog encrypted with a scheme vitess can't read; restore from incompatible backup.

Related errors


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