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 }