thanos-io/thanos · error

reading series length failed

Error message

reading series length failed

What it means

While decoding a fetched index byte range, each series entry begins with a uvarint length. binary.Uvarint returning n < 1 means the bytes at the expected offset do not even contain a valid length prefix, so the reader cannot parse the series record. This indicates the fetched range does not align with actual series data — i.e. corrupt, truncated, or stale index bytes.

Solutions

  1. Clear/invalidate the series cache for the affected block (or flush the whole index cache) and retry
  2. Verify the block's index integrity (thanos tools bucket verify); delete/re-upload the block if corrupt
  3. Ensure series refs come from the same block's postings — a ref from another block produces bogus offsets
  4. Check that block ULIDs are unique (rebuilt blocks must not reuse ULIDs) to avoid cache key collisions
Defensive patterns

Strategy: validation

Validate before calling

if uint64(id) < start || uint64(id)-start >= uint64(len(b)) {
	return errors.Errorf("series id %d outside fetched range [%d,%d)", id, start, len(b))
}

Type guard

func offsetInRange(b []byte, id, start uint64) bool { return id >= start && id-start < uint64(len(b)) }

Try / catch

l, n := binary.Uvarint(c)
if n < 1 {
	logger.Warn("invalid series record; invalidating cache entry", "id", id)
	seriesCache.Invalidate(blockMeta.ULID, id)
	return retryFetchSeries(ctx, ids) // refetch without cache
}

Prevention

When it happens

Trigger: DecodeSeries parses b[uint64(id)-start:] and binary.Uvarint fails (n < 1): id offset outside the fetched range, truncated read from object storage/cache, or index bytes corrupted in cache.

Common situations: Corrupted entries in the external series cache (e.g. Redis truncation); a series ref (id) that does not belong to the fetched block/range; partially uploaded or corrupted index file in the bucket; cache entry written by a different block with the same ULID (rebuilt block reusing ULID).

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07). Data as JSON: /api/errors/ee36e474d8ab681c. Report an issue: GitHub.

Appendix: source

Thrown at pkg/store/bucket.go:3411

	if err := bytesLimiter.ReserveWithType(uint64(end-start), SeriesFetched); err != nil {
		return httpgrpc.Errorf(int(codes.ResourceExhausted), "exceeded bytes limit while fetching series: %s", err)
	}

	b, err := r.block.readIndexRange(ctx, int64(start), int64(end-start), r.logger)
	if err != nil {
		return errors.Wrap(err, "read series range")
	}

	stats.seriesFetchCount++
	stats.SeriesFetchDurationSum += time.Since(begin)
	stats.add(SeriesFetched, len(ids), int(end-start))

	for i, id := range ids {
		c := b[uint64(id)-start:]

		l, n := binary.Uvarint(c)
		if n < 1 {
			return errors.New("reading series length failed")
		}
		if len(c) < n+int(l) {
			if i == 0 && refetch {
				return errors.Errorf("invalid remaining size, even after refetch, remaining: %d, expected %d", len(c), n+int(l))
			}

			// Inefficient, but should be rare.
			r.block.metrics.seriesRefetches.WithLabelValues(tenant).Inc()
			level.Warn(r.logger).Log("msg", "series size exceeded expected size; refetching", "id", id, "series length", n+int(l), "maxSeriesSize", r.block.estimatedMaxSeriesSize)

			// Fetch plus to get the size of next one if exists.
			return r.loadSeries(ctx, ids[i:], true, uint64(id), uint64(id)+uint64(n+int(l)+1), bytesLimiter, tenant)
		}
		c = c[n : n+int(l)]
		r.loadedSeriesMtx.Lock()
		r.loadedSeries[id] = c
		r.loadedSeriesMtx.Unlock()

View on GitHub (pinned to 35b8b99117)