{"record":{"id":"b0e9e8581e16752d","repo":"thanos-io/thanos","slug":"negative-offset","errorCode":null,"errorMessage":"negative offset","messagePattern":"negative offset","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"info","filePath":"internal/cortex/querier/queryrange/results_cache.go","lineNumber":427,"sourceCode":"\t\t\t\tatModCachable = false\n\t\t\t\treturn errAtModifierAfterEnd\n\t\t\t}\n\t\tcase *parser.SubqueryExpr:\n\t\t\tif e.Timestamp != nil && (*e.Timestamp > end || *e.Timestamp > maxCacheTime) {\n\t\t\t\tatModCachable = false\n\t\t\t\treturn errAtModifierAfterEnd\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\n\treturn atModCachable\n}\n\n// isOffsetCachable returns true if the offset is positive, result is safe to cache.\n// and false when offset is negative, result is not cached.\nfunc (s resultsCache) isOffsetCachable(r Request) bool {\n\tvar errNegativeOffset = errors.New(\"negative offset\")\n\tquery := r.GetQuery()\n\tif !strings.Contains(query, \"offset\") {\n\t\treturn true\n\t}\n\texpr, err := extpromql.ParseExpr(query)\n\tif err != nil {\n\t\tlevel.Warn(s.logger).Log(\"msg\", \"failed to parse query, considering offset as not cachable\", \"query\", query, \"err\", err)\n\t\treturn false\n\t}\n\n\toffsetCachable := true\n\tparser.Inspect(expr, func(n parser.Node, _ []parser.Node) error {\n\t\tswitch e := n.(type) {\n\t\tcase *parser.VectorSelector:\n\t\t\tif e.OriginalOffset < 0 {\n\t\t\t\toffsetCachable = false\n\t\t\t\treturn errNegativeOffset\n\t\t\t}","sourceCodeStart":409,"sourceCodeEnd":445,"githubUrl":"https://github.com/thanos-io/thanos/blob/35b8b991177def87ed52dcf10f9b6d87f07282c8/internal/cortex/querier/queryrange/results_cache.go#L409-L445","documentation":"resultsCache.isOffsetCachable raises this sentinel when a query contains a negative `offset`, because cached results for negative offsets would depend on data newer than the cached window and would be wrong. The cache skips such responses.","triggerScenarios":"Querying with PromQL containing `offset -5m` (or any negative offset) while results caching is enabled; detected via extpromql.ParseExpr during shouldCacheResponse.","commonSituations":"Dashboards comparing current vs. past values using negative offsets (a common Grafana idiom that is unsafe with result caching); users porting queries that relied on future data.","solutions":["Rewrite the query to use a positive offset or shift the query range instead","Accept no caching for this query (behavior is correct, just uncached)","Fetch the comparison data in a separate time-shifted query"],"exampleFix":"// before\nmetric offset -5m\n// after: run query with end time 5m earlier, or positive offset\nmetric offset 5m","handlingStrategy":"validation","validationCode":"if strings.Contains(query, \"offset -\") {\n\treturn errors.New(\"negative offsets bypass results cache\")\n}","typeGuard":null,"tryCatchPattern":"resp, err := frontend.Query(ctx, q)\nif err != nil && strings.Contains(err.Error(), \"negative offset\") {\n\t// rewrite query or skip caching path\n}","preventionTips":["Avoid negative offsets in dashboards; use time-shifted panels instead","Parse queries with promql parser to detect negative offsets before submitting","Accept that negative-offset queries are correct but uncached"],"tags":["promql","cache","offset","cortex"],"backgroundTag":"unsupported-cache-condition","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"}