thanos-io/thanos · error
fetching range [ , ]: caching key for offset not found
Error message
fetching range [%d, %d]: caching key for offset %d not found
What it means
While splitting a fetched missing range into subranges for caching, each offset must have a precomputed cache key in cacheKeys. If the key for an offset is empty, this error aborts the fetch — an internal invariant violation meaning the key map was built with a different subrange layout than the iteration uses.
Solutions
- Upgrade/patch Thanos: this indicates an internal inconsistency in key generation vs. iteration; check for known fixed issues in caching_bucket.go.
- Verify GetRange cache config (subrange size, TTLs) is coherent and not changed mid-request.
- Check the object size (attrs.Size) vs. requested offset/length — negative or zero lengths can misalign key generation.
- Report with the exact range, subrange size, and object size to reproduce the invariant violation.
Example fix
// before
key := cacheKeys[off]
if key == "" {
return errors.Errorf("fetching range [%d, %d]: caching key for offset %d not found", m.start, m.end, off)
}
// after
key := cacheKeys[off]
if key == "" {
logger.Warn("missing cache key, skipping subrange", "offset", off, "start", m.start, "end", m.end)
continue // degrade to uncached read instead of failing the fetch
} Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-validate range alignment against subrange size
if length > 0 && (length%cfg.SubrangeSize != 0) {
logger.Warn("range not subrange-aligned; key map may mismatch")
} Try / catch
if err := fetch(ctx, name, off, length); err != nil {
if strings.Contains(err.Error(), "caching key for offset") {
// fall back to direct bucket read, bypassing cache
return bkt.GetRange(ctx, name, off, length)
}
return err
} Prevention
- Keep Thanos patched — this is an internal invariant bug
- Avoid changing subrange-size config while requests are in flight
- Validate offset/length/attrs.Size inputs before cached range fetches
- Fall back to the uncached bucket read if cache bookkeeping fails
When it happens
Trigger: cachedGetRange iterates offsets with cfg.SubrangeSize steps inside a missing range and finds cacheKeys[off] == "", i.e. the key-building loop and the subrange loop disagree (e.g. subrange size config changed between building keys and iterating, or a boundary offset was skipped).
Common situations: Bugs or inconsistent GetRange caching configuration where the number of keys generated doesn't match the missing-range extent; misaligned last subrange when object size isn't a multiple of SubrangeSize.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- populateWithDelChunkSeriesIterator: unexpected empty chunk…
- ErrInvalidBucketCacheKeyFormat
- ErrInvalidBucketCacheKeyVerb
- ErrParseKeyInt
- errObjNotFound
AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07).
Data as JSON: /api/errors/762c477e3f1fa35e.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/store/cache/caching_bucket.go:448
defer runutil.CloseWithLogOnErr(cb.logger, r, "fetching range [%d, %d]", m.start, m.end)
var bufSize int64
if lastSubrangeOffset >= m.end {
bufSize = m.end - m.start
} else {
bufSize = ((m.end - m.start) - cfg.SubrangeSize) + int64(lastSubrangeLength)
}
buf := make([]byte, bufSize)
_, err = io.ReadFull(r, buf)
if err != nil {
return errors.Wrapf(err, "fetching range [%d, %d]", m.start, m.end)
}
for off := m.start; off < m.end && gctx.Err() == nil; off += cfg.SubrangeSize {
key := cacheKeys[off]
if key == "" {
return errors.Errorf("fetching range [%d, %d]: caching key for offset %d not found", m.start, m.end, off)
}
// We need a new buffer for each subrange, both for storing into hits, and also for caching.
var subrangeData []byte
if off == lastSubrangeOffset {
// The very last subrange in the object may have different length,
// if object length isn't divisible by subrange size.
subrangeData = buf[off-m.start : off-m.start+int64(lastSubrangeLength)]
} else {
subrangeData = buf[off-m.start : off-m.start+cfg.SubrangeSize]
}
storeToCache := false
hitsMutex.Lock()
if _, ok := hits[key]; !ok {
storeToCache = true
hits[key] = subrangeData
}View on GitHub (pinned to 35b8b99117)