JuliusBrussee/caveman · error

cachebench: observations exceed record limit %d

Error message

cachebench: observations exceed record limit %d

What it means

The JSONL observation reader caps how many records it will return: once limits.MaxRecords records have been parsed, the next non-empty line triggers this error instead of silently truncating. It fires mid-scan, so a partial slice is discarded (nil returned).

Source

Thrown at cacheengine/cachebench/observed.go:113

func ReadObservationJSONLWithLimits(reader io.Reader, limits ObservationReadLimits) ([]ObservationRecord, error) {
	if limits.MaxLineBytes <= 0 || limits.MaxLineBytes > 64<<20 || limits.MaxRecords <= 0 || limits.MaxRecords > 1_000_000 {
		return nil, errors.New("cachebench: invalid observation read limits")
	}
	scanner := bufio.NewScanner(reader)
	initial := 64 * 1024
	if limits.MaxLineBytes < initial {
		initial = limits.MaxLineBytes
	}
	scanner.Buffer(make([]byte, initial), limits.MaxLineBytes)
	seen := map[string]bool{}
	var records []ObservationRecord
	for line := 1; scanner.Scan(); line++ {
		raw := bytes.TrimSpace(scanner.Bytes())
		if len(raw) == 0 {
			continue
		}
		if len(records) >= limits.MaxRecords {
			return nil, fmt.Errorf("cachebench: observations exceed record limit %d", limits.MaxRecords)
		}
		if !validUniqueJSONObject(raw) {
			return nil, fmt.Errorf("cachebench: observation line %d: duplicate or invalid JSON", line)
		}
		decoder := json.NewDecoder(bytes.NewReader(raw))
		decoder.DisallowUnknownFields()
		var record ObservationRecord
		if err := decoder.Decode(&record); err != nil {
			return nil, fmt.Errorf("cachebench: observation line %d: %w", line, err)
		}
		var trailing any
		if err := decoder.Decode(&trailing); err != io.EOF {
			return nil, fmt.Errorf("cachebench: observation line %d: trailing JSON", line)
		}
		if record.Schema != ObservationSchema {
			return nil, fmt.Errorf("cachebench: observation line %d: schema %q", line, record.Schema)
		}
		if strings.TrimSpace(record.RequestID) == "" || seen[record.RequestID] {

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Raise limits.MaxRecords to at least the file's record count (count non-empty lines).
  2. Split the observation file and evaluate shards in separate runs.
  3. Subsample the replay so observations stay under the cap.

Example fix

// before
limits.MaxRecords = 50_000
// after
limits.MaxRecords = 500_000
Defensive patterns

Strategy: validation

Validate before calling

func countRecords(path string) (int, error) {
	f, err := os.Open(path)
	if err != nil {
		return 0, err
	}
	defer f.Close()
	n := 0
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		if len(bytes.TrimSpace(scanner.Bytes())) > 0 {
			n++
		}
	}
	return n, scanner.Err()
}
// if n > limits.MaxRecords { limits.MaxRecords = n }

Prevention

When it happens

Trigger: Loading an observations file whose non-empty line count exceeds ReaderLimits.MaxRecords — the (MaxRecords+1)-th populated line errors.

Common situations: Long replays producing more observations than the configured budget; raising replay length without raising MaxRecords; concatenating observation files.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/a469d185f857b964. Report an issue: GitHub.