thanos-io/thanos · error

no more data left in subrange at position

Error message

no more data left in subrange at position %d, subrange length %d, reading position %d

What it means

Raised by subrangesReader.Read when the current subrange is shorter than subrangeSize and the reader is asked to read past the data actually stored in that subrange. It indicates an internal inconsistency: the reader believed more bytes were available (c.remaining) than the subrange actually holds. The comment in the source says this 'can only happen if subrange's length is not subrangeSize'.

Solutions

  1. Check the cache backend's maximum value size; truncated entries are the usual culprit
  2. Flush/invalidate the affected cache entries and retry the read
  3. Ensure all Thanos components use the same subrangeSize configuration
  4. Confirm the reader is single-use and not advanced concurrently by multiple goroutines
  5. Bump to a newer Thanos version where subrange length accounting was fixed

Example fix

// before
cacheConfig:
  type: MEMCACHED
  memcached:
    max_item_size: 1MiB   # entries silently truncated
// after
cacheConfig:
  type: MEMCACHED
  memcached:
    max_item_size: 16MiB  # >= max subrange/object chunk size
Defensive patterns

Strategy: validation

Validate before calling

if cacheBackend == "MEMCACHED" && subrangeOrChunkSize > maxItemSize { return errors.New("subrange exceeds cache max item size; entries will truncate") }

Try / catch

if _, err := reader.Read(buf); err != nil {
    if strings.Contains(err.Error(), "no more data left in subrange") {
        // invalidate the cached entry and re-fetch from object store
    }
}

Prevention

When it happens

Trigger: Calling Read when len(currentSubrange) - offsetInSubrange <= 0, i.e. the stored subrange is truncated/shorter than subrangeSize while remaining bytes were still promised to the caller.

Common situations: Corrupted or partially written cache entries, cache backends that truncate large values (memcached/redis size limits), or reading an object whose cached chunks were stored by a different Thanos version with different subrange sizing.

Related errors


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

Appendix: source

Thrown at pkg/store/cache/caching_bucket.go:543

	}
}

func (c *subrangesReader) Read(p []byte) (n int, err error) {
	if c.remaining <= 0 {
		return 0, io.EOF
	}

	currentSubrangeOffset := (c.readOffset / c.subrangeSize) * c.subrangeSize
	currentSubrange, err := c.subrangeAt(currentSubrangeOffset)
	if err != nil {
		return 0, errors.Wrapf(err, "read position: %d", c.readOffset)
	}

	offsetInSubrange := int(c.readOffset - currentSubrangeOffset)
	toCopy := len(currentSubrange) - offsetInSubrange
	if toCopy <= 0 {
		// This can only happen if subrange's length is not subrangeSize, and reader is told to read more data.
		return 0, errors.Errorf("no more data left in subrange at position %d, subrange length %d, reading position %d", currentSubrangeOffset, len(currentSubrange), c.readOffset)
	}

	if len(p) < toCopy {
		toCopy = len(p)
	}
	if c.remaining < int64(toCopy) {
		toCopy = int(c.remaining) // Conversion is safe, c.remaining is small enough.
	}

	copy(p, currentSubrange[offsetInSubrange:offsetInSubrange+toCopy])
	c.readOffset += int64(toCopy)
	c.remaining -= int64(toCopy)

	return toCopy, nil
}

func (c *subrangesReader) subrangeAt(offset int64) ([]byte, error) {
	b := c.subranges[c.offsetsKeys[offset]]

View on GitHub (pinned to 35b8b99117)