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

  1. Upgrade/patch Thanos: this indicates an internal inconsistency in key generation vs. iteration; check for known fixed issues in caching_bucket.go.
  2. Verify GetRange cache config (subrange size, TTLs) is coherent and not changed mid-request.
  3. Check the object size (attrs.Size) vs. requested offset/length — negative or zero lengths can misalign key generation.
  4. 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

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


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)