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

     1  package queryrange
     2  
     3  import (
     4  	"flag"
     5  	"net/http"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/go-kit/log"
    10  	"github.com/prometheus/client_golang/prometheus"
    11  	"github.com/prometheus/prometheus/model/labels"
    12  	"github.com/weaveworks/common/httpgrpc"
    13  
    14  	"github.com/grafana/dskit/tenant"
    15  
    16  	"github.com/grafana/loki/pkg/loghttp"
    17  	"github.com/grafana/loki/pkg/logql/syntax"
    18  	"github.com/grafana/loki/pkg/logqlmodel/stats"
    19  	"github.com/grafana/loki/pkg/querier/queryrange/queryrangebase"
    20  	"github.com/grafana/loki/pkg/storage/chunk/cache"
    21  	"github.com/grafana/loki/pkg/storage/config"
    22  	"github.com/grafana/loki/pkg/util/validation"
    23  )
    24  
    25  // Config is the configuration for the queryrange tripperware
    26  type Config struct {
    27  	queryrangebase.Config `yaml:",inline"`
    28  }
    29  
    30  // RegisterFlags adds the flags required to configure this flag set.
    31  func (cfg *Config) RegisterFlags(f *flag.FlagSet) {
    32  	cfg.Config.RegisterFlags(f)
    33  }
    34  
    35  // Stopper gracefully shutdown resources created
    36  type Stopper interface {
    37  	Stop()
    38  }
    39  
    40  // NewTripperware returns a Tripperware configured with middlewares to align, split and cache requests.
    41  func NewTripperware(
    42  	cfg Config,
    43  	log log.Logger,
    44  	limits Limits,
    45  	schema config.SchemaConfig,
    46  	cacheGenNumLoader queryrangebase.CacheGenNumberLoader,
    47  	registerer prometheus.Registerer,
    48  ) (queryrangebase.Tripperware, Stopper, error) {
    49  	metrics := NewMetrics(registerer)
    50  
    51  	var (
    52  		c   cache.Cache
    53  		err error
    54  	)
    55  
    56  	if cfg.CacheResults {
    57  		c, err = cache.New(cfg.CacheConfig, registerer, log, stats.ResultCache)
    58  		if err != nil {
    59  			return nil, nil, err
    60  		}
    61  		if cfg.Compression == "snappy" {
    62  			c = cache.NewSnappy(c, log)
    63  		}
    64  	}
    65  
    66  	metricsTripperware, err := NewMetricTripperware(cfg, log, limits, schema, LokiCodec, c,
    67  		cacheGenNumLoader, PrometheusExtractor{}, metrics, registerer)
    68  	if err != nil {
    69  		return nil, nil, err
    70  	}
    71  
    72  	// NOTE: When we would start caching response from non-metric queries we would have to consider cache gen headers as well in
    73  	// MergeResponse implementation for Loki codecs same as it is done in Cortex at https://github.com/cortexproject/cortex/blob/21bad57b346c730d684d6d0205efef133422ab28/pkg/querier/queryrange/query_range.go#L170
    74  	logFilterTripperware, err := NewLogFilterTripperware(cfg, log, limits, schema, LokiCodec, c, metrics)
    75  	if err != nil {
    76  		return nil, nil, err
    77  	}
    78  
    79  	seriesTripperware, err := NewSeriesTripperware(cfg, log, limits, LokiCodec, metrics, schema)
    80  	if err != nil {
    81  		return nil, nil, err
    82  	}
    83  
    84  	labelsTripperware, err := NewLabelsTripperware(cfg, log, limits, LokiCodec, metrics)
    85  	if err != nil {
    86  		return nil, nil, err
    87  	}
    88  
    89  	instantMetricTripperware, err := NewInstantMetricTripperware(cfg, log, limits, schema, LokiCodec, metrics)
    90  	if err != nil {
    91  		return nil, nil, err
    92  	}
    93  	return func(next http.RoundTripper) http.RoundTripper {
    94  		metricRT := metricsTripperware(next)
    95  		logFilterRT := logFilterTripperware(next)
    96  		seriesRT := seriesTripperware(next)
    97  		labelsRT := labelsTripperware(next)
    98  		instantRT := instantMetricTripperware(next)
    99  		return newRoundTripper(next, logFilterRT, metricRT, seriesRT, labelsRT, instantRT, limits)
   100  	}, c, nil
   101  }
   102  
   103  type roundTripper struct {
   104  	next, log, metric, series, labels, instantMetric http.RoundTripper
   105  
   106  	limits Limits
   107  }
   108  
   109  // newRoundTripper creates a new queryrange roundtripper
   110  func newRoundTripper(next, log, metric, series, labels, instantMetric http.RoundTripper, limits Limits) roundTripper {
   111  	return roundTripper{
   112  		log:           log,
   113  		limits:        limits,
   114  		metric:        metric,
   115  		series:        series,
   116  		labels:        labels,
   117  		instantMetric: instantMetric,
   118  		next:          next,
   119  	}
   120  }
   121  
   122  func (r roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   123  	err := req.ParseForm()
   124  	if err != nil {
   125  		return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error())
   126  	}
   127  
   128  	switch op := getOperation(req.URL.Path); op {
   129  	case QueryRangeOp:
   130  		rangeQuery, err := loghttp.ParseRangeQuery(req)
   131  		if err != nil {
   132  			return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error())
   133  		}
   134  		expr, err := syntax.ParseExpr(rangeQuery.Query)
   135  		if err != nil {
   136  			return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error())
   137  		}
   138  		switch e := expr.(type) {
   139  		case syntax.SampleExpr:
   140  			return r.metric.RoundTrip(req)
   141  		case syntax.LogSelectorExpr:
   142  			expr, err := transformRegexQuery(req, e)
   143  			if err != nil {
   144  				return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error())
   145  			}
   146  			if err := validateLimits(req, rangeQuery.Limit, r.limits); err != nil {
   147  				return nil, err
   148  			}
   149  			// Only filter expressions are query sharded
   150  			if !expr.HasFilter() {
   151  				return r.next.RoundTrip(req)
   152  			}
   153  			return r.log.RoundTrip(req)
   154  
   155  		default:
   156  			return r.next.RoundTrip(req)
   157  		}
   158  	case SeriesOp:
   159  		_, err := loghttp.ParseAndValidateSeriesQuery(req)
   160  		if err != nil {
   161  			return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error())
   162  		}
   163  		return r.series.RoundTrip(req)
   164  	case LabelNamesOp:
   165  		_, err := loghttp.ParseLabelQuery(req)
   166  		if err != nil {
   167  			return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error())
   168  		}
   169  		return r.labels.RoundTrip(req)
   170  	case InstantQueryOp:
   171  		instantQuery, err := loghttp.ParseInstantQuery(req)
   172  		if err != nil {
   173  			return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error())
   174  		}
   175  		expr, err := syntax.ParseExpr(instantQuery.Query)
   176  		if err != nil {
   177  			return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error())
   178  		}
   179  		switch expr.(type) {
   180  		case syntax.SampleExpr:
   181  			return r.instantMetric.RoundTrip(req)
   182  		default:
   183  			return r.next.RoundTrip(req)
   184  		}
   185  	default:
   186  		return r.next.RoundTrip(req)
   187  	}
   188  }
   189  
   190  // transformRegexQuery backport the old regexp params into the v1 query format
   191  func transformRegexQuery(req *http.Request, expr syntax.LogSelectorExpr) (syntax.LogSelectorExpr, error) {
   192  	regexp := req.Form.Get("regexp")
   193  	if regexp != "" {
   194  		filterExpr, err := syntax.AddFilterExpr(expr, labels.MatchRegexp, "", regexp)
   195  		if err != nil {
   196  			return nil, err
   197  		}
   198  		params := req.URL.Query()
   199  		params.Set("query", filterExpr.String())
   200  		req.URL.RawQuery = params.Encode()
   201  		// force the form and query to be parsed again.
   202  		req.Form = nil
   203  		req.PostForm = nil
   204  		return filterExpr, nil
   205  	}
   206  	return expr, nil
   207  }
   208  
   209  // validates log entries limits
   210  func validateLimits(req *http.Request, reqLimit uint32, limits Limits) error {
   211  	tenantIDs, err := tenant.TenantIDs(req.Context())
   212  	if err != nil {
   213  		return httpgrpc.Errorf(http.StatusBadRequest, err.Error())
   214  	}
   215  
   216  	maxEntriesLimit := validation.SmallestPositiveNonZeroIntPerTenant(tenantIDs, limits.MaxEntriesLimitPerQuery)
   217  	if int(reqLimit) > maxEntriesLimit && maxEntriesLimit != 0 {
   218  		return httpgrpc.Errorf(http.StatusBadRequest,
   219  			"max entries limit per query exceeded, limit > max_entries_limit (%d > %d)", reqLimit, maxEntriesLimit)
   220  	}
   221  	return nil
   222  }
   223  
   224  const (
   225  	InstantQueryOp = "instant_query"
   226  	QueryRangeOp   = "query_range"
   227  	SeriesOp       = "series"
   228  	LabelNamesOp   = "labels"
   229  	IndexStatsOp   = "index_stats"
   230  )
   231  
   232  func getOperation(path string) string {
   233  	switch {
   234  	case strings.HasSuffix(path, "/query_range") || strings.HasSuffix(path, "/prom/query"):
   235  		return QueryRangeOp
   236  	case strings.HasSuffix(path, "/series"):
   237  		return SeriesOp
   238  	case strings.HasSuffix(path, "/labels") || strings.HasSuffix(path, "/label") || strings.HasSuffix(path, "/values"):
   239  		return LabelNamesOp
   240  	case strings.HasSuffix(path, "/v1/query"):
   241  		return InstantQueryOp
   242  	case path == "/loki/api/v1/index/stats":
   243  		return IndexStatsOp
   244  	default:
   245  		return ""
   246  	}
   247  }
   248  
   249  // NewLogFilterTripperware creates a new frontend tripperware responsible for handling log requests with regex.
   250  func NewLogFilterTripperware(
   251  	cfg Config,
   252  	log log.Logger,
   253  	limits Limits,
   254  	schema config.SchemaConfig,
   255  	codec queryrangebase.Codec,
   256  	c cache.Cache,
   257  	metrics *Metrics,
   258  ) (queryrangebase.Tripperware, error) {
   259  	queryRangeMiddleware := []queryrangebase.Middleware{
   260  		StatsCollectorMiddleware(),
   261  		NewLimitsMiddleware(limits),
   262  		queryrangebase.InstrumentMiddleware("split_by_interval", metrics.InstrumentMiddlewareMetrics),
   263  		SplitByIntervalMiddleware(limits, codec, splitByTime, metrics.SplitByMetrics),
   264  	}
   265  
   266  	if cfg.CacheResults {
   267  		queryCacheMiddleware := NewLogResultCache(
   268  			log,
   269  			limits,
   270  			c,
   271  			func(r queryrangebase.Request) bool {
   272  				return !r.GetCachingOptions().Disabled
   273  			},
   274  			metrics.LogResultCacheMetrics,
   275  		)
   276  		queryRangeMiddleware = append(
   277  			queryRangeMiddleware,
   278  			queryrangebase.InstrumentMiddleware("log_results_cache", metrics.InstrumentMiddlewareMetrics),
   279  			queryCacheMiddleware,
   280  		)
   281  	}
   282  
   283  	if cfg.ShardedQueries {
   284  		queryRangeMiddleware = append(queryRangeMiddleware,
   285  			NewQueryShardMiddleware(
   286  				log,
   287  				schema.Configs,
   288  				metrics.InstrumentMiddlewareMetrics, // instrumentation is included in the sharding middleware
   289  				metrics.MiddlewareMapperMetrics.shardMapper,
   290  				limits,
   291  			),
   292  		)
   293  	}
   294  
   295  	if cfg.MaxRetries > 0 {
   296  		queryRangeMiddleware = append(
   297  			queryRangeMiddleware, queryrangebase.InstrumentMiddleware("retry", metrics.InstrumentMiddlewareMetrics),
   298  			queryrangebase.NewRetryMiddleware(log, cfg.MaxRetries, metrics.RetryMiddlewareMetrics),
   299  		)
   300  	}
   301  
   302  	return func(next http.RoundTripper) http.RoundTripper {
   303  		if len(queryRangeMiddleware) > 0 {
   304  			return NewLimitedRoundTripper(next, codec, limits, queryRangeMiddleware...)
   305  		}
   306  		return next
   307  	}, nil
   308  }
   309  
   310  // NewSeriesTripperware creates a new frontend tripperware responsible for handling series requests
   311  func NewSeriesTripperware(
   312  	cfg Config,
   313  	log log.Logger,
   314  	limits Limits,
   315  	codec queryrangebase.Codec,
   316  	metrics *Metrics,
   317  	schema config.SchemaConfig,
   318  ) (queryrangebase.Tripperware, error) {
   319  	queryRangeMiddleware := []queryrangebase.Middleware{
   320  		StatsCollectorMiddleware(),
   321  		NewLimitsMiddleware(limits),
   322  		queryrangebase.InstrumentMiddleware("split_by_interval", metrics.InstrumentMiddlewareMetrics),
   323  		// The Series API needs to pull one chunk per series to extract the label set, which is much cheaper than iterating through all matching chunks.
   324  		// Force a 24 hours split by for series API, this will be more efficient with our static daily bucket storage.
   325  		// This would avoid queriers downloading chunks for same series over and over again for serving smaller queries.
   326  		SplitByIntervalMiddleware(WithSplitByLimits(limits, 24*time.Hour), codec, splitByTime, metrics.SplitByMetrics),
   327  	}
   328  
   329  	if cfg.MaxRetries > 0 {
   330  		queryRangeMiddleware = append(queryRangeMiddleware,
   331  			queryrangebase.InstrumentMiddleware("retry", metrics.InstrumentMiddlewareMetrics),
   332  			queryrangebase.NewRetryMiddleware(log, cfg.MaxRetries, metrics.RetryMiddlewareMetrics),
   333  		)
   334  	}
   335  
   336  	if cfg.ShardedQueries {
   337  		queryRangeMiddleware = append(queryRangeMiddleware,
   338  			NewSeriesQueryShardMiddleware(
   339  				log,
   340  				schema.Configs,
   341  				metrics.InstrumentMiddlewareMetrics,
   342  				metrics.MiddlewareMapperMetrics.shardMapper,
   343  				limits,
   344  				codec,
   345  			),
   346  		)
   347  	}
   348  
   349  	return func(next http.RoundTripper) http.RoundTripper {
   350  		if len(queryRangeMiddleware) > 0 {
   351  			return NewLimitedRoundTripper(next, codec, limits, queryRangeMiddleware...)
   352  		}
   353  		return next
   354  	}, nil
   355  }
   356  
   357  // NewLabelsTripperware creates a new frontend tripperware responsible for handling labels requests.
   358  func NewLabelsTripperware(
   359  	cfg Config,
   360  	log log.Logger,
   361  	limits Limits,
   362  	codec queryrangebase.Codec,
   363  	metrics *Metrics,
   364  ) (queryrangebase.Tripperware, error) {
   365  	queryRangeMiddleware := []queryrangebase.Middleware{
   366  		StatsCollectorMiddleware(),
   367  		NewLimitsMiddleware(limits),
   368  		queryrangebase.InstrumentMiddleware("split_by_interval", metrics.InstrumentMiddlewareMetrics),
   369  		// Force a 24 hours split by for labels API, this will be more efficient with our static daily bucket storage.
   370  		// This is because the labels API is an index-only operation.
   371  		SplitByIntervalMiddleware(WithSplitByLimits(limits, 24*time.Hour), codec, splitByTime, metrics.SplitByMetrics),
   372  	}
   373  
   374  	if cfg.MaxRetries > 0 {
   375  		queryRangeMiddleware = append(queryRangeMiddleware,
   376  			queryrangebase.InstrumentMiddleware("retry", metrics.InstrumentMiddlewareMetrics),
   377  			queryrangebase.NewRetryMiddleware(log, cfg.MaxRetries, metrics.RetryMiddlewareMetrics),
   378  		)
   379  	}
   380  
   381  	return func(next http.RoundTripper) http.RoundTripper {
   382  		if len(queryRangeMiddleware) > 0 {
   383  			// Do not forward any request header.
   384  			return queryrangebase.NewRoundTripper(next, codec, nil, queryRangeMiddleware...)
   385  		}
   386  		return next
   387  	}, nil
   388  }
   389  
   390  // NewMetricTripperware creates a new frontend tripperware responsible for handling metric queries
   391  func NewMetricTripperware(
   392  	cfg Config,
   393  	log log.Logger,
   394  	limits Limits,
   395  	schema config.SchemaConfig,
   396  	codec queryrangebase.Codec,
   397  	c cache.Cache,
   398  	cacheGenNumLoader queryrangebase.CacheGenNumberLoader,
   399  	extractor queryrangebase.Extractor,
   400  	metrics *Metrics,
   401  	registerer prometheus.Registerer,
   402  ) (queryrangebase.Tripperware, error) {
   403  	queryRangeMiddleware := []queryrangebase.Middleware{StatsCollectorMiddleware(), NewLimitsMiddleware(limits)}
   404  	if cfg.AlignQueriesWithStep {
   405  		queryRangeMiddleware = append(
   406  			queryRangeMiddleware,
   407  			queryrangebase.InstrumentMiddleware("step_align", metrics.InstrumentMiddlewareMetrics),
   408  			queryrangebase.StepAlignMiddleware,
   409  		)
   410  	}
   411  
   412  	queryRangeMiddleware = append(
   413  		queryRangeMiddleware,
   414  		queryrangebase.InstrumentMiddleware("split_by_interval", metrics.InstrumentMiddlewareMetrics),
   415  		SplitByIntervalMiddleware(limits, codec, splitMetricByTime, metrics.SplitByMetrics),
   416  	)
   417  
   418  	if cfg.CacheResults {
   419  		queryCacheMiddleware, err := queryrangebase.NewResultsCacheMiddleware(
   420  			log,
   421  			c,
   422  			cacheKeyLimits{limits},
   423  			limits,
   424  			codec,
   425  			extractor,
   426  			cacheGenNumLoader,
   427  			func(r queryrangebase.Request) bool {
   428  				return !r.GetCachingOptions().Disabled
   429  			},
   430  			registerer,
   431  		)
   432  		if err != nil {
   433  			return nil, err
   434  		}
   435  		queryRangeMiddleware = append(
   436  			queryRangeMiddleware,
   437  			queryrangebase.InstrumentMiddleware("results_cache", metrics.InstrumentMiddlewareMetrics),
   438  			queryCacheMiddleware,
   439  		)
   440  	}
   441  
   442  	if cfg.ShardedQueries {
   443  		queryRangeMiddleware = append(queryRangeMiddleware,
   444  			NewQueryShardMiddleware(
   445  				log,
   446  				schema.Configs,
   447  				metrics.InstrumentMiddlewareMetrics, // instrumentation is included in the sharding middleware
   448  				metrics.MiddlewareMapperMetrics.shardMapper,
   449  				limits,
   450  			),
   451  		)
   452  	}
   453  
   454  	if cfg.MaxRetries > 0 {
   455  		queryRangeMiddleware = append(
   456  			queryRangeMiddleware,
   457  			queryrangebase.InstrumentMiddleware("retry", metrics.InstrumentMiddlewareMetrics),
   458  			queryrangebase.NewRetryMiddleware(log, cfg.MaxRetries, metrics.RetryMiddlewareMetrics),
   459  		)
   460  	}
   461  
   462  	return func(next http.RoundTripper) http.RoundTripper {
   463  		// Finally, if the user selected any query range middleware, stitch it in.
   464  		if len(queryRangeMiddleware) > 0 {
   465  			rt := NewLimitedRoundTripper(next, codec, limits, queryRangeMiddleware...)
   466  			return queryrangebase.RoundTripFunc(func(r *http.Request) (*http.Response, error) {
   467  				if !strings.HasSuffix(r.URL.Path, "/query_range") {
   468  					return next.RoundTrip(r)
   469  				}
   470  				return rt.RoundTrip(r)
   471  			})
   472  		}
   473  		return next
   474  	}, nil
   475  }
   476  
   477  // NewInstantMetricTripperware creates a new frontend tripperware responsible for handling metric queries
   478  func NewInstantMetricTripperware(
   479  	cfg Config,
   480  	log log.Logger,
   481  	limits Limits,
   482  	schema config.SchemaConfig,
   483  	codec queryrangebase.Codec,
   484  	metrics *Metrics,
   485  ) (queryrangebase.Tripperware, error) {
   486  	queryRangeMiddleware := []queryrangebase.Middleware{StatsCollectorMiddleware(), NewLimitsMiddleware(limits)}
   487  
   488  	if cfg.ShardedQueries {
   489  		queryRangeMiddleware = append(queryRangeMiddleware,
   490  			NewSplitByRangeMiddleware(log, limits, metrics.MiddlewareMapperMetrics.rangeMapper),
   491  			NewQueryShardMiddleware(
   492  				log,
   493  				schema.Configs,
   494  				metrics.InstrumentMiddlewareMetrics, // instrumentation is included in the sharding middleware
   495  				metrics.MiddlewareMapperMetrics.shardMapper,
   496  				limits,
   497  			),
   498  		)
   499  	}
   500  
   501  	if cfg.MaxRetries > 0 {
   502  		queryRangeMiddleware = append(
   503  			queryRangeMiddleware,
   504  			queryrangebase.InstrumentMiddleware("retry", metrics.InstrumentMiddlewareMetrics),
   505  			queryrangebase.NewRetryMiddleware(log, cfg.MaxRetries, metrics.RetryMiddlewareMetrics),
   506  		)
   507  	}
   508  
   509  	return func(next http.RoundTripper) http.RoundTripper {
   510  		if len(queryRangeMiddleware) > 0 {
   511  			return NewLimitedRoundTripper(next, codec, limits, queryRangeMiddleware...)
   512  		}
   513  		return next
   514  	}, nil
   515  }