github.com/thanos-io/thanos@v0.32.5/pkg/queryfrontend/split_by_interval.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  // This is a modified copy from
     5  // https://github.com/cortexproject/cortex/blob/master/pkg/querier/queryrange/split_by_interval.go.
     6  
     7  package queryfrontend
     8  
     9  import (
    10  	"context"
    11  	"time"
    12  
    13  	"github.com/prometheus/client_golang/prometheus"
    14  	"github.com/prometheus/client_golang/prometheus/promauto"
    15  
    16  	"github.com/thanos-io/thanos/internal/cortex/querier/queryrange"
    17  )
    18  
    19  // SplitByIntervalMiddleware creates a new Middleware that splits requests by a given interval.
    20  func SplitByIntervalMiddleware(interval queryrange.IntervalFn, limits queryrange.Limits, merger queryrange.Merger, registerer prometheus.Registerer) queryrange.Middleware {
    21  	return queryrange.MiddlewareFunc(func(next queryrange.Handler) queryrange.Handler {
    22  		return splitByInterval{
    23  			next:     next,
    24  			limits:   limits,
    25  			merger:   merger,
    26  			interval: interval,
    27  			splitByCounter: promauto.With(registerer).NewCounter(prometheus.CounterOpts{
    28  				Namespace: "thanos",
    29  				Name:      "frontend_split_queries_total",
    30  				Help:      "Total number of underlying query requests after the split by interval is applied",
    31  			}),
    32  		}
    33  	})
    34  }
    35  
    36  type splitByInterval struct {
    37  	next     queryrange.Handler
    38  	limits   queryrange.Limits
    39  	merger   queryrange.Merger
    40  	interval queryrange.IntervalFn
    41  
    42  	// Metrics.
    43  	splitByCounter prometheus.Counter
    44  }
    45  
    46  func (s splitByInterval) Do(ctx context.Context, r queryrange.Request) (queryrange.Response, error) {
    47  	// First we're going to build new requests, one for each day, taking care
    48  	// to line up the boundaries with step.
    49  	reqs, err := splitQuery(r, s.interval(r))
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  	s.splitByCounter.Add(float64(len(reqs)))
    54  
    55  	reqResps, err := queryrange.DoRequests(ctx, s.next, reqs, s.limits)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	resps := make([]queryrange.Response, 0, len(reqResps))
    61  	for _, reqResp := range reqResps {
    62  		resps = append(resps, reqResp.Response)
    63  	}
    64  
    65  	response, err := s.merger.MergeResponse(r, resps...)
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  	return response, nil
    70  }
    71  
    72  func splitQuery(r queryrange.Request, interval time.Duration) ([]queryrange.Request, error) {
    73  	var reqs []queryrange.Request
    74  	if _, ok := r.(*ThanosQueryRangeRequest); ok {
    75  		// Replace @ modifier function to their respective constant values in the query.
    76  		// This way subqueries will be evaluated at the same time as the parent query.
    77  		query, err := queryrange.EvaluateAtModifierFunction(r.GetQuery(), r.GetStart(), r.GetEnd())
    78  		if err != nil {
    79  			return nil, err
    80  		}
    81  		if start := r.GetStart(); start == r.GetEnd() {
    82  			reqs = append(reqs, r.WithStartEnd(start, start))
    83  		} else {
    84  			for ; start < r.GetEnd(); start = nextIntervalBoundary(start, r.GetStep(), interval) + r.GetStep() {
    85  				end := nextIntervalBoundary(start, r.GetStep(), interval)
    86  				if end+r.GetStep() >= r.GetEnd() {
    87  					end = r.GetEnd()
    88  				}
    89  
    90  				reqs = append(reqs, r.WithQuery(query).WithStartEnd(start, end))
    91  			}
    92  		}
    93  	} else {
    94  		dur := int64(interval / time.Millisecond)
    95  		for start := r.GetStart(); start < r.GetEnd(); start = start + dur {
    96  			end := start + dur
    97  			if end > r.GetEnd() {
    98  				end = r.GetEnd()
    99  			}
   100  
   101  			reqs = append(reqs, r.WithStartEnd(start, end))
   102  		}
   103  	}
   104  
   105  	return reqs, nil
   106  }
   107  
   108  // Round up to the step before the next interval boundary.
   109  func nextIntervalBoundary(t, step int64, interval time.Duration) int64 {
   110  	msPerInterval := int64(interval / time.Millisecond)
   111  	startOfNextInterval := ((t / msPerInterval) + 1) * msPerInterval
   112  	// ensure that target is a multiple of steps away from the start time
   113  	target := startOfNextInterval - ((startOfNextInterval - t) % step)
   114  	if target == startOfNextInterval {
   115  		target -= step
   116  	}
   117  	return target
   118  }