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

     1  package queryrange
     2  
     3  import (
     4  	"context"
     5  	"net/http"
     6  	"time"
     7  
     8  	"github.com/opentracing/opentracing-go"
     9  	otlog "github.com/opentracing/opentracing-go/log"
    10  	"github.com/prometheus/client_golang/prometheus"
    11  	"github.com/prometheus/client_golang/prometheus/promauto"
    12  	"github.com/weaveworks/common/httpgrpc"
    13  
    14  	"github.com/grafana/dskit/tenant"
    15  
    16  	"github.com/grafana/loki/pkg/logproto"
    17  	"github.com/grafana/loki/pkg/logql/syntax"
    18  	"github.com/grafana/loki/pkg/querier/queryrange/queryrangebase"
    19  	"github.com/grafana/loki/pkg/util"
    20  	"github.com/grafana/loki/pkg/util/validation"
    21  )
    22  
    23  type lokiResult struct {
    24  	req queryrangebase.Request
    25  	ch  chan *packedResp
    26  }
    27  
    28  type packedResp struct {
    29  	resp queryrangebase.Response
    30  	err  error
    31  }
    32  
    33  type SplitByMetrics struct {
    34  	splits prometheus.Histogram
    35  }
    36  
    37  func NewSplitByMetrics(r prometheus.Registerer) *SplitByMetrics {
    38  	return &SplitByMetrics{
    39  		splits: promauto.With(r).NewHistogram(prometheus.HistogramOpts{
    40  			Namespace: "loki",
    41  			Name:      "query_frontend_partitions",
    42  			Help:      "Number of time-based partitions (sub-requests) per request",
    43  			Buckets:   prometheus.ExponentialBuckets(1, 4, 5), // 1 -> 1024
    44  		}),
    45  	}
    46  }
    47  
    48  type splitByInterval struct {
    49  	next     queryrangebase.Handler
    50  	limits   Limits
    51  	merger   queryrangebase.Merger
    52  	metrics  *SplitByMetrics
    53  	splitter Splitter
    54  }
    55  
    56  type Splitter func(req queryrangebase.Request, interval time.Duration) ([]queryrangebase.Request, error)
    57  
    58  // SplitByIntervalMiddleware creates a new Middleware that splits log requests by a given interval.
    59  func SplitByIntervalMiddleware(limits Limits, merger queryrangebase.Merger, splitter Splitter, metrics *SplitByMetrics) queryrangebase.Middleware {
    60  	return queryrangebase.MiddlewareFunc(func(next queryrangebase.Handler) queryrangebase.Handler {
    61  		return &splitByInterval{
    62  			next:     next,
    63  			limits:   limits,
    64  			merger:   merger,
    65  			metrics:  metrics,
    66  			splitter: splitter,
    67  		}
    68  	})
    69  }
    70  
    71  func (h *splitByInterval) Feed(ctx context.Context, input []*lokiResult) chan *lokiResult {
    72  	ch := make(chan *lokiResult)
    73  
    74  	go func() {
    75  		defer close(ch)
    76  		for _, d := range input {
    77  			select {
    78  			case <-ctx.Done():
    79  				return
    80  			case ch <- d:
    81  				continue
    82  			}
    83  		}
    84  	}()
    85  
    86  	return ch
    87  }
    88  
    89  func (h *splitByInterval) Process(
    90  	ctx context.Context,
    91  	parallelism int,
    92  	threshold int64,
    93  	input []*lokiResult,
    94  	maxSeries int,
    95  ) ([]queryrangebase.Response, error) {
    96  	var responses []queryrangebase.Response
    97  	ctx, cancel := context.WithCancel(ctx)
    98  	defer cancel()
    99  
   100  	ch := h.Feed(ctx, input)
   101  
   102  	// queries with 0 limits should not be exited early
   103  	var unlimited bool
   104  	if threshold == 0 {
   105  		unlimited = true
   106  	}
   107  
   108  	// don't spawn unnecessary goroutines
   109  	p := parallelism
   110  	if len(input) < parallelism {
   111  		p = len(input)
   112  	}
   113  
   114  	// per request wrapped handler for limiting the amount of series.
   115  	next := newSeriesLimiter(maxSeries).Wrap(h.next)
   116  	for i := 0; i < p; i++ {
   117  		go h.loop(ctx, ch, next)
   118  	}
   119  
   120  	for _, x := range input {
   121  		select {
   122  		case <-ctx.Done():
   123  			return nil, ctx.Err()
   124  		case data := <-x.ch:
   125  			if data.err != nil {
   126  				return nil, data.err
   127  			}
   128  
   129  			responses = append(responses, data.resp)
   130  
   131  			// see if we can exit early if a limit has been reached
   132  			if casted, ok := data.resp.(*LokiResponse); !unlimited && ok {
   133  				threshold -= casted.Count()
   134  
   135  				if threshold <= 0 {
   136  					return responses, nil
   137  				}
   138  
   139  			}
   140  
   141  		}
   142  	}
   143  
   144  	return responses, nil
   145  }
   146  
   147  func (h *splitByInterval) loop(ctx context.Context, ch <-chan *lokiResult, next queryrangebase.Handler) {
   148  	for data := range ch {
   149  
   150  		sp, ctx := opentracing.StartSpanFromContext(ctx, "interval")
   151  		data.req.LogToSpan(sp)
   152  
   153  		resp, err := next.Do(ctx, data.req)
   154  		sp.Finish()
   155  
   156  		select {
   157  		case <-ctx.Done():
   158  			return
   159  		case data.ch <- &packedResp{resp, err}:
   160  		}
   161  	}
   162  }
   163  
   164  func (h *splitByInterval) Do(ctx context.Context, r queryrangebase.Request) (queryrangebase.Response, error) {
   165  	tenantIDs, err := tenant.TenantIDs(ctx)
   166  	if err != nil {
   167  		return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error())
   168  	}
   169  
   170  	interval := validation.MaxDurationOrZeroPerTenant(tenantIDs, h.limits.QuerySplitDuration)
   171  	// skip split by if unset
   172  	if interval == 0 {
   173  		return h.next.Do(ctx, r)
   174  	}
   175  
   176  	intervals, err := h.splitter(r, interval)
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  	h.metrics.splits.Observe(float64(len(intervals)))
   181  
   182  	// no interval should not be processed by the frontend.
   183  	if len(intervals) == 0 {
   184  		return h.next.Do(ctx, r)
   185  	}
   186  
   187  	if sp := opentracing.SpanFromContext(ctx); sp != nil {
   188  		sp.LogFields(otlog.Int("n_intervals", len(intervals)))
   189  	}
   190  
   191  	if len(intervals) == 1 {
   192  		return h.next.Do(ctx, intervals[0])
   193  	}
   194  
   195  	var limit int64
   196  	switch req := r.(type) {
   197  	case *LokiRequest:
   198  		limit = int64(req.Limit)
   199  		if req.Direction == logproto.BACKWARD {
   200  			for i, j := 0, len(intervals)-1; i < j; i, j = i+1, j-1 {
   201  				intervals[i], intervals[j] = intervals[j], intervals[i]
   202  			}
   203  		}
   204  	case *LokiSeriesRequest, *LokiLabelNamesRequest:
   205  		// Set this to 0 since this is not used in Series/Labels Request.
   206  		limit = 0
   207  	default:
   208  		return nil, httpgrpc.Errorf(http.StatusBadRequest, "unknown request type")
   209  	}
   210  
   211  	input := make([]*lokiResult, 0, len(intervals))
   212  	for _, interval := range intervals {
   213  		input = append(input, &lokiResult{
   214  			req: interval,
   215  			ch:  make(chan *packedResp),
   216  		})
   217  	}
   218  
   219  	maxSeries := validation.SmallestPositiveIntPerTenant(tenantIDs, h.limits.MaxQuerySeries)
   220  	maxParallelism := validation.SmallestPositiveIntPerTenant(tenantIDs, h.limits.MaxQueryParallelism)
   221  	resps, err := h.Process(ctx, maxParallelism, limit, input, maxSeries)
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  	return h.merger.MergeResponse(resps...)
   226  }
   227  
   228  func splitByTime(req queryrangebase.Request, interval time.Duration) ([]queryrangebase.Request, error) {
   229  	var reqs []queryrangebase.Request
   230  
   231  	switch r := req.(type) {
   232  	case *LokiRequest:
   233  		forInterval(interval, r.StartTs, r.EndTs, false, func(start, end time.Time) {
   234  			reqs = append(reqs, &LokiRequest{
   235  				Query:     r.Query,
   236  				Limit:     r.Limit,
   237  				Step:      r.Step,
   238  				Interval:  r.Interval,
   239  				Direction: r.Direction,
   240  				Path:      r.Path,
   241  				StartTs:   start,
   242  				EndTs:     end,
   243  			})
   244  		})
   245  	case *LokiSeriesRequest:
   246  		forInterval(interval, r.StartTs, r.EndTs, true, func(start, end time.Time) {
   247  			reqs = append(reqs, &LokiSeriesRequest{
   248  				Match:   r.Match,
   249  				Path:    r.Path,
   250  				StartTs: start,
   251  				EndTs:   end,
   252  				Shards:  r.Shards,
   253  			})
   254  		})
   255  	case *LokiLabelNamesRequest:
   256  		forInterval(interval, r.StartTs, r.EndTs, true, func(start, end time.Time) {
   257  			reqs = append(reqs, &LokiLabelNamesRequest{
   258  				Path:    r.Path,
   259  				StartTs: start,
   260  				EndTs:   end,
   261  			})
   262  		})
   263  	default:
   264  		return nil, nil
   265  	}
   266  	return reqs, nil
   267  }
   268  
   269  // forInterval splits the given start and end time into given interval.
   270  // When endTimeInclusive is true, it would keep a gap of 1ms between the splits.
   271  // The only queries that have both start and end time inclusive are metadata queries,
   272  // and without keeping a gap, we would end up querying duplicate data in adjacent queries.
   273  func forInterval(interval time.Duration, start, end time.Time, endTimeInclusive bool, callback func(start, end time.Time)) {
   274  	// align the start time by split interval for better query performance of metadata queries and
   275  	// better cache-ability of query types that are cached.
   276  	ogStart := start
   277  	startNs := start.UnixNano()
   278  	start = time.Unix(0, startNs-startNs%interval.Nanoseconds())
   279  	firstInterval := true
   280  
   281  	for start := start; start.Before(end); start = start.Add(interval) {
   282  		newEnd := start.Add(interval)
   283  		if !newEnd.Before(end) {
   284  			newEnd = end
   285  		} else if endTimeInclusive {
   286  			newEnd = newEnd.Add(-time.Millisecond)
   287  		}
   288  		if firstInterval {
   289  			callback(ogStart, newEnd)
   290  			firstInterval = false
   291  			continue
   292  		}
   293  		callback(start, newEnd)
   294  	}
   295  }
   296  
   297  // maxRangeVectorDuration returns the maximum range vector duration within a LogQL query.
   298  func maxRangeVectorDuration(q string) (time.Duration, error) {
   299  	expr, err := syntax.ParseSampleExpr(q)
   300  	if err != nil {
   301  		return 0, err
   302  	}
   303  	var max time.Duration
   304  	expr.Walk(func(e interface{}) {
   305  		if r, ok := e.(*syntax.LogRange); ok && r.Interval > max {
   306  			max = r.Interval
   307  		}
   308  	})
   309  	return max, nil
   310  }
   311  
   312  // reduceSplitIntervalForRangeVector reduces the split interval for a range query based on the duration of the range vector.
   313  // Large range vector durations will not be split into smaller intervals because it can cause the queries to be slow by over-processing data.
   314  func reduceSplitIntervalForRangeVector(r queryrangebase.Request, interval time.Duration) (time.Duration, error) {
   315  	maxRange, err := maxRangeVectorDuration(r.GetQuery())
   316  	if err != nil {
   317  		return 0, err
   318  	}
   319  	if maxRange > interval {
   320  		return maxRange, nil
   321  	}
   322  	return interval, nil
   323  }
   324  
   325  func splitMetricByTime(r queryrangebase.Request, interval time.Duration) ([]queryrangebase.Request, error) {
   326  	var reqs []queryrangebase.Request
   327  
   328  	interval, err := reduceSplitIntervalForRangeVector(r, interval)
   329  	if err != nil {
   330  		return nil, err
   331  	}
   332  
   333  	lokiReq := r.(*LokiRequest)
   334  
   335  	// step align start and end time of the query. Start time is rounded down and end time is rounded up.
   336  	stepNs := r.GetStep() * 1e6
   337  	startNs := lokiReq.StartTs.UnixNano()
   338  	start := time.Unix(0, startNs-startNs%stepNs)
   339  
   340  	endNs := lokiReq.EndTs.UnixNano()
   341  	if mod := endNs % stepNs; mod != 0 {
   342  		endNs += stepNs - mod
   343  	}
   344  	end := time.Unix(0, endNs)
   345  
   346  	lokiReq = lokiReq.WithStartEnd(util.TimeToMillis(start), util.TimeToMillis(end)).(*LokiRequest)
   347  
   348  	// step is >= configured split interval, let us just split the query interval by step
   349  	if lokiReq.Step >= interval.Milliseconds() {
   350  		forInterval(time.Duration(lokiReq.Step*1e6), lokiReq.StartTs, lokiReq.EndTs, false, func(start, end time.Time) {
   351  			reqs = append(reqs, &LokiRequest{
   352  				Query:     lokiReq.Query,
   353  				Limit:     lokiReq.Limit,
   354  				Step:      lokiReq.Step,
   355  				Interval:  lokiReq.Interval,
   356  				Direction: lokiReq.Direction,
   357  				Path:      lokiReq.Path,
   358  				StartTs:   start,
   359  				EndTs:     end,
   360  			})
   361  		})
   362  
   363  		return reqs, nil
   364  	}
   365  
   366  	for start := lokiReq.StartTs; start.Before(lokiReq.EndTs); start = nextIntervalBoundary(start, r.GetStep(), interval).Add(time.Duration(r.GetStep()) * time.Millisecond) {
   367  		end := nextIntervalBoundary(start, r.GetStep(), interval)
   368  		if end.Add(time.Duration(r.GetStep())*time.Millisecond).After(lokiReq.EndTs) || end.Add(time.Duration(r.GetStep())*time.Millisecond) == lokiReq.EndTs {
   369  			end = lokiReq.EndTs
   370  		}
   371  		reqs = append(reqs, &LokiRequest{
   372  			Query:     lokiReq.Query,
   373  			Limit:     lokiReq.Limit,
   374  			Step:      lokiReq.Step,
   375  			Interval:  lokiReq.Interval,
   376  			Direction: lokiReq.Direction,
   377  			Path:      lokiReq.Path,
   378  			StartTs:   start,
   379  			EndTs:     end,
   380  		})
   381  	}
   382  
   383  	return reqs, nil
   384  }
   385  
   386  // Round up to the step before the next interval boundary.
   387  func nextIntervalBoundary(t time.Time, step int64, interval time.Duration) time.Time {
   388  	stepNs := step * 1e6
   389  	nsPerInterval := interval.Nanoseconds()
   390  	startOfNextInterval := ((t.UnixNano() / nsPerInterval) + 1) * nsPerInterval
   391  	// ensure that target is a multiple of steps away from the start time
   392  	target := startOfNextInterval - ((startOfNextInterval - t.UnixNano()) % stepNs)
   393  	if target == startOfNextInterval {
   394  		target -= stepNs
   395  	}
   396  	return time.Unix(0, target)
   397  }