thanos-io/thanos · error
read position
Error message
read position: %d
What it means
This error wraps any failure from subrangeAt while reading cached object data. It reports the reader's current logical read offset (c.readOffset) so the developer can tell which position in the cached object could not be resolved to a subrange. It is thrown by subrangesReader.Read in Thanos's caching bucket layer when the internal subrange index is inconsistent.
Solutions
- Check that the reader is not shared between goroutines without synchronization
- Verify the cache backend is healthy and entries were not evicted/truncated mid-read
- Log c.readOffset, c.subrangeSize and available subrange keys to diagnose the mismatch
- Update Thanos to the latest patch release; known subrange bugs have been fixed
- As a workaround, disable the caching bucket for this reader path (use the plain bucket)
Example fix
// before
n, err := r.Read(buf) // panics/errors deep in subrangeAt
// after
if r, ok := r.(*storecache.SubrangesReader); ok {
// use a fresh reader per request and limit reads to advertised size
}
n, err := io.ReadFull(freshReader, buf[:min(len(buf), size)]) Defensive patterns
Strategy: try-catch
Validate before calling
if reader == nil || objectSize <= 0 { return errors.New("invalid reader or size") } Type guard
func usableReader(r io.Reader, size int64) bool { return r != nil && size > 0 } Try / catch
n, err := reader.Read(buf)
if err != nil {
var wrapped interface{ Cause() error }
if errors.As(err, &cause) && strings.Contains(err.Error(), "read position:") {
// fall back to a fresh GetRange for the failed offset
}
return err
} Prevention
- Use one reader per goroutine; never share subrangesReader concurrently
- Keep Thanos components on the same version
- Monitor cache eviction metrics and backend truncation
- Bound reads to the advertised object size
When it happens
Trigger: Calling Read on a reader obtained from a caching bucket when c.subranges has no entry for the subrange-aligned offset derived from c.readOffset (c.offsetsKeys lacks the computed key), e.g. after internal state corruption or a partially populated subrange map.
Common situations: Seen when debugging Thanos caching-bucket issues: cache entries evicted or truncated inconsistently, concurrent use of a single subrangesReader across goroutines, or version incompatibilities in cached data layout.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- no more data left in subrange at position
- subrange for offset not found
- failed to create inmemory cache
- failed to parse template
- failed to execute template
AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07).
Data as JSON: /api/errors/18d276f44bc4b91f.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/store/cache/caching_bucket.go:536
return &subrangesReader{
subrangeSize: subrangeSize,
offsetsKeys: offsetsKeys,
subranges: subranges,
readOffset: readOffset,
remaining: remaining,
}
}
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)View on GitHub (pinned to 35b8b99117)