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  }