{"record":{"id":"762c477e3f1fa35e","repo":"thanos-io/thanos","slug":"fetching-range-d-d-caching-key-for-offset-d","errorCode":null,"errorMessage":"fetching range [%d, %d]: caching key for offset %d not found","messagePattern":"fetching range \\[(.+?), (.+?)\\]: caching key for offset (.+?) not found","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/store/cache/caching_bucket.go","lineNumber":448,"sourceCode":"\t\t\tdefer runutil.CloseWithLogOnErr(cb.logger, r, \"fetching range [%d, %d]\", m.start, m.end)\n\n\t\t\tvar bufSize int64\n\t\t\tif lastSubrangeOffset >= m.end {\n\t\t\t\tbufSize = m.end - m.start\n\t\t\t} else {\n\t\t\t\tbufSize = ((m.end - m.start) - cfg.SubrangeSize) + int64(lastSubrangeLength)\n\t\t\t}\n\n\t\t\tbuf := make([]byte, bufSize)\n\t\t\t_, err = io.ReadFull(r, buf)\n\t\t\tif err != nil {\n\t\t\t\treturn errors.Wrapf(err, \"fetching range [%d, %d]\", m.start, m.end)\n\t\t\t}\n\n\t\t\tfor off := m.start; off < m.end && gctx.Err() == nil; off += cfg.SubrangeSize {\n\t\t\t\tkey := cacheKeys[off]\n\t\t\t\tif key == \"\" {\n\t\t\t\t\treturn errors.Errorf(\"fetching range [%d, %d]: caching key for offset %d not found\", m.start, m.end, off)\n\t\t\t\t}\n\n\t\t\t\t// We need a new buffer for each subrange, both for storing into hits, and also for caching.\n\t\t\t\tvar subrangeData []byte\n\t\t\t\tif off == lastSubrangeOffset {\n\t\t\t\t\t// The very last subrange in the object may have different length,\n\t\t\t\t\t// if object length isn't divisible by subrange size.\n\t\t\t\t\tsubrangeData = buf[off-m.start : off-m.start+int64(lastSubrangeLength)]\n\t\t\t\t} else {\n\t\t\t\t\tsubrangeData = buf[off-m.start : off-m.start+cfg.SubrangeSize]\n\t\t\t\t}\n\n\t\t\t\tstoreToCache := false\n\t\t\t\thitsMutex.Lock()\n\t\t\t\tif _, ok := hits[key]; !ok {\n\t\t\t\t\tstoreToCache = true\n\t\t\t\t\thits[key] = subrangeData\n\t\t\t\t}","sourceCodeStart":430,"sourceCodeEnd":466,"githubUrl":"https://github.com/thanos-io/thanos/blob/35b8b991177def87ed52dcf10f9b6d87f07282c8/pkg/store/cache/caching_bucket.go#L430-L466","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nkey := cacheKeys[off]\nif key == \"\" {\n    return errors.Errorf(\"fetching range [%d, %d]: caching key for offset %d not found\", m.start, m.end, off)\n}\n// after\nkey := cacheKeys[off]\nif key == \"\" {\n    logger.Warn(\"missing cache key, skipping subrange\", \"offset\", off, \"start\", m.start, \"end\", m.end)\n    continue // degrade to uncached read instead of failing the fetch\n}","handlingStrategy":"try-catch","validationCode":"// Pre-validate range alignment against subrange size\nif length > 0 && (length%cfg.SubrangeSize != 0) {\n    logger.Warn(\"range not subrange-aligned; key map may mismatch\")\n}","typeGuard":null,"tryCatchPattern":"if err := fetch(ctx, name, off, length); err != nil {\n    if strings.Contains(err.Error(), \"caching key for offset\") {\n        // fall back to direct bucket read, bypassing cache\n        return bkt.GetRange(ctx, name, off, length)\n    }\n    return err\n}","preventionTips":["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"],"tags":["go","cache","invariant","range-request"],"backgroundTag":"internal-invariant-violation","analyzedSha":"35b8b991177def87ed52dcf10f9b6d87f07282c8","analyzedAt":"2026-09-07T01:49:59.689Z","contentChangedAt":"2026-09-07T01:49:59.689Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}