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 }