github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/storage/m3/consolidators/multi_fetch_result_test.go (about)

     1  // Copyright (c) 2019 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package consolidators
    22  
    23  import (
    24  	"fmt"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/m3db/m3/src/dbnode/encoding"
    29  	"github.com/m3db/m3/src/query/block"
    30  	"github.com/m3db/m3/src/query/models"
    31  	"github.com/m3db/m3/src/query/storage/m3/storagemetadata"
    32  	"github.com/m3db/m3/src/x/ident"
    33  	xtest "github.com/m3db/m3/src/x/test"
    34  	xtime "github.com/m3db/m3/src/x/time"
    35  
    36  	"github.com/golang/mock/gomock"
    37  	"github.com/stretchr/testify/assert"
    38  	"github.com/stretchr/testify/require"
    39  )
    40  
    41  var defaultTestOpts = MatchOptions{
    42  	MatchType: defaultMatchType,
    43  }
    44  
    45  const (
    46  	common       = "common"
    47  	short        = "short"
    48  	long         = "long"
    49  	unaggregated = "unaggregated"
    50  )
    51  
    52  // NB: Each seriesIterators has two seriesIterator; one with a constant ID which
    53  // will be overwritten as necessary by multi_fetch_result, and one per namespace
    54  // which should not be overwritten and should appear in the results.
    55  func generateSeriesIterators(ctrl *gomock.Controller, ns string) encoding.SeriesIterators {
    56  	var (
    57  		end   = xtime.Now().Truncate(time.Hour)
    58  		start = end.Add(-24 * time.Hour)
    59  	)
    60  
    61  	iter := encoding.NewMockSeriesIterator(ctrl)
    62  	iter.EXPECT().ID().Return(ident.StringID(common)).MinTimes(1)
    63  	iter.EXPECT().Namespace().Return(ident.StringID(ns)).AnyTimes()
    64  	iter.EXPECT().Tags().Return(ident.EmptyTagIterator).AnyTimes()
    65  	iter.EXPECT().Start().Return(start).AnyTimes()
    66  	iter.EXPECT().End().Return(end).AnyTimes()
    67  	iter.EXPECT().Close()
    68  
    69  	unique := encoding.NewMockSeriesIterator(ctrl)
    70  	unique.EXPECT().ID().Return(ident.StringID(ns)).MinTimes(1)
    71  	unique.EXPECT().Namespace().Return(ident.StringID(ns)).AnyTimes()
    72  	unique.EXPECT().Tags().Return(ident.EmptyTagIterator).AnyTimes()
    73  	unique.EXPECT().Start().Return(start).AnyTimes()
    74  	unique.EXPECT().End().Return(end).AnyTimes()
    75  	unique.EXPECT().Close()
    76  
    77  	return encoding.NewSeriesIterators([]encoding.SeriesIterator{iter, unique})
    78  }
    79  
    80  func generateUnreadSeriesIterators(ctrl *gomock.Controller, ns string) encoding.SeriesIterators {
    81  	iter := encoding.NewMockSeriesIterator(ctrl)
    82  	iter.EXPECT().Namespace().Return(ident.StringID(ns)).AnyTimes()
    83  	iter.EXPECT().Close()
    84  
    85  	unique := encoding.NewMockSeriesIterator(ctrl)
    86  	unique.EXPECT().Namespace().Return(ident.StringID(ns)).AnyTimes()
    87  	unique.EXPECT().Close()
    88  
    89  	return encoding.NewSeriesIterators([]encoding.SeriesIterator{iter, unique})
    90  }
    91  
    92  var namespaces = []struct {
    93  	attrs storagemetadata.Attributes
    94  	ns    string
    95  }{
    96  	{
    97  		attrs: storagemetadata.Attributes{
    98  			MetricsType: storagemetadata.UnaggregatedMetricsType,
    99  			Retention:   24 * time.Hour,
   100  			Resolution:  0 * time.Minute,
   101  		},
   102  		ns: unaggregated,
   103  	},
   104  	{
   105  		attrs: storagemetadata.Attributes{
   106  			MetricsType: storagemetadata.AggregatedMetricsType,
   107  			Retention:   360 * time.Hour,
   108  			Resolution:  2 * time.Minute,
   109  		},
   110  		ns: short,
   111  	},
   112  	{
   113  		attrs: storagemetadata.Attributes{
   114  			MetricsType: storagemetadata.AggregatedMetricsType,
   115  			Retention:   17520 * time.Hour,
   116  			Resolution:  10 * time.Minute,
   117  		},
   118  		ns: long,
   119  	},
   120  }
   121  
   122  func TestMultiResultPartialQueryRange(t *testing.T) {
   123  	testMultiResult(t, NamespaceCoversPartialQueryRange, long)
   124  }
   125  
   126  func TestMultiResultAllQueryRange(t *testing.T) {
   127  	testMultiResult(t, NamespaceCoversAllQueryRange, unaggregated)
   128  }
   129  
   130  func testMultiResult(t *testing.T, fanoutType QueryFanoutType, expected string) {
   131  	ctrl := xtest.NewController(t)
   132  
   133  	r := NewMultiFetchResult(fanoutType,
   134  		defaultTestOpts, models.NewTagOptions(), LimitOptions{Limit: 1000})
   135  
   136  	meta := block.NewResultMetadata()
   137  	meta.FetchedSeriesCount = 4
   138  	for _, ns := range namespaces {
   139  		iters := generateSeriesIterators(ctrl, ns.ns)
   140  		res := MultiFetchResults{
   141  			SeriesIterators: iters,
   142  			Metadata:        meta,
   143  			Attrs:           ns.attrs,
   144  			Err:             nil,
   145  		}
   146  		r.Add(res)
   147  	}
   148  
   149  	result, err := r.FinalResult()
   150  	assert.NoError(t, err)
   151  
   152  	assert.True(t, result.Metadata.Exhaustive)
   153  	assert.True(t, result.Metadata.LocalOnly)
   154  	assert.Equal(t, 0, len(result.Metadata.Warnings))
   155  
   156  	iters := result.seriesData.seriesIterators
   157  	assert.Equal(t, 4, iters.Len())
   158  	assert.Equal(t, 4, len(iters.Iters()))
   159  
   160  	for _, n := range iters.Iters() {
   161  		id := n.ID().String()
   162  		// NB: if this is the common id, check against expected for the fanout type.
   163  		if id == common {
   164  			assert.Equal(t, expected, n.Namespace().String())
   165  		} else {
   166  			assert.Equal(t, id, n.Namespace().String())
   167  		}
   168  	}
   169  
   170  	assert.NoError(t, r.Close())
   171  }
   172  
   173  func TestLimit(t *testing.T) {
   174  	ctrl := xtest.NewController(t)
   175  	defer ctrl.Finish()
   176  
   177  	r := NewMultiFetchResult(NamespaceCoversPartialQueryRange,
   178  		defaultTestOpts, models.NewTagOptions(), LimitOptions{
   179  			Limit:             2,
   180  			RequireExhaustive: false,
   181  		})
   182  
   183  	meta := block.NewResultMetadata()
   184  	for _, ns := range namespaces[0:2] {
   185  		iters := generateSeriesIterators(ctrl, ns.ns)
   186  		res := MultiFetchResults{
   187  			SeriesIterators: iters,
   188  			Metadata:        meta,
   189  			Attrs:           ns.attrs,
   190  			Err:             nil,
   191  		}
   192  		r.Add(res)
   193  	}
   194  	longNs := namespaces[2]
   195  	res := MultiFetchResults{
   196  		SeriesIterators: generateUnreadSeriesIterators(ctrl, longNs.ns),
   197  		Metadata:        meta,
   198  		Attrs:           longNs.attrs,
   199  		Err:             nil,
   200  	}
   201  	r.Add(res)
   202  
   203  	result, err := r.FinalResult()
   204  	assert.NoError(t, err)
   205  	assert.False(t, result.Metadata.Exhaustive)
   206  	assert.True(t, result.Metadata.LocalOnly)
   207  	assert.Equal(t, 2, result.Metadata.FetchedSeriesCount)
   208  	assert.Equal(t, 0, len(result.Metadata.Warnings))
   209  
   210  	iters := result.seriesData.seriesIterators
   211  	assert.Equal(t, 2, iters.Len())
   212  	assert.Equal(t, 2, len(iters.Iters()))
   213  
   214  	for _, iter := range iters.Iters() {
   215  		ns := iter.Namespace().String()
   216  		if ns != short {
   217  			assert.Equal(t, iter.ID().String(), ns)
   218  		}
   219  	}
   220  	assert.NoError(t, r.Close())
   221  }
   222  
   223  func TestLimitRequireExhaustive(t *testing.T) {
   224  	ctrl := xtest.NewController(t)
   225  	defer ctrl.Finish()
   226  
   227  	r := NewMultiFetchResult(NamespaceCoversPartialQueryRange,
   228  		defaultTestOpts, models.NewTagOptions(), LimitOptions{
   229  			Limit:             2,
   230  			RequireExhaustive: true,
   231  		})
   232  
   233  	meta := block.NewResultMetadata()
   234  	for _, ns := range namespaces[0:2] {
   235  		iters := generateSeriesIterators(ctrl, ns.ns)
   236  		res := MultiFetchResults{
   237  			SeriesIterators: iters,
   238  			Metadata:        meta,
   239  			Attrs:           ns.attrs,
   240  			Err:             nil,
   241  		}
   242  		r.Add(res)
   243  	}
   244  	longNs := namespaces[2]
   245  	res := MultiFetchResults{
   246  		SeriesIterators: generateUnreadSeriesIterators(ctrl, longNs.ns),
   247  		Metadata:        meta,
   248  		Attrs:           longNs.attrs,
   249  		Err:             nil,
   250  	}
   251  	r.Add(res)
   252  
   253  	_, err := r.FinalResult()
   254  	require.Error(t, err)
   255  	assert.NoError(t, r.Close())
   256  }
   257  
   258  var exhaustTests = []struct {
   259  	name        string
   260  	exhaustives []bool
   261  	expected    bool
   262  }{
   263  	{"single exhaustive", []bool{true}, true},
   264  	{"single non-exhaustive", []bool{false}, false},
   265  	{"multiple exhaustive", []bool{true, true}, true},
   266  	{"multiple non-exhaustive", []bool{false, false}, false},
   267  	{"some exhaustive", []bool{true, false}, false},
   268  	{"mixed", []bool{true, false, true}, false},
   269  }
   270  
   271  func TestExhaustiveMerge(t *testing.T) {
   272  	ctrl := xtest.NewController(t)
   273  	defer ctrl.Finish()
   274  
   275  	for _, tt := range exhaustTests {
   276  		t.Run(tt.name, func(t *testing.T) {
   277  			r := NewMultiFetchResult(NamespaceCoversAllQueryRange,
   278  				defaultTestOpts, models.NewTagOptions(), LimitOptions{Limit: 1000})
   279  			for i, ex := range tt.exhaustives {
   280  				iters := encoding.NewSeriesIterators([]encoding.SeriesIterator{
   281  					encoding.NewSeriesIterator(encoding.SeriesIteratorOptions{
   282  						ID:        ident.StringID(fmt.Sprint(i)),
   283  						Namespace: ident.StringID("ns"),
   284  					}, nil),
   285  				})
   286  
   287  				meta := block.NewResultMetadata()
   288  				meta.Exhaustive = ex
   289  				res := MultiFetchResults{
   290  					SeriesIterators: iters,
   291  					Metadata:        meta,
   292  					Attrs:           storagemetadata.Attributes{},
   293  					Err:             nil,
   294  				}
   295  				r.Add(res)
   296  			}
   297  
   298  			result, err := r.FinalResult()
   299  			assert.NoError(t, err)
   300  			assert.Equal(t, tt.expected, result.Metadata.Exhaustive)
   301  			assert.NoError(t, r.Close())
   302  		})
   303  	}
   304  }
   305  
   306  func TestAddWarningsPreservedFollowedByAdd(t *testing.T) {
   307  	ctrl := xtest.NewController(t)
   308  	defer ctrl.Finish()
   309  
   310  	r := NewMultiFetchResult(NamespaceCoversPartialQueryRange,
   311  		defaultTestOpts, models.NewTagOptions(), LimitOptions{
   312  			Limit:             100,
   313  			RequireExhaustive: true,
   314  		})
   315  
   316  	r.AddWarnings(block.Warning{
   317  		Name:    "foo",
   318  		Message: "bar",
   319  	})
   320  	r.AddWarnings(block.Warning{
   321  		Name:    "baz",
   322  		Message: "qux",
   323  	})
   324  
   325  	for i := 0; i < 3; i++ {
   326  		iters := encoding.NewSeriesIterators([]encoding.SeriesIterator{
   327  			encoding.NewSeriesIterator(encoding.SeriesIteratorOptions{
   328  				ID:        ident.StringID(fmt.Sprintf("series-%d", i)),
   329  				Namespace: ident.StringID(fmt.Sprintf("ns-%d", i)),
   330  			}, nil),
   331  		})
   332  
   333  		meta := block.NewResultMetadata()
   334  		meta.Exhaustive = true
   335  		res := MultiFetchResults{
   336  			SeriesIterators: iters,
   337  			Metadata:        meta,
   338  			Attrs:           storagemetadata.Attributes{},
   339  			Err:             nil,
   340  		}
   341  		r.Add(res)
   342  	}
   343  
   344  	finalResult, err := r.FinalResult()
   345  	require.NoError(t, err)
   346  
   347  	assert.Equal(t, 2, len(finalResult.Metadata.Warnings))
   348  
   349  	assert.NoError(t, r.Close())
   350  }