vitessio/vitess · error

BUG: cannot add record because it does not start at the begi

Error message

BUG: cannot add record because it does not start at the beginning of the interval. record: %v

What it means

intervalHistory.add requires each record's time to fall exactly on an interval boundary (time truncated by the interval must equal itself). Records that start mid-interval would corrupt the fixed-width bucketing, so add panics with this BUG message. It is a programmer-contract violation within the throttler.

Source

Thrown at go/vt/throttler/interval_history.go:54

	nextIntervalStart time.Time
}

func newIntervalHistory(capacity int64, interval time.Duration) *intervalHistory {
	return &intervalHistory{
		records:  make([]record, 0, capacity),
		interval: interval,
	}
}

// add
// It is up to the programmer to ensure that two add() calls do not cover the
// same interval.
func (h *intervalHistory) add(record record) {
	if record.time.Before(h.nextIntervalStart) {
		panic(fmt.Sprintf("BUG: cannot add record because it is already covered by a previous entry. record: %v next expected interval start: %v", record, h.nextIntervalStart))
	}
	if !record.time.Truncate(h.interval).Equal(record.time) {
		panic(fmt.Sprintf("BUG: cannot add record because it does not start at the beginning of the interval. record: %v", record))
	}
	// TODO(mberlin): Bound the list.
	h.records = append(h.records, record)
	h.nextIntervalStart = record.time.Add(h.interval)
}

// average returns the average value across all observations which span
// the range [from, to).
// Partially included observations are accounted by their included fraction.
// Missing observations are assumed with the value zero.
func (h *intervalHistory) average(from, to time.Time) float64 {
	// Search only entries whose time of observation is in [start, end).
	// Example: [from, to) = [1.5s, 2.5s) => [start, end) = [1s, 2s)
	start := from.Truncate(h.interval)
	end := to.Truncate(h.interval)

	sum := 0.0
	count := 0.0

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Align the record time before calling add: record.time = rawTime.Truncate(h.interval).
  2. In tests, build timestamps as base.Add(n * h.interval) from a truncated base so they land on boundaries.
  3. Verify the interval passed to newIntervalHistory matches the granularity of the timestamps you feed it.

Example fix

// before
h.add(record{time: time.Now(), lag: lag}) // mid-interval time panics
// after
h.add(record{time: time.Now().Truncate(h.interval), lag: lag})
Defensive patterns

Strategy: validation

Validate before calling

func isAligned(t time.Time, interval time.Duration) bool {
  return t.Truncate(interval).Equal(t)
}
// only call h.add when isAligned(t, h.interval)

Prevention

When it happens

Trigger: Calling add with a record whose time is not aligned to the interval grid (e.g. interval = 1s but record.time = 12:00:00.250).

Common situations: Test code constructing record times without truncating to the interval; a producer passing raw measurement timestamps instead of interval-start-aligned timestamps; changes to the interval duration that silently break previously-aligned timestamps.

Related errors


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