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

     1  package queryrange
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"math"
     8  	"sort"
     9  	"sync"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/go-kit/log"
    14  	"github.com/stretchr/testify/require"
    15  	"github.com/weaveworks/common/user"
    16  
    17  	"github.com/grafana/loki/pkg/loghttp"
    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/storage/config"
    22  	"github.com/grafana/loki/pkg/util"
    23  )
    24  
    25  var (
    26  	nilShardingMetrics = logql.NewShardMapperMetrics(nil)
    27  	defaultReq         = func() *LokiRequest {
    28  		return &LokiRequest{
    29  			Limit:     100,
    30  			StartTs:   start,
    31  			EndTs:     end,
    32  			Direction: logproto.BACKWARD,
    33  			Path:      "/loki/api/v1/query_range",
    34  		}
    35  	}
    36  	lokiResps = []queryrangebase.Response{
    37  		&LokiResponse{
    38  			Status:    loghttp.QueryStatusSuccess,
    39  			Direction: logproto.BACKWARD,
    40  			Limit:     defaultReq().Limit,
    41  			Version:   1,
    42  			Data: LokiData{
    43  				ResultType: loghttp.ResultTypeStream,
    44  				Result: []logproto.Stream{
    45  					{
    46  						Labels: `{foo="bar", level="debug"}`,
    47  						Entries: []logproto.Entry{
    48  							{Timestamp: time.Unix(0, 6), Line: "6"},
    49  							{Timestamp: time.Unix(0, 5), Line: "5"},
    50  						},
    51  					},
    52  				},
    53  			},
    54  		},
    55  		&LokiResponse{
    56  			Status:    loghttp.QueryStatusSuccess,
    57  			Direction: logproto.BACKWARD,
    58  			Limit:     100,
    59  			Version:   1,
    60  			Data: LokiData{
    61  				ResultType: loghttp.ResultTypeStream,
    62  				Result: []logproto.Stream{
    63  					{
    64  						Labels: `{foo="bar", level="error"}`,
    65  						Entries: []logproto.Entry{
    66  							{Timestamp: time.Unix(0, 2), Line: "2"},
    67  							{Timestamp: time.Unix(0, 1), Line: "1"},
    68  						},
    69  					},
    70  				},
    71  			},
    72  		},
    73  	}
    74  )
    75  
    76  func Test_shardSplitter(t *testing.T) {
    77  	req := defaultReq().WithStartEnd(
    78  		util.TimeToMillis(start),
    79  		util.TimeToMillis(end),
    80  	)
    81  
    82  	for _, tc := range []struct {
    83  		desc        string
    84  		lookback    time.Duration
    85  		shouldShard bool
    86  	}{
    87  		{
    88  			desc:        "older than lookback",
    89  			lookback:    -time.Minute, // a negative lookback will ensure the entire query doesn't cross the sharding boundary & can safely be sharded.
    90  			shouldShard: true,
    91  		},
    92  		{
    93  			desc:        "overlaps lookback",
    94  			lookback:    end.Sub(start) / 2, // intersect the request causing it to avoid sharding
    95  			shouldShard: false,
    96  		},
    97  		{
    98  			desc:        "newer than lookback",
    99  			lookback:    end.Sub(start) + 1, // the entire query is in the ingester range and should avoid sharding.
   100  			shouldShard: false,
   101  		},
   102  		{
   103  			desc:        "default",
   104  			lookback:    0,
   105  			shouldShard: true,
   106  		},
   107  	} {
   108  		t.Run(tc.desc, func(t *testing.T) {
   109  			var didShard bool
   110  			splitter := &shardSplitter{
   111  				shardingware: queryrangebase.HandlerFunc(func(ctx context.Context, req queryrangebase.Request) (queryrangebase.Response, error) {
   112  					didShard = true
   113  					return mockHandler(lokiResps[0], nil).Do(ctx, req)
   114  				}),
   115  				next: mockHandler(lokiResps[1], nil),
   116  				now:  func() time.Time { return end },
   117  				limits: fakeLimits{
   118  					minShardingLookback: tc.lookback,
   119  					maxQueryParallelism: 1,
   120  				},
   121  			}
   122  
   123  			resp, err := splitter.Do(user.InjectOrgID(context.Background(), "1"), req)
   124  			require.Nil(t, err)
   125  
   126  			require.Equal(t, tc.shouldShard, didShard)
   127  			require.Nil(t, err)
   128  
   129  			if tc.shouldShard {
   130  				require.Equal(t, lokiResps[0], resp)
   131  			} else {
   132  				require.Equal(t, lokiResps[1], resp)
   133  			}
   134  		})
   135  	}
   136  }
   137  
   138  func Test_astMapper(t *testing.T) {
   139  	var lock sync.Mutex
   140  	called := 0
   141  
   142  	handler := queryrangebase.HandlerFunc(func(ctx context.Context, req queryrangebase.Request) (queryrangebase.Response, error) {
   143  		lock.Lock()
   144  		defer lock.Unlock()
   145  		resp := lokiResps[called]
   146  		called++
   147  		return resp, nil
   148  	})
   149  
   150  	mware := newASTMapperware(
   151  		ShardingConfigs{
   152  			config.PeriodConfig{
   153  				RowShards: 2,
   154  			},
   155  		},
   156  		handler,
   157  		log.NewNopLogger(),
   158  		nilShardingMetrics,
   159  		fakeLimits{maxSeries: math.MaxInt32, maxQueryParallelism: 1},
   160  	)
   161  
   162  	resp, err := mware.Do(user.InjectOrgID(context.Background(), "1"), defaultReq().WithQuery(`{food="bar"}`))
   163  	require.Nil(t, err)
   164  
   165  	expected, err := LokiCodec.MergeResponse(lokiResps...)
   166  	sort.Sort(logproto.Streams(expected.(*LokiResponse).Data.Result))
   167  	require.Nil(t, err)
   168  	require.Equal(t, called, 2)
   169  	require.Equal(t, expected.(*LokiResponse).Data, resp.(*LokiResponse).Data)
   170  }
   171  
   172  func Test_ShardingByPass(t *testing.T) {
   173  	called := 0
   174  	handler := queryrangebase.HandlerFunc(func(ctx context.Context, req queryrangebase.Request) (queryrangebase.Response, error) {
   175  		called++
   176  		return nil, nil
   177  	})
   178  
   179  	mware := newASTMapperware(
   180  		ShardingConfigs{
   181  			config.PeriodConfig{
   182  				RowShards: 2,
   183  			},
   184  		},
   185  		handler,
   186  		log.NewNopLogger(),
   187  		nilShardingMetrics,
   188  		fakeLimits{maxSeries: math.MaxInt32, maxQueryParallelism: 1},
   189  	)
   190  
   191  	_, err := mware.Do(user.InjectOrgID(context.Background(), "1"), defaultReq().WithQuery(`1+1`))
   192  	require.Nil(t, err)
   193  	require.Equal(t, called, 1)
   194  }
   195  
   196  func Test_hasShards(t *testing.T) {
   197  	for i, tc := range []struct {
   198  		input    ShardingConfigs
   199  		expected bool
   200  	}{
   201  		{
   202  			input: ShardingConfigs{
   203  				{},
   204  			},
   205  			expected: false,
   206  		},
   207  		{
   208  			input: ShardingConfigs{
   209  				{RowShards: 16},
   210  			},
   211  			expected: true,
   212  		},
   213  		{
   214  			input: ShardingConfigs{
   215  				{},
   216  				{RowShards: 16},
   217  				{},
   218  			},
   219  			expected: true,
   220  		},
   221  		{
   222  			input:    nil,
   223  			expected: false,
   224  		},
   225  	} {
   226  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
   227  			require.Equal(t, tc.expected, hasShards(tc.input))
   228  		})
   229  	}
   230  }
   231  
   232  // astmapper successful stream & prom conversion
   233  
   234  func mockHandler(resp queryrangebase.Response, err error) queryrangebase.Handler {
   235  	return queryrangebase.HandlerFunc(func(ctx context.Context, req queryrangebase.Request) (queryrangebase.Response, error) {
   236  		if expired := ctx.Err(); expired != nil {
   237  			return nil, expired
   238  		}
   239  
   240  		return resp, err
   241  	})
   242  }
   243  
   244  func Test_InstantSharding(t *testing.T) {
   245  	ctx := user.InjectOrgID(context.Background(), "1")
   246  
   247  	var lock sync.Mutex
   248  	called := 0
   249  	shards := []string{}
   250  
   251  	sharding := NewQueryShardMiddleware(log.NewNopLogger(), ShardingConfigs{
   252  		config.PeriodConfig{
   253  			RowShards: 3,
   254  		},
   255  	}, queryrangebase.NewInstrumentMiddlewareMetrics(nil),
   256  		nilShardingMetrics,
   257  		fakeLimits{
   258  			maxSeries:           math.MaxInt32,
   259  			maxQueryParallelism: 10,
   260  		})
   261  	response, err := sharding.Wrap(queryrangebase.HandlerFunc(func(c context.Context, r queryrangebase.Request) (queryrangebase.Response, error) {
   262  		lock.Lock()
   263  		defer lock.Unlock()
   264  		called++
   265  		shards = append(shards, r.(*LokiInstantRequest).Shards...)
   266  		return &LokiPromResponse{Response: &queryrangebase.PrometheusResponse{
   267  			Data: queryrangebase.PrometheusData{
   268  				ResultType: loghttp.ResultTypeVector,
   269  				Result: []queryrangebase.SampleStream{
   270  					{
   271  						Labels:  []logproto.LabelAdapter{{Name: "foo", Value: "bar"}},
   272  						Samples: []logproto.LegacySample{{Value: 10, TimestampMs: 10}},
   273  					},
   274  				},
   275  			},
   276  		}}, nil
   277  	})).Do(ctx, &LokiInstantRequest{
   278  		Query:  `rate({app="foo"}[1m])`,
   279  		TimeTs: util.TimeFromMillis(10),
   280  		Path:   "/v1/query",
   281  	})
   282  	require.NoError(t, err)
   283  	require.Equal(t, 3, called, "expected 3 calls but got {}", called)
   284  	require.Len(t, response.(*LokiPromResponse).Response.Data.Result, 3)
   285  	require.ElementsMatch(t, []string{"0_of_3", "1_of_3", "2_of_3"}, shards)
   286  	require.Equal(t, queryrangebase.PrometheusData{
   287  		ResultType: loghttp.ResultTypeVector,
   288  		Result: []queryrangebase.SampleStream{
   289  			{
   290  				Labels:  []logproto.LabelAdapter{{Name: "foo", Value: "bar"}},
   291  				Samples: []logproto.LegacySample{{Value: 10, TimestampMs: 10}},
   292  			},
   293  			{
   294  				Labels:  []logproto.LabelAdapter{{Name: "foo", Value: "bar"}},
   295  				Samples: []logproto.LegacySample{{Value: 10, TimestampMs: 10}},
   296  			},
   297  			{
   298  				Labels:  []logproto.LabelAdapter{{Name: "foo", Value: "bar"}},
   299  				Samples: []logproto.LegacySample{{Value: 10, TimestampMs: 10}},
   300  			},
   301  		},
   302  	}, response.(*LokiPromResponse).Response.Data)
   303  	require.Equal(t, loghttp.QueryStatusSuccess, response.(*LokiPromResponse).Response.Status)
   304  }
   305  
   306  func Test_SeriesShardingHandler(t *testing.T) {
   307  	sharding := NewSeriesQueryShardMiddleware(log.NewNopLogger(), ShardingConfigs{
   308  		config.PeriodConfig{
   309  			RowShards: 3,
   310  		},
   311  	},
   312  		queryrangebase.NewInstrumentMiddlewareMetrics(nil),
   313  		nilShardingMetrics,
   314  		fakeLimits{
   315  			maxQueryParallelism: 10,
   316  		},
   317  		LokiCodec,
   318  	)
   319  	ctx := user.InjectOrgID(context.Background(), "1")
   320  
   321  	response, err := sharding.Wrap(queryrangebase.HandlerFunc(func(c context.Context, r queryrangebase.Request) (queryrangebase.Response, error) {
   322  		req, ok := r.(*LokiSeriesRequest)
   323  		if !ok {
   324  			return nil, errors.New("not a series call")
   325  		}
   326  		return &LokiSeriesResponse{
   327  			Status:  "success",
   328  			Version: 1,
   329  			Data: []logproto.SeriesIdentifier{
   330  				{
   331  					Labels: map[string]string{
   332  						"foo": "bar",
   333  					},
   334  				},
   335  				{
   336  					Labels: map[string]string{
   337  						"shard": req.Shards[0],
   338  					},
   339  				},
   340  			},
   341  		}, nil
   342  	})).Do(ctx, &LokiSeriesRequest{
   343  		Match:   []string{"foo", "bar"},
   344  		StartTs: time.Unix(0, 1),
   345  		EndTs:   time.Unix(0, 10),
   346  		Path:    "foo",
   347  	})
   348  
   349  	expected := &LokiSeriesResponse{
   350  		Status:  "success",
   351  		Version: 1,
   352  		Data: []logproto.SeriesIdentifier{
   353  			{
   354  				Labels: map[string]string{
   355  					"foo": "bar",
   356  				},
   357  			},
   358  			{
   359  				Labels: map[string]string{
   360  					"shard": "0_of_3",
   361  				},
   362  			},
   363  			{
   364  				Labels: map[string]string{
   365  					"shard": "1_of_3",
   366  				},
   367  			},
   368  			{
   369  				Labels: map[string]string{
   370  					"shard": "2_of_3",
   371  				},
   372  			},
   373  		},
   374  	}
   375  	sort.Slice(expected.Data, func(i, j int) bool {
   376  		return expected.Data[i].Labels["shard"] > expected.Data[j].Labels["shard"]
   377  	})
   378  	actual := response.(*LokiSeriesResponse)
   379  	sort.Slice(actual.Data, func(i, j int) bool {
   380  		return actual.Data[i].Labels["shard"] > actual.Data[j].Labels["shard"]
   381  	})
   382  	require.NoError(t, err)
   383  	require.Equal(t, expected, actual)
   384  }