github.com/thanos-io/thanos@v0.32.5/internal/cortex/querier/queryrange/limits.go (about) 1 // Copyright (c) The Cortex Authors. 2 // Licensed under the Apache License 2.0. 3 4 package queryrange 5 6 import ( 7 "context" 8 "net/http" 9 "time" 10 11 "github.com/go-kit/log/level" 12 "github.com/prometheus/prometheus/model/timestamp" 13 "github.com/weaveworks/common/httpgrpc" 14 15 "github.com/thanos-io/thanos/internal/cortex/tenant" 16 "github.com/thanos-io/thanos/internal/cortex/util" 17 "github.com/thanos-io/thanos/internal/cortex/util/spanlogger" 18 "github.com/thanos-io/thanos/internal/cortex/util/validation" 19 ) 20 21 // Limits allows us to specify per-tenant runtime limits on the behavior of 22 // the query handling code. 23 type Limits interface { 24 // MaxQueryLookback returns the max lookback period of queries. 25 MaxQueryLookback(userID string) time.Duration 26 27 // MaxQueryLength returns the limit of the length (in time) of a query. 28 MaxQueryLength(string) time.Duration 29 30 // MaxQueryParallelism returns the limit to the number of split queries the 31 // frontend will process in parallel. 32 MaxQueryParallelism(string) int 33 34 // MaxCacheFreshness returns the period after which results are cacheable, 35 // to prevent caching of very recent results. 36 MaxCacheFreshness(string) time.Duration 37 } 38 39 type limitsMiddleware struct { 40 Limits 41 next Handler 42 } 43 44 // NewLimitsMiddleware creates a new Middleware that enforces query limits. 45 func NewLimitsMiddleware(l Limits) Middleware { 46 return MiddlewareFunc(func(next Handler) Handler { 47 return limitsMiddleware{ 48 next: next, 49 Limits: l, 50 } 51 }) 52 } 53 54 func (l limitsMiddleware) Do(ctx context.Context, r Request) (Response, error) { 55 log, ctx := spanlogger.New(ctx, "limits") 56 defer log.Finish() 57 58 tenantIDs, err := tenant.TenantIDs(ctx) 59 if err != nil { 60 return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error()) 61 } 62 63 // Clamp the time range based on the max query lookback. 64 65 if maxQueryLookback := validation.SmallestPositiveNonZeroDurationPerTenant(tenantIDs, l.MaxQueryLookback); maxQueryLookback > 0 { 66 minStartTime := util.TimeToMillis(time.Now().Add(-maxQueryLookback)) 67 68 if r.GetEnd() < minStartTime { 69 // The request is fully outside the allowed range, so we can return an 70 // empty response. 71 level.Debug(log).Log( 72 "msg", "skipping the execution of the query because its time range is before the 'max query lookback' setting", 73 "reqStart", util.FormatTimeMillis(r.GetStart()), 74 "redEnd", util.FormatTimeMillis(r.GetEnd()), 75 "maxQueryLookback", maxQueryLookback) 76 77 return NewEmptyPrometheusResponse(), nil 78 } 79 80 if r.GetStart() < minStartTime { 81 // Replace the start time in the request. 82 level.Debug(log).Log( 83 "msg", "the start time of the query has been manipulated because of the 'max query lookback' setting", 84 "original", util.FormatTimeMillis(r.GetStart()), 85 "updated", util.FormatTimeMillis(minStartTime)) 86 87 r = r.WithStartEnd(minStartTime, r.GetEnd()) 88 } 89 } 90 91 // Enforce the max query length. 92 if maxQueryLength := validation.SmallestPositiveNonZeroDurationPerTenant(tenantIDs, l.MaxQueryLength); maxQueryLength > 0 { 93 queryLen := timestamp.Time(r.GetEnd()).Sub(timestamp.Time(r.GetStart())) 94 if queryLen > maxQueryLength { 95 return nil, httpgrpc.Errorf(http.StatusBadRequest, validation.ErrQueryTooLong, queryLen, maxQueryLength) 96 } 97 } 98 99 return l.next.Do(ctx, r) 100 }