github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/querier/queryrange/limits.go (about)

     1  package queryrange
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/go-kit/log/level"
    11  	"github.com/opentracing/opentracing-go"
    12  	"github.com/prometheus/prometheus/model/timestamp"
    13  	"github.com/weaveworks/common/httpgrpc"
    14  	"github.com/weaveworks/common/user"
    15  
    16  	"github.com/grafana/dskit/tenant"
    17  
    18  	"github.com/grafana/loki/pkg/logproto"
    19  	"github.com/grafana/loki/pkg/logql"
    20  	"github.com/grafana/loki/pkg/querier/queryrange/queryrangebase"
    21  	"github.com/grafana/loki/pkg/util"
    22  	"github.com/grafana/loki/pkg/util/spanlogger"
    23  	"github.com/grafana/loki/pkg/util/validation"
    24  )
    25  
    26  const (
    27  	limitErrTmpl = "maximum of series (%d) reached for a single query"
    28  )
    29  
    30  var (
    31  	ErrMaxQueryParalellism = fmt.Errorf("querying is disabled, please contact your Loki operator")
    32  )
    33  
    34  // Limits extends the cortex limits interface with support for per tenant splitby parameters
    35  type Limits interface {
    36  	queryrangebase.Limits
    37  	logql.Limits
    38  	QuerySplitDuration(string) time.Duration
    39  	MaxQuerySeries(string) int
    40  	MaxEntriesLimitPerQuery(string) int
    41  	MinShardingLookback(string) time.Duration
    42  }
    43  
    44  type limits struct {
    45  	Limits
    46  	splitDuration time.Duration
    47  }
    48  
    49  func (l limits) QuerySplitDuration(user string) time.Duration {
    50  	return l.splitDuration
    51  }
    52  
    53  // WithSplitByLimits will construct a Limits with a static split by duration.
    54  func WithSplitByLimits(l Limits, splitBy time.Duration) Limits {
    55  	return limits{
    56  		Limits:        l,
    57  		splitDuration: splitBy,
    58  	}
    59  }
    60  
    61  // cacheKeyLimits intersects Limits and CacheSplitter
    62  type cacheKeyLimits struct {
    63  	Limits
    64  }
    65  
    66  func (l cacheKeyLimits) GenerateCacheKey(userID string, r queryrangebase.Request) string {
    67  	split := l.QuerySplitDuration(userID)
    68  
    69  	var currentInterval int64
    70  	if denominator := int64(split / time.Millisecond); denominator > 0 {
    71  		currentInterval = r.GetStart() / denominator
    72  	}
    73  
    74  	// include both the currentInterval and the split duration in key to ensure
    75  	// a cache key can't be reused when an interval changes
    76  	return fmt.Sprintf("%s:%s:%d:%d:%d", userID, r.GetQuery(), r.GetStep(), currentInterval, split)
    77  }
    78  
    79  type limitsMiddleware struct {
    80  	Limits
    81  	next queryrangebase.Handler
    82  }
    83  
    84  // NewLimitsMiddleware creates a new Middleware that enforces query limits.
    85  func NewLimitsMiddleware(l Limits) queryrangebase.Middleware {
    86  	return queryrangebase.MiddlewareFunc(func(next queryrangebase.Handler) queryrangebase.Handler {
    87  		return limitsMiddleware{
    88  			next:   next,
    89  			Limits: l,
    90  		}
    91  	})
    92  }
    93  
    94  func (l limitsMiddleware) Do(ctx context.Context, r queryrangebase.Request) (queryrangebase.Response, error) {
    95  	log, ctx := spanlogger.New(ctx, "limits")
    96  	defer log.Finish()
    97  
    98  	tenantIDs, err := tenant.TenantIDs(ctx)
    99  	if err != nil {
   100  		return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error())
   101  	}
   102  
   103  	// Clamp the time range based on the max query lookback.
   104  
   105  	if maxQueryLookback := validation.SmallestPositiveNonZeroDurationPerTenant(tenantIDs, l.MaxQueryLookback); maxQueryLookback > 0 {
   106  		minStartTime := util.TimeToMillis(time.Now().Add(-maxQueryLookback))
   107  
   108  		if r.GetEnd() < minStartTime {
   109  			// The request is fully outside the allowed range, so we can return an
   110  			// empty response.
   111  			level.Debug(log).Log(
   112  				"msg", "skipping the execution of the query because its time range is before the 'max query lookback' setting",
   113  				"reqStart", util.FormatTimeMillis(r.GetStart()),
   114  				"redEnd", util.FormatTimeMillis(r.GetEnd()),
   115  				"maxQueryLookback", maxQueryLookback)
   116  
   117  			return NewEmptyResponse(r)
   118  		}
   119  
   120  		if r.GetStart() < minStartTime {
   121  			// Replace the start time in the request.
   122  			level.Debug(log).Log(
   123  				"msg", "the start time of the query has been manipulated because of the 'max query lookback' setting",
   124  				"original", util.FormatTimeMillis(r.GetStart()),
   125  				"updated", util.FormatTimeMillis(minStartTime))
   126  
   127  			r = r.WithStartEnd(minStartTime, r.GetEnd())
   128  		}
   129  	}
   130  
   131  	// Enforce the max query length.
   132  	if maxQueryLength := validation.SmallestPositiveNonZeroDurationPerTenant(tenantIDs, l.MaxQueryLength); maxQueryLength > 0 {
   133  		queryLen := timestamp.Time(r.GetEnd()).Sub(timestamp.Time(r.GetStart()))
   134  		if queryLen > maxQueryLength {
   135  			return nil, httpgrpc.Errorf(http.StatusBadRequest, validation.ErrQueryTooLong, queryLen, maxQueryLength)
   136  		}
   137  	}
   138  
   139  	return l.next.Do(ctx, r)
   140  }
   141  
   142  type seriesLimiter struct {
   143  	hashes map[uint64]struct{}
   144  	rw     sync.RWMutex
   145  	buf    []byte // buf used for hashing to avoid allocations.
   146  
   147  	maxSeries int
   148  	next      queryrangebase.Handler
   149  }
   150  
   151  type seriesLimiterMiddleware int
   152  
   153  // newSeriesLimiter creates a new series limiter middleware for use for a single request.
   154  func newSeriesLimiter(maxSeries int) queryrangebase.Middleware {
   155  	return seriesLimiterMiddleware(maxSeries)
   156  }
   157  
   158  // Wrap wraps a global handler and returns a per request limited handler.
   159  // The handler returned is thread safe.
   160  func (slm seriesLimiterMiddleware) Wrap(next queryrangebase.Handler) queryrangebase.Handler {
   161  	return &seriesLimiter{
   162  		hashes:    make(map[uint64]struct{}),
   163  		maxSeries: int(slm),
   164  		buf:       make([]byte, 0, 1024),
   165  		next:      next,
   166  	}
   167  }
   168  
   169  func (sl *seriesLimiter) Do(ctx context.Context, req queryrangebase.Request) (queryrangebase.Response, error) {
   170  	// no need to fire a request if the limit is already reached.
   171  	if sl.isLimitReached() {
   172  		return nil, httpgrpc.Errorf(http.StatusBadRequest, limitErrTmpl, sl.maxSeries)
   173  	}
   174  	res, err := sl.next.Do(ctx, req)
   175  	if err != nil {
   176  		return res, err
   177  	}
   178  	promResponse, ok := res.(*LokiPromResponse)
   179  	if !ok {
   180  		return res, nil
   181  	}
   182  	if promResponse.Response == nil {
   183  		return res, nil
   184  	}
   185  	sl.rw.Lock()
   186  	var hash uint64
   187  	for _, s := range promResponse.Response.Data.Result {
   188  		lbs := logproto.FromLabelAdaptersToLabels(s.Labels)
   189  		hash, sl.buf = lbs.HashWithoutLabels(sl.buf, []string(nil)...)
   190  		sl.hashes[hash] = struct{}{}
   191  	}
   192  	sl.rw.Unlock()
   193  	if sl.isLimitReached() {
   194  		return nil, httpgrpc.Errorf(http.StatusBadRequest, limitErrTmpl, sl.maxSeries)
   195  	}
   196  	return res, nil
   197  }
   198  
   199  func (sl *seriesLimiter) isLimitReached() bool {
   200  	sl.rw.RLock()
   201  	defer sl.rw.RUnlock()
   202  	return len(sl.hashes) > sl.maxSeries
   203  }
   204  
   205  type limitedRoundTripper struct {
   206  	next   http.RoundTripper
   207  	limits Limits
   208  
   209  	codec      queryrangebase.Codec
   210  	middleware queryrangebase.Middleware
   211  }
   212  
   213  // NewLimitedRoundTripper creates a new roundtripper that enforces MaxQueryParallelism to the `next` roundtripper across `middlewares`.
   214  func NewLimitedRoundTripper(next http.RoundTripper, codec queryrangebase.Codec, limits Limits, middlewares ...queryrangebase.Middleware) http.RoundTripper {
   215  	transport := limitedRoundTripper{
   216  		next:       next,
   217  		codec:      codec,
   218  		limits:     limits,
   219  		middleware: queryrangebase.MergeMiddlewares(middlewares...),
   220  	}
   221  	return transport
   222  }
   223  
   224  type work struct {
   225  	req    queryrangebase.Request
   226  	ctx    context.Context
   227  	result chan result
   228  }
   229  
   230  type result struct {
   231  	response queryrangebase.Response
   232  	err      error
   233  }
   234  
   235  func newWork(ctx context.Context, req queryrangebase.Request) work {
   236  	return work{
   237  		req:    req,
   238  		ctx:    ctx,
   239  		result: make(chan result, 1),
   240  	}
   241  }
   242  
   243  func (rt limitedRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
   244  	var (
   245  		wg           sync.WaitGroup
   246  		intermediate = make(chan work)
   247  		ctx, cancel  = context.WithCancel(r.Context())
   248  	)
   249  	defer func() {
   250  		cancel()
   251  		wg.Wait()
   252  	}()
   253  
   254  	// Do not forward any request header.
   255  	request, err := rt.codec.DecodeRequest(ctx, r, nil)
   256  	if err != nil {
   257  		return nil, err
   258  	}
   259  
   260  	if span := opentracing.SpanFromContext(ctx); span != nil {
   261  		request.LogToSpan(span)
   262  	}
   263  	tenantIDs, err := tenant.TenantIDs(ctx)
   264  	if err != nil {
   265  		return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error())
   266  	}
   267  
   268  	parallelism := validation.SmallestPositiveIntPerTenant(tenantIDs, rt.limits.MaxQueryParallelism)
   269  	if parallelism < 1 {
   270  		return nil, httpgrpc.Errorf(http.StatusTooManyRequests, ErrMaxQueryParalellism.Error())
   271  	}
   272  
   273  	for i := 0; i < parallelism; i++ {
   274  		wg.Add(1)
   275  		go func() {
   276  			defer wg.Done()
   277  			for {
   278  				select {
   279  				case w := <-intermediate:
   280  					resp, err := rt.do(w.ctx, w.req)
   281  					w.result <- result{response: resp, err: err}
   282  				case <-ctx.Done():
   283  					return
   284  				}
   285  			}
   286  		}()
   287  	}
   288  
   289  	response, err := rt.middleware.Wrap(
   290  		queryrangebase.HandlerFunc(func(ctx context.Context, r queryrangebase.Request) (queryrangebase.Response, error) {
   291  			w := newWork(ctx, r)
   292  			select {
   293  			case intermediate <- w:
   294  			case <-ctx.Done():
   295  				return nil, ctx.Err()
   296  			}
   297  			select {
   298  			case response := <-w.result:
   299  				return response.response, response.err
   300  			case <-ctx.Done():
   301  				return nil, ctx.Err()
   302  			}
   303  		})).Do(ctx, request)
   304  	if err != nil {
   305  		return nil, err
   306  	}
   307  	return rt.codec.EncodeResponse(ctx, response)
   308  }
   309  
   310  func (rt limitedRoundTripper) do(ctx context.Context, r queryrangebase.Request) (queryrangebase.Response, error) {
   311  	request, err := rt.codec.EncodeRequest(ctx, r)
   312  	if err != nil {
   313  		return nil, err
   314  	}
   315  
   316  	if err := user.InjectOrgIDIntoHTTPRequest(ctx, request); err != nil {
   317  		return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error())
   318  	}
   319  
   320  	response, err := rt.next.RoundTrip(request)
   321  	if err != nil {
   322  		return nil, err
   323  	}
   324  	defer func() { _ = response.Body.Close() }()
   325  
   326  	return rt.codec.DecodeResponse(ctx, response, r)
   327  }