github.com/m3db/m3@v1.5.0/src/query/storage/m3/consolidators/id_dedupe_map_test.go (about)

     1  // Copyright (c) 2020 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  	"testing"
    25  	"time"
    26  
    27  	"github.com/golang/mock/gomock"
    28  	"github.com/stretchr/testify/assert"
    29  	"github.com/stretchr/testify/require"
    30  
    31  	"github.com/m3db/m3/src/dbnode/encoding"
    32  	"github.com/m3db/m3/src/dbnode/ts"
    33  	"github.com/m3db/m3/src/query/models"
    34  	"github.com/m3db/m3/src/query/storage/m3/storagemetadata"
    35  	"github.com/m3db/m3/src/x/ident"
    36  	xtest "github.com/m3db/m3/src/x/test"
    37  	xtime "github.com/m3db/m3/src/x/time"
    38  )
    39  
    40  func verifyIDDedupeMap(
    41  	t *testing.T,
    42  	dedupeMap fetchDedupeMap,
    43  	numSeries int,
    44  ) {
    45  	series := dedupeMap.list()
    46  	require.Equal(t, numSeries, len(series))
    47  	val, found := series[0].tags.Get([]byte("foo"))
    48  	require.True(t, found)
    49  	assert.Equal(t, "bar", string(val))
    50  	val, found = series[0].tags.Get([]byte("qux"))
    51  	require.True(t, found)
    52  	assert.Equal(t, "quail", string(val))
    53  }
    54  
    55  func idIt(
    56  	ctrl *gomock.Controller,
    57  	dp dp,
    58  	id string,
    59  	tags ...string,
    60  ) encoding.SeriesIterator {
    61  	it := encoding.NewMockSeriesIterator(ctrl)
    62  	it.EXPECT().ID().Return(ident.StringID(id)).AnyTimes()
    63  
    64  	it.EXPECT().Namespace().Return(ident.StringID("ns")).AnyTimes()
    65  	it.EXPECT().Start().Return(dp.t).AnyTimes()
    66  	it.EXPECT().End().Return(dp.t.Add(time.Hour)).AnyTimes()
    67  	it.EXPECT().FirstAnnotation().Return(nil).AnyTimes()
    68  
    69  	tagIter := ident.MustNewTagStringsIterator(tags...)
    70  	it.EXPECT().Tags().Return(tagIter).AnyTimes()
    71  
    72  	it.EXPECT().Current().
    73  		Return(ts.Datapoint{
    74  			TimestampNanos: dp.t,
    75  			Value:          dp.val,
    76  		}, xtime.Second, nil).AnyTimes()
    77  	it.EXPECT().Err().Return(nil).AnyTimes()
    78  	it.EXPECT().Close()
    79  
    80  	return it
    81  }
    82  
    83  func rangeIt(
    84  	ctrl *gomock.Controller,
    85  	dp dp,
    86  	id string,
    87  	start, end xtime.UnixNano,
    88  	tags ...string,
    89  ) encoding.SeriesIterator {
    90  	it := encoding.NewMockSeriesIterator(ctrl)
    91  	it.EXPECT().ID().Return(ident.StringID(id)).AnyTimes()
    92  
    93  	it.EXPECT().Namespace().Return(ident.StringID("ns")).AnyTimes()
    94  	it.EXPECT().Start().Return(start).AnyTimes()
    95  	it.EXPECT().End().Return(end).AnyTimes()
    96  	it.EXPECT().FirstAnnotation().Return(nil).AnyTimes()
    97  	it.EXPECT().Next().Return(true).MaxTimes(1)
    98  
    99  	tagIter := ident.MustNewTagStringsIterator(tags...)
   100  	it.EXPECT().Tags().Return(tagIter).AnyTimes()
   101  
   102  	it.EXPECT().Current().
   103  		Return(ts.Datapoint{
   104  			TimestampNanos: dp.t,
   105  			Value:          dp.val,
   106  		}, xtime.Second, nil).AnyTimes()
   107  	it.EXPECT().Next().Return(false).AnyTimes()
   108  
   109  	it.EXPECT().Err().Return(nil).AnyTimes()
   110  	it.EXPECT().Close()
   111  
   112  	return it
   113  }
   114  
   115  func TestIDDedupeMap(t *testing.T) {
   116  	ctrl := xtest.NewController(t)
   117  	defer ctrl.Finish()
   118  
   119  	dedupeMap := newIDDedupeMap(dedupeMapOpts{
   120  		size:    8,
   121  		fanout:  NamespaceCoversAllQueryRange,
   122  		tagOpts: models.NewTagOptions(),
   123  	})
   124  
   125  	start := xtime.Now().Truncate(time.Hour)
   126  	attrs := storagemetadata.Attributes{
   127  		MetricsType: storagemetadata.UnaggregatedMetricsType,
   128  		Resolution:  time.Hour,
   129  	}
   130  
   131  	var allIters []encoding.SeriesIterator
   132  
   133  	addIter := func(iter encoding.SeriesIterator, attrs storagemetadata.Attributes) {
   134  		err := dedupeMap.add(iter, attrs)
   135  		require.NoError(t, err)
   136  		allIters = append(allIters, iter)
   137  	}
   138  
   139  	addIter(idIt(ctrl, dp{t: start, val: 14},
   140  		"id1", "foo", "bar", "qux", "quail"), attrs)
   141  	verifyIDDedupeMap(t, dedupeMap, 1)
   142  
   143  	// Higher resolution must override.
   144  	attrs.Resolution = time.Minute
   145  
   146  	addIter(idIt(ctrl, dp{t: start.Add(time.Minute), val: 10},
   147  		"id1", "foo", "bar", "qux", "quail"), attrs)
   148  
   149  	addIter(idIt(ctrl, dp{t: start.Add(time.Minute * 2), val: 12},
   150  		"id2", "foo", "bar", "qux", "quail"), attrs)
   151  
   152  	verifyIDDedupeMap(t, dedupeMap, 2)
   153  
   154  	// Higher resolution must override.
   155  	attrs.Resolution = time.Second
   156  
   157  	addIter(idIt(ctrl, dp{t: start, val: 100},
   158  		"id1", "foo", "bar", "qux", "quail"), attrs)
   159  
   160  	verifyIDDedupeMap(t, dedupeMap, 2)
   161  
   162  	addIter(idIt(ctrl, dp{t: start.Add(time.Minute * 2), val: 12},
   163  		"id4", "foo", "bar", "qux", "quail"), attrs)
   164  
   165  	addIter(idIt(ctrl, dp{t: start.Add(time.Minute * 2), val: 12},
   166  		"id3", "foo", "bar", "qux", "quail"), attrs)
   167  
   168  	// Get list multiple times and ensure they are always in same order.
   169  	expectedIDs := make([]string, 0)
   170  	for _, it := range dedupeMap.list() {
   171  		expectedIDs = append(expectedIDs, it.tags.String())
   172  	}
   173  	for i := 0; i < 10; i++ {
   174  		ids := make([]string, 0)
   175  		for _, it := range dedupeMap.list() {
   176  			ids = append(ids, it.tags.String())
   177  		}
   178  		require.Equal(t, expectedIDs, ids)
   179  	}
   180  
   181  	for _, iter := range allIters {
   182  		checkAndClose(t, iter)
   183  	}
   184  }
   185  
   186  func TestIDDedupeMapWithStitching(t *testing.T) {
   187  	ctrl := xtest.NewController(t)
   188  	defer ctrl.Finish()
   189  
   190  	dedupeMap := newIDDedupeMap(dedupeMapOpts{
   191  		size:    8,
   192  		fanout:  NamespaceCoversAllQueryRange,
   193  		tagOpts: models.NewTagOptions(),
   194  	})
   195  
   196  	var (
   197  		start    = xtime.Now().Truncate(time.Hour).Add(-24 * time.Hour)
   198  		stitchAt = start.Add(12 * time.Hour)
   199  		end      = start.Add(20 * time.Hour)
   200  
   201  		unaggAttrs = storagemetadata.Attributes{
   202  			MetricsType: storagemetadata.UnaggregatedMetricsType,
   203  			Resolution:  time.Minute,
   204  			Retention:   12 * time.Hour,
   205  		}
   206  
   207  		aggAttrs = storagemetadata.Attributes{
   208  			MetricsType: storagemetadata.AggregatedMetricsType,
   209  			Resolution:  5 * time.Minute,
   210  			Retention:   24 * time.Hour,
   211  		}
   212  	)
   213  
   214  	var allIters []encoding.SeriesIterator
   215  
   216  	addIter := func(iter encoding.SeriesIterator, attrs storagemetadata.Attributes) {
   217  		err := dedupeMap.add(iter, attrs)
   218  		require.NoError(t, err)
   219  		allIters = append(allIters, iter)
   220  	}
   221  
   222  	addIter(
   223  		rangeIt(ctrl, dp{t: start, val: 14}, "id1", start, stitchAt), unaggAttrs)
   224  	assert.Equal(t, dedupeMap.len(), 1)
   225  
   226  	addIter(
   227  		rangeIt(ctrl, dp{t: start.Add(time.Minute), val: 10}, "id1", stitchAt, end), aggAttrs)
   228  	assert.Equal(t, dedupeMap.len(), 1)
   229  
   230  	addIter(
   231  		rangeIt(ctrl, dp{t: start.Add(time.Minute * 2), val: 12}, "id2", stitchAt, end), aggAttrs)
   232  	assert.Equal(t, dedupeMap.len(), 2)
   233  
   234  	addIter(
   235  		rangeIt(ctrl, dp{t: start, val: 100}, "id2", start, stitchAt), unaggAttrs)
   236  	assert.Equal(t, dedupeMap.len(), 2)
   237  
   238  	addIter(
   239  		rangeIt(ctrl, dp{t: start.Add(time.Minute * 2), val: 12}, "id3", start, stitchAt), aggAttrs)
   240  	assert.Equal(t, dedupeMap.len(), 3)
   241  
   242  	addIter(
   243  		rangeIt(ctrl, dp{t: start.Add(time.Minute * 2), val: 12}, "id4", stitchAt, end), aggAttrs)
   244  	assert.Equal(t, dedupeMap.len(), 4)
   245  
   246  	addIter(
   247  		rangeIt(ctrl, dp{t: start.Add(time.Minute * 2), val: 5}, "id5", start, end), aggAttrs)
   248  	assert.Equal(t, dedupeMap.len(), 5)
   249  
   250  	addIter(rangeIt(ctrl, dp{t: start.Add(time.Minute * 3), val: 6}, "id5", start, end), aggAttrs)
   251  	assert.Equal(t, dedupeMap.len(), 5)
   252  
   253  	actual := map[string]startEnd{}
   254  	for _, series := range dedupeMap.list() {
   255  		iter := series.iter
   256  		id := iter.ID().String()
   257  		actual[id] = startEnd{
   258  			start: iter.Start(),
   259  			end:   iter.End(),
   260  		}
   261  
   262  		assert.Equal(t, aggAttrs, series.attrs, id)
   263  	}
   264  
   265  	for _, iter := range allIters {
   266  		checkAndClose(t, iter)
   267  	}
   268  
   269  	expected := map[string]startEnd{
   270  		"id1": {start, end},
   271  		"id2": {start, end},
   272  		"id3": {start, stitchAt},
   273  		"id4": {stitchAt, end},
   274  		"id5": {start, end},
   275  	}
   276  	assert.Equal(t, expected, actual)
   277  }
   278  
   279  type startEnd struct {
   280  	start, end xtime.UnixNano
   281  }