thanos-io/thanos · error

fast forward range reader

Error message

fast forward range reader

What it means

Raised in bucketChunkReader.load while reading pre-fetched byte ranges from object storage: bufio.Reader.Discard fails when fast-forwarding the range reader to the next chunk's offset. The cause is an underlying read error on the object storage stream (connection reset, unexpected EOF), not a logic error in offsets.

Solutions

  1. Retry the query; transient object storage read errors often resolve
  2. Check object store connectivity and range-request support
  3. Reduce requested time range/chunk count if the streaming read keeps failing
Defensive patterns

Strategy: retry

When it happens

Trigger: Thrown at pkg/store/bucket.go:3767 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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

Appendix: source

Thrown at pkg/store/bucket.go:3767

		diff     uint32
		chunkLen int
		n        int
	)

	bufPooled, err := r.block.chunkPool.Get(r.block.estimatedMaxChunkSize)
	if err == nil {
		buf = *bufPooled
		defer r.block.chunkPool.Put(&buf)
	} else {
		buf = make([]byte, r.block.estimatedMaxChunkSize)
	}

	for i, pIdx := range pIdxs {
		// Fast forward range reader to the next chunk start in case of sparse (for our purposes) byte range.
		for readOffset < int(pIdx.offset) {
			written, err = bufReader.Discard(int(pIdx.offset) - int(readOffset))
			if err != nil {
				return errors.Wrap(err, "fast forward range reader")
			}
			readOffset += written
		}
		// Presume chunk length to be reasonably large for common use cases.
		// However, declaration for EstimatedMaxChunkSize warns us some chunks could be larger in some rare cases.
		// This is handled further down below.
		chunkLen = r.block.estimatedMaxChunkSize
		if i+1 < len(pIdxs) {
			if diff = pIdxs[i+1].offset - pIdx.offset; int(diff) < chunkLen {
				chunkLen = int(diff)
			}
		}
		cb := buf[:chunkLen]
		n, err = io.ReadFull(bufReader, cb)
		readOffset += n
		// Unexpected EOF for last chunk could be a valid case. Any other errors are definitely real.
		if err != nil && !errors.Is(err, io.ErrUnexpectedEOF) && i != len(pIdxs)-1 {
			return errors.Wrapf(err, "read range for seq %d offset %x", seq, pIdx.offset)

View on GitHub (pinned to 35b8b99117)