thanos-io/thanos · error

read series range

Error message

read series range

What it means

Loading series (Series/Lookup) fetches a byte range of the block index via r.block.readIndexRange, which may consult cache or do a ranged object-storage GET. This error wraps any failure fetching that byte range — cache miss failure, object missing, network error, or throttling — for the requested series ids range.

Solutions

  1. Retry the query; verify whether the error is transient (network/throttle) or persistent (missing object)
  2. Check that the block and its index still exist in the bucket and is not deleted mid-query
  3. Inspect and fix the series cache backend configuration, or clear corrupted cache entries
  4. Look at the wrapped error to distinguish permission issues (fix IAM) from network issues (fix connectivity)
Defensive patterns

Strategy: retry

Validate before calling

if end <= start || uint64(end) > indexFileSize {
	// invalid range: do not call readIndexRange
}

Type guard

func validRange(start, end uint64, size uint64) bool { return start < end && end <= size }

Try / catch

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

Prevention

When it happens

Trigger: r.block.readIndexRange(ctx, start, end-start, logger) fails during LoadSeries: object-storage ranged GET error, series cache (store.index-cache.series) fetch error, or context cancellation while fetching.

Common situations: Heavy series fan-out queries exceeding bucket request rate limits; index object deleted while being read; misconfigured or unavailable external series cache; network timeouts on large ranges.

Related errors


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

Appendix: source

Thrown at pkg/store/bucket.go:3399

		})
	}
	return g.Wait()
}

func (r *bucketIndexReader) loadSeries(ctx context.Context, ids []storage.SeriesRef, refetch bool, start, end uint64, bytesLimiter BytesLimiter, tenant string) error {
	begin := time.Now()
	stats := new(queryStats)
	defer func() {
		r.stats.merge(stats)
	}()

	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))
			}

View on GitHub (pinned to 35b8b99117)