github.com/thanos-io/thanos@v0.32.5/test/e2e/query_frontend_test.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package e2e_test
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"os"
    10  	"reflect"
    11  	"sort"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/efficientgo/e2e"
    16  	e2emon "github.com/efficientgo/e2e/monitoring"
    17  	"github.com/efficientgo/e2e/monitoring/matchers"
    18  	"github.com/pkg/errors"
    19  	"github.com/prometheus/common/model"
    20  	"github.com/prometheus/prometheus/model/labels"
    21  	"github.com/prometheus/prometheus/model/timestamp"
    22  
    23  	"github.com/efficientgo/core/testutil"
    24  	"github.com/thanos-io/thanos/pkg/block/metadata"
    25  	"github.com/thanos-io/thanos/pkg/cacheutil"
    26  	"github.com/thanos-io/thanos/pkg/promclient"
    27  	"github.com/thanos-io/thanos/pkg/queryfrontend"
    28  	"github.com/thanos-io/thanos/pkg/runutil"
    29  	"github.com/thanos-io/thanos/pkg/testutil/e2eutil"
    30  	"github.com/thanos-io/thanos/test/e2e/e2ethanos"
    31  )
    32  
    33  func TestQFEEngineExplanation(t *testing.T) {
    34  	t.Parallel()
    35  
    36  	e, err := e2e.NewDockerEnvironment("qfe-opts")
    37  	testutil.Ok(t, err)
    38  	t.Cleanup(e2ethanos.CleanScenario(t, e))
    39  
    40  	prom, sidecar := e2ethanos.NewPrometheusWithSidecar(e, "1", e2ethanos.DefaultPromConfig("test", 0, "", "", e2ethanos.LocalPrometheusTarget), "", e2ethanos.DefaultPrometheusImage(), "")
    41  	testutil.Ok(t, e2e.StartAndWaitReady(prom, sidecar))
    42  
    43  	q := e2ethanos.NewQuerierBuilder(e, "1", sidecar.InternalEndpoint("grpc")).Init()
    44  	testutil.Ok(t, e2e.StartAndWaitReady(q))
    45  
    46  	inMemoryCacheConfig := queryfrontend.CacheProviderConfig{
    47  		Type: queryfrontend.INMEMORY,
    48  		Config: queryfrontend.InMemoryResponseCacheConfig{
    49  			MaxSizeItems: 1000,
    50  			Validity:     time.Hour,
    51  		},
    52  	}
    53  
    54  	cfg := queryfrontend.Config{}
    55  	queryFrontend := e2ethanos.NewQueryFrontend(e, "1", "http://"+q.InternalEndpoint("http"), cfg, inMemoryCacheConfig)
    56  	testutil.Ok(t, e2e.StartAndWaitReady(queryFrontend))
    57  
    58  	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
    59  	t.Cleanup(cancel)
    60  
    61  	testutil.Ok(t, q.WaitSumMetricsWithOptions(e2emon.Equals(1), []string{"thanos_store_nodes_grpc_connections"}, e2emon.WaitMissingMetrics()))
    62  
    63  	var queriesBeforeUp = 0
    64  	testutil.Ok(t, runutil.RetryWithLog(e2e.NewLogger(os.Stderr), 5*time.Second, ctx.Done(), func() error {
    65  		queriesBeforeUp++
    66  		_, _, err := simpleInstantQuery(t, ctx, queryFrontend.Endpoint("http"), e2ethanos.QueryUpWithoutInstance, time.Now, promclient.QueryOptions{}, 1)
    67  		return err
    68  	}))
    69  
    70  	t.Logf("queried %d times before prom is up", queriesBeforeUp)
    71  
    72  	t.Run("passes query to Thanos engine", func(t *testing.T) {
    73  		queryAndAssertSeries(t, ctx, queryFrontend.Endpoint("http"), e2ethanos.QueryUpWithoutInstance, time.Now, promclient.QueryOptions{
    74  			Deduplicate: false,
    75  			Engine:      "thanos",
    76  		}, []model.Metric{
    77  			{
    78  				"job":        "myself",
    79  				"prometheus": "test",
    80  				"replica":    "0",
    81  			},
    82  		})
    83  
    84  		testutil.Ok(t, q.WaitSumMetricsWithOptions(e2emon.GreaterOrEqual(1), []string{"thanos_engine_queries_total"}, e2emon.WaitMissingMetrics(), e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "fallback", "false"))))
    85  
    86  	})
    87  
    88  	now := time.Now()
    89  	t.Run("passes range query to Thanos engine and returns explanation", func(t *testing.T) {
    90  		explanation := rangeQuery(t, ctx, queryFrontend.Endpoint("http"), e2ethanos.QueryUpWithoutInstance,
    91  			timestamp.FromTime(now.Add(-5*time.Minute)),
    92  			timestamp.FromTime(now), 1, promclient.QueryOptions{
    93  				Explain:     true,
    94  				Engine:      "thanos",
    95  				Deduplicate: true,
    96  			}, func(res model.Matrix) error {
    97  				if res.Len() == 0 {
    98  					return fmt.Errorf("expected results")
    99  				}
   100  				return nil
   101  			})
   102  		testutil.Assert(t, explanation != nil, "expected to have an explanation")
   103  		testutil.Ok(t, q.WaitSumMetricsWithOptions(e2emon.GreaterOrEqual(2), []string{"thanos_engine_queries_total"}, e2emon.WaitMissingMetrics(), e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "fallback", "false"))))
   104  	})
   105  
   106  	t.Run("passes query to Prometheus engine", func(t *testing.T) {
   107  		queryAndAssertSeries(t, ctx, queryFrontend.Endpoint("http"), e2ethanos.QueryUpWithoutInstance, time.Now, promclient.QueryOptions{
   108  			Deduplicate: false,
   109  			Engine:      "prometheus",
   110  		}, []model.Metric{
   111  			{
   112  				"job":        "myself",
   113  				"prometheus": "test",
   114  				"replica":    "0",
   115  			},
   116  		})
   117  
   118  		testutil.Ok(t, q.WaitSumMetricsWithOptions(e2emon.GreaterOrEqual(1), []string{"thanos_engine_queries_total"}, e2emon.WaitMissingMetrics(), e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "fallback", "false"))))
   119  		testutil.Ok(t, q.WaitSumMetricsWithOptions(e2emon.GreaterOrEqual(float64(queriesBeforeUp)+2), []string{"thanos_query_concurrent_gate_queries_total"}, e2emon.WaitMissingMetrics()))
   120  
   121  	})
   122  
   123  	t.Run("explanation works with instant query", func(t *testing.T) {
   124  		_, explanation := instantQuery(t, ctx, queryFrontend.Endpoint("http"), e2ethanos.QueryUpWithoutInstance, time.Now, promclient.QueryOptions{
   125  			Deduplicate: true,
   126  			Engine:      "thanos",
   127  			Explain:     true,
   128  		}, 1)
   129  		testutil.Assert(t, explanation != nil, "expected to have an explanation")
   130  	})
   131  
   132  	t.Run("does not return explanation if not needed", func(t *testing.T) {
   133  		_, explanation := instantQuery(t, ctx, queryFrontend.Endpoint("http"), e2ethanos.QueryUpWithoutInstance, time.Now, promclient.QueryOptions{
   134  			Deduplicate: false,
   135  			Engine:      "thanos",
   136  			Explain:     false,
   137  		}, 1)
   138  		testutil.Assert(t, explanation == nil, "expected to have no explanation")
   139  	})
   140  }
   141  
   142  func TestQueryFrontend(t *testing.T) {
   143  	t.Parallel()
   144  
   145  	e, err := e2e.NewDockerEnvironment("query-frontend")
   146  	testutil.Ok(t, err)
   147  	t.Cleanup(e2ethanos.CleanScenario(t, e))
   148  
   149  	now := time.Now()
   150  
   151  	prom, sidecar := e2ethanos.NewPrometheusWithSidecar(e, "1", e2ethanos.DefaultPromConfig("test", 0, "", "", e2ethanos.LocalPrometheusTarget), "", e2ethanos.DefaultPrometheusImage(), "")
   152  	testutil.Ok(t, e2e.StartAndWaitReady(prom, sidecar))
   153  
   154  	q := e2ethanos.NewQuerierBuilder(e, "1", sidecar.InternalEndpoint("grpc")).Init()
   155  	testutil.Ok(t, e2e.StartAndWaitReady(q))
   156  
   157  	inMemoryCacheConfig := queryfrontend.CacheProviderConfig{
   158  		Type: queryfrontend.INMEMORY,
   159  		Config: queryfrontend.InMemoryResponseCacheConfig{
   160  			MaxSizeItems: 1000,
   161  			Validity:     time.Hour,
   162  		},
   163  	}
   164  
   165  	cfg := queryfrontend.Config{}
   166  	queryFrontend := e2ethanos.NewQueryFrontend(e, "1", "http://"+q.InternalEndpoint("http"), cfg, inMemoryCacheConfig)
   167  	testutil.Ok(t, e2e.StartAndWaitReady(queryFrontend))
   168  
   169  	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
   170  	t.Cleanup(cancel)
   171  
   172  	testutil.Ok(t, q.WaitSumMetricsWithOptions(e2emon.Equals(1), []string{"thanos_store_nodes_grpc_connections"}, e2emon.WaitMissingMetrics()))
   173  
   174  	// Ensure we can get the result from Querier first so that it
   175  	// doesn't need to retry when we send queries to the frontend later.
   176  	queryAndAssertSeries(t, ctx, q.Endpoint("http"), e2ethanos.QueryUpWithoutInstance, time.Now, promclient.QueryOptions{
   177  		Deduplicate: false,
   178  	}, []model.Metric{
   179  		{
   180  			"job":        "myself",
   181  			"prometheus": "test",
   182  			"replica":    "0",
   183  		},
   184  	})
   185  
   186  	vals, err := q.SumMetrics([]string{"http_requests_total"})
   187  	e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "handler", "query"))
   188  
   189  	testutil.Ok(t, err)
   190  	testutil.Equals(t, 1, len(vals))
   191  	queryTimes := vals[0]
   192  
   193  	t.Run("query frontend works for instant query", func(t *testing.T) {
   194  		queryAndAssertSeries(t, ctx, queryFrontend.Endpoint("http"), e2ethanos.QueryUpWithoutInstance, time.Now, promclient.QueryOptions{
   195  			Deduplicate: false,
   196  		}, []model.Metric{
   197  			{
   198  				"job":        "myself",
   199  				"prometheus": "test",
   200  				"replica":    "0",
   201  			},
   202  		})
   203  
   204  		testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions(
   205  			e2emon.Equals(1),
   206  			[]string{"thanos_query_frontend_queries_total"},
   207  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "query")),
   208  		))
   209  
   210  		testutil.Ok(t, q.WaitSumMetricsWithOptions(
   211  			e2emon.Equals(queryTimes+1),
   212  			[]string{"http_requests_total"},
   213  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "handler", "query")),
   214  		))
   215  	})
   216  
   217  	t.Run("query frontend works for range query and it can cache results", func(t *testing.T) {
   218  		rangeQuery(
   219  			t,
   220  			ctx,
   221  			queryFrontend.Endpoint("http"),
   222  			e2ethanos.QueryUpWithoutInstance,
   223  			timestamp.FromTime(now.Add(-time.Hour)),
   224  			timestamp.FromTime(now.Add(time.Hour)),
   225  			14,
   226  			promclient.QueryOptions{
   227  				Deduplicate: true,
   228  			},
   229  			func(res model.Matrix) error {
   230  				if len(res) == 0 {
   231  					return errors.Errorf("expected some results, got nothing")
   232  				}
   233  				return nil
   234  			},
   235  		)
   236  
   237  		testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions(
   238  			e2emon.Equals(1),
   239  			[]string{"thanos_query_frontend_queries_total"},
   240  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "query_range")),
   241  		))
   242  		testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "cortex_cache_fetched_keys_total"))
   243  		testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(0), "cortex_cache_hits_total"))
   244  		testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "querier_cache_added_new_total"))
   245  		testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "querier_cache_added_total"))
   246  		testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "querier_cache_entries"))
   247  		testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "querier_cache_gets_total"))
   248  		testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "querier_cache_misses_total"))
   249  
   250  		// Query is only 2h so it won't be split.
   251  		testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "thanos_frontend_split_queries_total"))
   252  
   253  		testutil.Ok(t, q.WaitSumMetricsWithOptions(
   254  			e2emon.Equals(1),
   255  			[]string{"http_requests_total"},
   256  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "handler", "query_range")),
   257  		))
   258  	})
   259  
   260  	t.Run("same range query, cache hit.", func(t *testing.T) {
   261  		// Run the same range query again, the result can be retrieved from cache directly.
   262  		rangeQuery(
   263  			t,
   264  			ctx,
   265  			queryFrontend.Endpoint("http"),
   266  			e2ethanos.QueryUpWithoutInstance,
   267  			timestamp.FromTime(now.Add(-time.Hour)),
   268  			timestamp.FromTime(now.Add(time.Hour)),
   269  			14,
   270  			promclient.QueryOptions{
   271  				Deduplicate: true,
   272  			},
   273  			func(res model.Matrix) error {
   274  				if len(res) == 0 {
   275  					return errors.Errorf("expected some results, got nothing")
   276  				}
   277  				return nil
   278  			},
   279  		)
   280  
   281  		testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions(
   282  			e2emon.Equals(2),
   283  			[]string{"thanos_query_frontend_queries_total"},
   284  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "query_range"))),
   285  		)
   286  		testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(2), "cortex_cache_fetched_keys_total"))
   287  		testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "cortex_cache_hits_total"))
   288  		testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "querier_cache_added_new_total"))
   289  		testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(2), "querier_cache_added_total"))
   290  		testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "querier_cache_entries"))
   291  		testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(2), "querier_cache_gets_total"))
   292  		testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "querier_cache_misses_total"))
   293  
   294  		// Query is only 2h so it won't be split.
   295  		testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions(
   296  			e2emon.Equals(2), []string{"thanos_frontend_split_queries_total"},
   297  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "tripperware", "query_range"))),
   298  		)
   299  
   300  		// One more request is needed in order to satisfy the req range.
   301  		testutil.Ok(t, q.WaitSumMetricsWithOptions(
   302  			e2emon.Equals(2),
   303  			[]string{"http_requests_total"},
   304  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "handler", "query_range"))),
   305  		)
   306  	})
   307  
   308  	t.Run("range query > 24h should be split", func(t *testing.T) {
   309  		rangeQuery(
   310  			t,
   311  			ctx,
   312  			queryFrontend.Endpoint("http"),
   313  			e2ethanos.QueryUpWithoutInstance,
   314  			timestamp.FromTime(now.Add(-time.Hour)),
   315  			timestamp.FromTime(now.Add(24*time.Hour)),
   316  			14,
   317  			promclient.QueryOptions{
   318  				Deduplicate: true,
   319  			},
   320  			func(res model.Matrix) error {
   321  				if len(res) == 0 {
   322  					return errors.Errorf("expected some results, got nothing")
   323  				}
   324  				return nil
   325  			},
   326  		)
   327  
   328  		testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions(
   329  			e2emon.Equals(3),
   330  			[]string{"thanos_query_frontend_queries_total"},
   331  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "query_range"))),
   332  		)
   333  		testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(3), "cortex_cache_fetched_keys_total"))
   334  		testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(2), "cortex_cache_hits_total"))
   335  		testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "querier_cache_added_new_total"))
   336  		testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(3), "querier_cache_added_total"))
   337  		testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "querier_cache_entries"))
   338  		testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(3), "querier_cache_gets_total"))
   339  		testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "querier_cache_misses_total"))
   340  
   341  		// Query is 25h so it will be split to 2 requests.
   342  		testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions(
   343  			e2emon.Equals(4), []string{"thanos_frontend_split_queries_total"},
   344  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "tripperware", "query_range"))),
   345  		)
   346  
   347  		testutil.Ok(t, q.WaitSumMetricsWithOptions(
   348  			e2emon.Equals(4),
   349  			[]string{"http_requests_total"},
   350  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "handler", "query_range"))),
   351  		)
   352  	})
   353  
   354  	t.Run("query frontend splitting works for labels names API", func(t *testing.T) {
   355  		// LabelNames and LabelValues API should still work via query frontend.
   356  		labelNames(t, ctx, queryFrontend.Endpoint("http"), nil, timestamp.FromTime(now.Add(-time.Hour)), timestamp.FromTime(now.Add(time.Hour)), func(res []string) bool {
   357  			return len(res) > 0
   358  		})
   359  		testutil.Ok(t, q.WaitSumMetricsWithOptions(
   360  			e2emon.Equals(1),
   361  			[]string{"http_requests_total"},
   362  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "handler", "label_names"))),
   363  		)
   364  		testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions(
   365  			e2emon.Equals(1),
   366  			[]string{"thanos_query_frontend_queries_total"},
   367  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "label_names"))),
   368  		)
   369  		// Query is only 2h so it won't be split.
   370  		testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions(
   371  			e2emon.Equals(1), []string{"thanos_frontend_split_queries_total"},
   372  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "tripperware", "labels"))),
   373  		)
   374  
   375  		labelNames(t, ctx, queryFrontend.Endpoint("http"), nil, timestamp.FromTime(now.Add(-24*time.Hour)), timestamp.FromTime(now.Add(time.Hour)), func(res []string) bool {
   376  			return len(res) > 0
   377  		})
   378  		testutil.Ok(t, q.WaitSumMetricsWithOptions(
   379  			e2emon.Equals(3),
   380  			[]string{"http_requests_total"},
   381  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "handler", "label_names"))),
   382  		)
   383  		testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions(
   384  			e2emon.Equals(2),
   385  			[]string{"thanos_query_frontend_queries_total"},
   386  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "label_names"))),
   387  		)
   388  		// Query is 25h so split to 2 requests.
   389  		testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions(
   390  			e2emon.Equals(3), []string{"thanos_frontend_split_queries_total"},
   391  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "tripperware", "labels"))),
   392  		)
   393  	})
   394  
   395  	t.Run("query frontend splitting works for labels values API", func(t *testing.T) {
   396  		labelValues(t, ctx, queryFrontend.Endpoint("http"), "instance", nil, timestamp.FromTime(now.Add(-time.Hour)), timestamp.FromTime(now.Add(time.Hour)), func(res []string) bool {
   397  			return len(res) == 1 && res[0] == "localhost:9090"
   398  		})
   399  		testutil.Ok(t, q.WaitSumMetricsWithOptions(
   400  			e2emon.Equals(1),
   401  			[]string{"http_requests_total"},
   402  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "handler", "label_values"))),
   403  		)
   404  		testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions(
   405  			e2emon.Equals(1),
   406  			[]string{"thanos_query_frontend_queries_total"},
   407  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "label_values"))),
   408  		)
   409  		// Query is only 2h so it won't be split.
   410  		testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions(
   411  			e2emon.Equals(4), []string{"thanos_frontend_split_queries_total"},
   412  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "tripperware", "labels"))),
   413  		)
   414  
   415  		labelValues(t, ctx, queryFrontend.Endpoint("http"), "instance", nil, timestamp.FromTime(now.Add(-24*time.Hour)), timestamp.FromTime(now.Add(time.Hour)), func(res []string) bool {
   416  			return len(res) == 1 && res[0] == "localhost:9090"
   417  		})
   418  		testutil.Ok(t, q.WaitSumMetricsWithOptions(
   419  			e2emon.Equals(3),
   420  			[]string{"http_requests_total"},
   421  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "handler", "label_values"))),
   422  		)
   423  		testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions(
   424  			e2emon.Equals(2),
   425  			[]string{"thanos_query_frontend_queries_total"},
   426  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "label_values"))),
   427  		)
   428  		// Query is 25h so split to 2 requests.
   429  		testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions(
   430  			e2emon.Equals(6), []string{"thanos_frontend_split_queries_total"},
   431  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "tripperware", "labels"))),
   432  		)
   433  	})
   434  
   435  	t.Run("query frontend splitting works for series API", func(t *testing.T) {
   436  		series(
   437  			t,
   438  			ctx,
   439  			queryFrontend.Endpoint("http"),
   440  			[]*labels.Matcher{labels.MustNewMatcher(labels.MatchEqual, "__name__", "up")},
   441  			timestamp.FromTime(now.Add(-time.Hour)),
   442  			timestamp.FromTime(now.Add(time.Hour)),
   443  			func(res []map[string]string) bool {
   444  				if len(res) != 1 {
   445  					return false
   446  				}
   447  
   448  				return reflect.DeepEqual(res[0], map[string]string{
   449  					"__name__":   "up",
   450  					"instance":   "localhost:9090",
   451  					"job":        "myself",
   452  					"prometheus": "test",
   453  				})
   454  			},
   455  		)
   456  		testutil.Ok(t, q.WaitSumMetricsWithOptions(
   457  			e2emon.Equals(1),
   458  			[]string{"http_requests_total"},
   459  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "handler", "series"))),
   460  		)
   461  		testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions(
   462  			e2emon.Equals(1),
   463  			[]string{"thanos_query_frontend_queries_total"},
   464  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "series"))),
   465  		)
   466  		// Query is only 2h so it won't be split.
   467  		testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions(
   468  			e2emon.Equals(7), []string{"thanos_frontend_split_queries_total"},
   469  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "tripperware", "labels"))),
   470  		)
   471  
   472  		series(
   473  			t,
   474  			ctx,
   475  			queryFrontend.Endpoint("http"),
   476  			[]*labels.Matcher{labels.MustNewMatcher(labels.MatchEqual, "__name__", "up")},
   477  			timestamp.FromTime(now.Add(-24*time.Hour)),
   478  			timestamp.FromTime(now.Add(time.Hour)),
   479  			func(res []map[string]string) bool {
   480  				if len(res) != 1 {
   481  					return false
   482  				}
   483  
   484  				return reflect.DeepEqual(res[0], map[string]string{
   485  					"__name__":   "up",
   486  					"instance":   "localhost:9090",
   487  					"job":        "myself",
   488  					"prometheus": "test",
   489  				})
   490  			},
   491  		)
   492  		testutil.Ok(t, q.WaitSumMetricsWithOptions(
   493  			e2emon.Equals(3),
   494  			[]string{"http_requests_total"},
   495  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "handler", "series"))),
   496  		)
   497  		testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions(
   498  			e2emon.Equals(2),
   499  			[]string{"thanos_query_frontend_queries_total"},
   500  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "series"))),
   501  		)
   502  		// Query is only 2h so it won't be split.
   503  		testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions(
   504  			e2emon.Equals(9), []string{"thanos_frontend_split_queries_total"},
   505  			e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "tripperware", "labels"))),
   506  		)
   507  	})
   508  }
   509  
   510  func TestQueryFrontendMemcachedCache(t *testing.T) {
   511  	t.Parallel()
   512  
   513  	e, err := e2e.NewDockerEnvironment("qf-memcached")
   514  	testutil.Ok(t, err)
   515  	t.Cleanup(e2ethanos.CleanScenario(t, e))
   516  
   517  	now := time.Now()
   518  
   519  	prom, sidecar := e2ethanos.NewPrometheusWithSidecar(e, "1", e2ethanos.DefaultPromConfig("test", 0, "", "", e2ethanos.LocalPrometheusTarget), "", e2ethanos.DefaultPrometheusImage(), "")
   520  	testutil.Ok(t, e2e.StartAndWaitReady(prom, sidecar))
   521  
   522  	q := e2ethanos.NewQuerierBuilder(e, "1", sidecar.InternalEndpoint("grpc")).Init()
   523  	testutil.Ok(t, e2e.StartAndWaitReady(q))
   524  
   525  	memcached := e2ethanos.NewMemcached(e, "1")
   526  	testutil.Ok(t, e2e.StartAndWaitReady(memcached))
   527  
   528  	memCachedConfig := queryfrontend.CacheProviderConfig{
   529  		Type: queryfrontend.MEMCACHED,
   530  		Config: queryfrontend.MemcachedResponseCacheConfig{
   531  			Memcached: cacheutil.MemcachedClientConfig{
   532  				Addresses:                 []string{memcached.InternalEndpoint("memcached")},
   533  				MaxIdleConnections:        100,
   534  				MaxAsyncConcurrency:       20,
   535  				MaxGetMultiConcurrency:    100,
   536  				MaxGetMultiBatchSize:      0,
   537  				Timeout:                   time.Minute,
   538  				MaxAsyncBufferSize:        10000,
   539  				DNSProviderUpdateInterval: 10 * time.Second,
   540  			},
   541  		},
   542  	}
   543  
   544  	cfg := queryfrontend.Config{}
   545  	queryFrontend := e2ethanos.NewQueryFrontend(e, "1", "http://"+q.InternalEndpoint("http"), cfg, memCachedConfig)
   546  	testutil.Ok(t, e2e.StartAndWaitReady(queryFrontend))
   547  
   548  	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
   549  	t.Cleanup(cancel)
   550  
   551  	testutil.Ok(t, q.WaitSumMetricsWithOptions(e2emon.Equals(1), []string{"thanos_store_nodes_grpc_connections"}, e2emon.WaitMissingMetrics()))
   552  
   553  	testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "cortex_memcache_client_servers"))
   554  
   555  	// Ensure we can get the result from Querier first so that it
   556  	// doesn't need to retry when we send queries to the frontend later.
   557  	queryAndAssertSeries(t, ctx, q.Endpoint("http"), e2ethanos.QueryUpWithoutInstance, time.Now, promclient.QueryOptions{
   558  		Deduplicate: false,
   559  	}, []model.Metric{
   560  		{
   561  			"job":        "myself",
   562  			"prometheus": "test",
   563  			"replica":    "0",
   564  		},
   565  	})
   566  
   567  	vals, err := q.SumMetrics([]string{"http_requests_total"}, e2emon.WithLabelMatchers(
   568  		matchers.MustNewMatcher(matchers.MatchEqual, "handler", "query")))
   569  	testutil.Ok(t, err)
   570  	testutil.Equals(t, 1, len(vals))
   571  
   572  	rangeQuery(
   573  		t,
   574  		ctx,
   575  		queryFrontend.Endpoint("http"),
   576  		e2ethanos.QueryUpWithoutInstance,
   577  		timestamp.FromTime(now.Add(-time.Hour)),
   578  		timestamp.FromTime(now.Add(time.Hour)),
   579  		14,
   580  		promclient.QueryOptions{
   581  			Deduplicate: true,
   582  		},
   583  		func(res model.Matrix) error {
   584  			if len(res) == 0 {
   585  				return errors.Errorf("expected some results, got nothing")
   586  			}
   587  			return nil
   588  		},
   589  	)
   590  
   591  	testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions(
   592  		e2emon.Equals(1),
   593  		[]string{"thanos_query_frontend_queries_total"},
   594  		e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "query_range"))),
   595  	)
   596  
   597  	testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "cortex_cache_fetched_keys_total"))
   598  	testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(0), "cortex_cache_hits_total"))
   599  
   600  	// Query is only 2h so it won't be split.
   601  	testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "thanos_frontend_split_queries_total"))
   602  
   603  	// Run the same range query again, the result can be retrieved from cache directly.
   604  	rangeQuery(
   605  		t,
   606  		ctx,
   607  		queryFrontend.Endpoint("http"),
   608  		e2ethanos.QueryUpWithoutInstance,
   609  		timestamp.FromTime(now.Add(-time.Hour)),
   610  		timestamp.FromTime(now.Add(time.Hour)),
   611  		14,
   612  		promclient.QueryOptions{
   613  			Deduplicate: true,
   614  		},
   615  		func(res model.Matrix) error {
   616  			if len(res) == 0 {
   617  				return errors.Errorf("expected some results, got nothing")
   618  			}
   619  			return nil
   620  		},
   621  	)
   622  
   623  	testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions(
   624  		e2emon.Equals(2),
   625  		[]string{"thanos_query_frontend_queries_total"},
   626  		e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "query_range"))),
   627  	)
   628  
   629  	// Query is only 2h so it won't be split.
   630  	// If it was split this would be increase by more then 1.
   631  	testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(2), "thanos_frontend_split_queries_total"))
   632  
   633  	testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(2), "cortex_cache_fetched_keys_total"))
   634  	testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "cortex_cache_hits_total"))
   635  }
   636  
   637  func TestRangeQueryShardingWithRandomData(t *testing.T) {
   638  	t.Parallel()
   639  
   640  	e, err := e2e.NewDockerEnvironment("rq-sharding")
   641  	testutil.Ok(t, err)
   642  	t.Cleanup(e2ethanos.CleanScenario(t, e))
   643  
   644  	promConfig := e2ethanos.DefaultPromConfig("p1", 0, "", "")
   645  	prom, sidecar := e2ethanos.NewPrometheusWithSidecar(e, "p1", promConfig, "", e2ethanos.DefaultPrometheusImage(), "", "remote-write-receiver")
   646  
   647  	now := model.Now()
   648  	ctx := context.Background()
   649  	timeSeries := []labels.Labels{
   650  		{{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "1"}, {Name: "handler", Value: "/"}},
   651  		{{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "1"}, {Name: "handler", Value: "/metrics"}},
   652  		{{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "2"}, {Name: "handler", Value: "/"}},
   653  		{{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "2"}, {Name: "handler", Value: "/metrics"}},
   654  		{{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "3"}, {Name: "handler", Value: "/"}},
   655  		{{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "3"}, {Name: "handler", Value: "/metrics"}},
   656  		{{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "4"}, {Name: "handler", Value: "/"}},
   657  		{{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "4"}, {Name: "handler", Value: "/metrics"}},
   658  		{{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "5"}, {Name: "handler", Value: "/"}},
   659  		{{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "5"}, {Name: "handler", Value: "/metrics"}},
   660  		{{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "6"}, {Name: "handler", Value: "/"}},
   661  		{{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "6"}, {Name: "handler", Value: "/metrics"}},
   662  	}
   663  
   664  	startTime := now.Time().Add(-1 * time.Hour)
   665  	endTime := now.Time().Add(1 * time.Hour)
   666  	_, err = e2eutil.CreateBlock(ctx, prom.Dir(), timeSeries, 20, timestamp.FromTime(startTime), timestamp.FromTime(endTime), nil, 0, metadata.NoneFunc)
   667  	testutil.Ok(t, err)
   668  	testutil.Ok(t, e2e.StartAndWaitReady(prom, sidecar))
   669  
   670  	stores := []string{sidecar.InternalEndpoint("grpc")}
   671  	q1 := e2ethanos.NewQuerierBuilder(e, "q1", stores...).Init()
   672  	testutil.Ok(t, e2e.StartAndWaitReady(q1))
   673  
   674  	inMemoryCacheConfig := queryfrontend.CacheProviderConfig{
   675  		Type: queryfrontend.INMEMORY,
   676  		Config: queryfrontend.InMemoryResponseCacheConfig{
   677  			MaxSizeItems: 1000,
   678  			Validity:     time.Hour,
   679  		},
   680  	}
   681  	config := queryfrontend.Config{
   682  		QueryRangeConfig: queryfrontend.QueryRangeConfig{
   683  			AlignRangeWithStep: false,
   684  		},
   685  		NumShards: 2,
   686  	}
   687  	qfe := e2ethanos.NewQueryFrontend(e, "query-frontend", "http://"+q1.InternalEndpoint("http"), config, inMemoryCacheConfig)
   688  	testutil.Ok(t, e2e.StartAndWaitReady(qfe))
   689  
   690  	qryFunc := func() string { return `sum by (pod) (http_requests_total)` }
   691  	queryOpts := promclient.QueryOptions{Deduplicate: true}
   692  
   693  	var resultWithoutSharding model.Matrix
   694  	rangeQuery(t, ctx, q1.Endpoint("http"), qryFunc, timestamp.FromTime(startTime), timestamp.FromTime(endTime), 30, queryOpts, func(res model.Matrix) error {
   695  		resultWithoutSharding = res
   696  		return nil
   697  	})
   698  	var resultWithSharding model.Matrix
   699  	rangeQuery(t, ctx, qfe.Endpoint("http"), qryFunc, timestamp.FromTime(startTime), timestamp.FromTime(endTime), 30, queryOpts, func(res model.Matrix) error {
   700  		resultWithSharding = res
   701  		return nil
   702  	})
   703  
   704  	testutil.Equals(t, resultWithoutSharding, resultWithSharding)
   705  }
   706  
   707  func TestRangeQueryDynamicHorizontalSharding(t *testing.T) {
   708  	t.Parallel()
   709  
   710  	e, err := e2e.New(e2e.WithName("qfe-dyn-sharding"))
   711  	testutil.Ok(t, err)
   712  	t.Cleanup(e2ethanos.CleanScenario(t, e))
   713  
   714  	now := time.Now()
   715  
   716  	prom, sidecar := e2ethanos.NewPrometheusWithSidecar(e, "1", e2ethanos.DefaultPromConfig("test", 0, "", "", e2ethanos.LocalPrometheusTarget), "", e2ethanos.DefaultPrometheusImage(), "")
   717  	testutil.Ok(t, e2e.StartAndWaitReady(prom, sidecar))
   718  
   719  	querier := e2ethanos.NewQuerierBuilder(e, "1", sidecar.InternalEndpoint("grpc")).Init()
   720  	testutil.Ok(t, e2e.StartAndWaitReady(querier))
   721  
   722  	inMemoryCacheConfig := queryfrontend.CacheProviderConfig{
   723  		Type: queryfrontend.INMEMORY,
   724  		Config: queryfrontend.InMemoryResponseCacheConfig{
   725  			MaxSizeItems: 1000,
   726  			Validity:     time.Hour,
   727  		},
   728  	}
   729  
   730  	cfg := queryfrontend.Config{
   731  		QueryRangeConfig: queryfrontend.QueryRangeConfig{
   732  			MinQuerySplitInterval:  time.Hour,
   733  			MaxQuerySplitInterval:  12 * time.Hour,
   734  			HorizontalShards:       4,
   735  			SplitQueriesByInterval: 0,
   736  		},
   737  	}
   738  	queryFrontend := e2ethanos.NewQueryFrontend(e, "1", "http://"+querier.InternalEndpoint("http"), cfg, inMemoryCacheConfig)
   739  	testutil.Ok(t, e2e.StartAndWaitReady(queryFrontend))
   740  
   741  	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
   742  	t.Cleanup(cancel)
   743  
   744  	testutil.Ok(t, querier.WaitSumMetricsWithOptions(e2emon.Equals(1), []string{"thanos_store_nodes_grpc_connections"}, e2emon.WaitMissingMetrics()))
   745  
   746  	// Ensure we can get the result from Querier first so that it
   747  	// doesn't need to retry when we send queries to the frontend later.
   748  	queryAndAssertSeries(t, ctx, querier.Endpoint("http"), e2ethanos.QueryUpWithoutInstance, time.Now, promclient.QueryOptions{
   749  		Deduplicate: false,
   750  	}, []model.Metric{
   751  		{
   752  			"job":        "myself",
   753  			"prometheus": "test",
   754  			"replica":    "0",
   755  		},
   756  	})
   757  
   758  	// -- test starts here --
   759  	rangeQuery(
   760  		t,
   761  		ctx,
   762  		queryFrontend.Endpoint("http"),
   763  		e2ethanos.QueryUpWithoutInstance,
   764  		timestamp.FromTime(now.Add(-time.Hour)),
   765  		timestamp.FromTime(now.Add(time.Hour)),
   766  		14,
   767  		promclient.QueryOptions{
   768  			Deduplicate: true,
   769  		},
   770  		func(res model.Matrix) error {
   771  			if len(res) == 0 {
   772  				return errors.Errorf("expected some results, got nothing")
   773  			}
   774  			return nil
   775  		},
   776  	)
   777  
   778  	testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions(
   779  		e2emon.Equals(1),
   780  		[]string{"thanos_query_frontend_queries_total"},
   781  		e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "query_range")),
   782  	))
   783  
   784  	// make sure that we don't break cortex cache code.
   785  	testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(3), "cortex_cache_fetched_keys_total"))
   786  	testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(0), "cortex_cache_hits_total"))
   787  	testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(2), "querier_cache_added_new_total"))
   788  	testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(3), "querier_cache_added_total"))
   789  	testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(3), "querier_cache_misses_total"))
   790  
   791  	// Query interval is 2 hours, which is greater than min-slit-interval, query will be broken down into 4 parts
   792  	// + rest (of interval)
   793  	testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(5), "thanos_frontend_split_queries_total"))
   794  
   795  	testutil.Ok(t, querier.WaitSumMetricsWithOptions(
   796  		e2emon.Equals(5),
   797  		[]string{"http_requests_total"},
   798  		e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "handler", "query_range")),
   799  	))
   800  }
   801  
   802  func TestInstantQueryShardingWithRandomData(t *testing.T) {
   803  	t.Parallel()
   804  
   805  	e, err := e2e.NewDockerEnvironment("query-sharding")
   806  	testutil.Ok(t, err)
   807  	t.Cleanup(e2ethanos.CleanScenario(t, e))
   808  
   809  	promConfig := e2ethanos.DefaultPromConfig("p1", 0, "", "")
   810  	prom, sidecar := e2ethanos.NewPrometheusWithSidecar(e, "p1", promConfig, "", e2ethanos.DefaultPrometheusImage(), "", "remote-write-receiver")
   811  
   812  	now := model.Now()
   813  	ctx := context.Background()
   814  	timeSeries := []labels.Labels{
   815  		{{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "1"}, {Name: "handler", Value: "/"}},
   816  		{{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "1"}, {Name: "handler", Value: "/metrics"}},
   817  		{{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "2"}, {Name: "handler", Value: "/"}},
   818  		{{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "2"}, {Name: "handler", Value: "/metrics"}},
   819  		{{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "3"}, {Name: "handler", Value: "/"}},
   820  		{{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "3"}, {Name: "handler", Value: "/metrics"}},
   821  		{{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "4"}, {Name: "handler", Value: "/"}},
   822  		{{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "4"}, {Name: "handler", Value: "/metrics"}},
   823  		{{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "5"}, {Name: "handler", Value: "/"}},
   824  		{{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "5"}, {Name: "handler", Value: "/metrics"}},
   825  		{{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "6"}, {Name: "handler", Value: "/"}},
   826  		{{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "6"}, {Name: "handler", Value: "/metrics"}},
   827  	}
   828  
   829  	// Ensure labels are ordered.
   830  	for _, ts := range timeSeries {
   831  		sort.Slice(ts, func(i, j int) bool {
   832  			return ts[i].Name < ts[j].Name
   833  		})
   834  	}
   835  
   836  	startTime := now.Time().Add(-1 * time.Hour)
   837  	endTime := now.Time().Add(1 * time.Hour)
   838  	_, err = e2eutil.CreateBlock(ctx, prom.Dir(), timeSeries, 20, timestamp.FromTime(startTime), timestamp.FromTime(endTime), nil, 0, metadata.NoneFunc)
   839  	testutil.Ok(t, err)
   840  	testutil.Ok(t, e2e.StartAndWaitReady(prom, sidecar))
   841  
   842  	stores := []string{sidecar.InternalEndpoint("grpc")}
   843  	q1 := e2ethanos.NewQuerierBuilder(e, "q1", stores...).Init()
   844  	testutil.Ok(t, e2e.StartAndWaitReady(q1))
   845  
   846  	inMemoryCacheConfig := queryfrontend.CacheProviderConfig{
   847  		Type: queryfrontend.INMEMORY,
   848  		Config: queryfrontend.InMemoryResponseCacheConfig{
   849  			MaxSizeItems: 1000,
   850  			Validity:     time.Hour,
   851  		},
   852  	}
   853  	config := queryfrontend.Config{
   854  		QueryRangeConfig: queryfrontend.QueryRangeConfig{
   855  			AlignRangeWithStep: false,
   856  		},
   857  		NumShards: 2,
   858  	}
   859  	qfe := e2ethanos.NewQueryFrontend(e, "query-frontend", "http://"+q1.InternalEndpoint("http"), config, inMemoryCacheConfig)
   860  	testutil.Ok(t, e2e.StartAndWaitReady(qfe))
   861  
   862  	queryOpts := promclient.QueryOptions{Deduplicate: true}
   863  	for _, tc := range []struct {
   864  		name           string
   865  		qryFunc        func() string
   866  		expectedSeries int
   867  	}{
   868  		{
   869  			name:           "aggregation",
   870  			qryFunc:        func() string { return `sum(http_requests_total)` },
   871  			expectedSeries: 1,
   872  		},
   873  		{
   874  			name:           "outer aggregation with no grouping",
   875  			qryFunc:        func() string { return `count(sum by (pod) (http_requests_total))` },
   876  			expectedSeries: 1,
   877  		},
   878  		{
   879  			name:           "scalar",
   880  			qryFunc:        func() string { return `1 + 1` },
   881  			expectedSeries: 1,
   882  		},
   883  		{
   884  			name:           "binary expression",
   885  			qryFunc:        func() string { return `http_requests_total{pod="1"} / http_requests_total` },
   886  			expectedSeries: 2,
   887  		},
   888  		{
   889  			name:           "binary expression with constant",
   890  			qryFunc:        func() string { return `http_requests_total / 2` },
   891  			expectedSeries: 12,
   892  		},
   893  		{
   894  			name:           "vector selector",
   895  			qryFunc:        func() string { return `http_requests_total` },
   896  			expectedSeries: 12,
   897  		},
   898  		{
   899  			name:           "aggregation with grouping",
   900  			qryFunc:        func() string { return `sum by (pod) (http_requests_total)` },
   901  			expectedSeries: 6,
   902  		},
   903  		{
   904  			name:           "aggregate without grouping",
   905  			qryFunc:        func() string { return `sum without (pod) (http_requests_total)` },
   906  			expectedSeries: 2,
   907  		},
   908  		{
   909  			name:           "multiple aggregations with grouping",
   910  			qryFunc:        func() string { return `max by (handler) (sum(http_requests_total) by (pod, handler))` },
   911  			expectedSeries: 2,
   912  		},
   913  	} {
   914  		t.Run(tc.name, func(t *testing.T) {
   915  			resultWithoutSharding, _ := instantQuery(t, ctx, q1.Endpoint("http"), tc.qryFunc, func() time.Time {
   916  				return now.Time()
   917  			}, queryOpts, tc.expectedSeries)
   918  			resultWithSharding, _ := instantQuery(t, ctx, qfe.Endpoint("http"), tc.qryFunc, func() time.Time {
   919  				return now.Time()
   920  			}, queryOpts, tc.expectedSeries)
   921  			testutil.Equals(t, resultWithoutSharding, resultWithSharding)
   922  		})
   923  	}
   924  }