github.com/m3db/m3@v1.5.0/src/dbnode/storage/index/read_through_segment_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 index
    22  
    23  import (
    24  	"regexp/syntax"
    25  	"testing"
    26  
    27  	"github.com/m3db/m3/src/m3ninx/index"
    28  	"github.com/m3db/m3/src/m3ninx/index/segment"
    29  	"github.com/m3db/m3/src/m3ninx/index/segment/fst"
    30  	"github.com/m3db/m3/src/m3ninx/postings/roaring"
    31  	xtest "github.com/m3db/m3/src/x/test"
    32  
    33  	"github.com/golang/mock/gomock"
    34  	"github.com/stretchr/testify/require"
    35  )
    36  
    37  var (
    38  	defaultReadThroughSegmentOptions = ReadThroughSegmentOptions{
    39  		CacheRegexp: true,
    40  		CacheTerms:  true,
    41  	}
    42  )
    43  
    44  func testReadThroughSegmentCaches(
    45  	segmentPostingsListCache *PostingsListCache,
    46  ) ReadThroughSegmentCaches {
    47  	return ReadThroughSegmentCaches{
    48  		SegmentPostingsListCache: segmentPostingsListCache,
    49  	}
    50  }
    51  
    52  func TestReadThroughSegmentMatchRegexp(t *testing.T) {
    53  	ctrl := xtest.NewController(t)
    54  	defer ctrl.Finish()
    55  
    56  	seg := fst.NewMockSegment(ctrl)
    57  	reader := segment.NewMockReader(ctrl)
    58  	seg.EXPECT().Reader().Return(reader, nil)
    59  
    60  	cache, err := NewPostingsListCache(1, testPostingListCacheOptions)
    61  	require.NoError(t, err)
    62  
    63  	field := []byte("some-field")
    64  	parsedRegex, err := syntax.Parse(".*this-will-be-slow.*", syntax.Simple)
    65  	require.NoError(t, err)
    66  	compiledRegex := index.CompiledRegex{
    67  		FSTSyntax: parsedRegex,
    68  	}
    69  
    70  	readThrough, err := NewReadThroughSegment(seg,
    71  		testReadThroughSegmentCaches(cache),
    72  		defaultReadThroughSegmentOptions).Reader()
    73  	require.NoError(t, err)
    74  
    75  	originalPL := roaring.NewPostingsList()
    76  	require.NoError(t, originalPL.Insert(1))
    77  	reader.EXPECT().MatchRegexp(field, gomock.Any()).Return(originalPL, nil)
    78  
    79  	// Make sure it goes to the segment when the cache misses.
    80  	pl, err := readThrough.MatchRegexp(field, compiledRegex)
    81  	require.NoError(t, err)
    82  	require.True(t, pl.Equal(originalPL))
    83  
    84  	// Make sure it relies on the cache if its present (mock only expects
    85  	// one call.)
    86  	pl, err = readThrough.MatchRegexp(field, compiledRegex)
    87  	require.NoError(t, err)
    88  	require.True(t, pl.Equal(originalPL))
    89  }
    90  
    91  func TestReadThroughSegmentMatchRegexpCacheDisabled(t *testing.T) {
    92  	ctrl := xtest.NewController(t)
    93  	defer ctrl.Finish()
    94  
    95  	seg := fst.NewMockSegment(ctrl)
    96  	reader := segment.NewMockReader(ctrl)
    97  	seg.EXPECT().Reader().Return(reader, nil)
    98  
    99  	cache, err := NewPostingsListCache(1, testPostingListCacheOptions)
   100  	require.NoError(t, err)
   101  
   102  	field := []byte("some-field")
   103  	parsedRegex, err := syntax.Parse(".*this-will-be-slow.*", syntax.Simple)
   104  	require.NoError(t, err)
   105  	compiledRegex := index.CompiledRegex{
   106  		FSTSyntax: parsedRegex,
   107  	}
   108  
   109  	readThrough, err := NewReadThroughSegment(seg,
   110  		testReadThroughSegmentCaches(cache),
   111  		ReadThroughSegmentOptions{
   112  			CacheRegexp: false,
   113  		}).
   114  		Reader()
   115  	require.NoError(t, err)
   116  
   117  	originalPL := roaring.NewPostingsList()
   118  	require.NoError(t, originalPL.Insert(1))
   119  	reader.EXPECT().
   120  		MatchRegexp(field, gomock.Any()).
   121  		Return(originalPL, nil).
   122  		Times(2)
   123  
   124  	// Make sure it goes to the segment.
   125  	pl, err := readThrough.MatchRegexp(field, compiledRegex)
   126  	require.NoError(t, err)
   127  	require.True(t, pl.Equal(originalPL))
   128  
   129  	// Make sure it goes to the segment the second time - meaning the cache was
   130  	// disabled.
   131  	pl, err = readThrough.MatchRegexp(field, compiledRegex)
   132  	require.NoError(t, err)
   133  	require.True(t, pl.Equal(originalPL))
   134  }
   135  
   136  func TestReadThroughSegmentMatchRegexpNoCache(t *testing.T) {
   137  	ctrl := xtest.NewController(t)
   138  	defer ctrl.Finish()
   139  
   140  	var (
   141  		seg              = fst.NewMockSegment(ctrl)
   142  		reader           = segment.NewMockReader(ctrl)
   143  		field            = []byte("some-field")
   144  		parsedRegex, err = syntax.Parse(".*this-will-be-slow.*", syntax.Simple)
   145  	)
   146  	require.NoError(t, err)
   147  
   148  	seg.EXPECT().Reader().Return(reader, nil)
   149  	compiledRegex := index.CompiledRegex{
   150  		FSTSyntax: parsedRegex,
   151  	}
   152  
   153  	readThrough, err := NewReadThroughSegment(seg,
   154  		testReadThroughSegmentCaches(nil),
   155  		defaultReadThroughSegmentOptions).
   156  		Reader()
   157  	require.NoError(t, err)
   158  
   159  	originalPL := roaring.NewPostingsList()
   160  	require.NoError(t, originalPL.Insert(1))
   161  	reader.EXPECT().MatchRegexp(field, gomock.Any()).Return(originalPL, nil)
   162  
   163  	// Make sure it it works with no cache.
   164  	pl, err := readThrough.MatchRegexp(field, compiledRegex)
   165  	require.NoError(t, err)
   166  	require.True(t, pl.Equal(originalPL))
   167  }
   168  
   169  func TestReadThroughSegmentMatchTerm(t *testing.T) {
   170  	ctrl := xtest.NewController(t)
   171  	defer ctrl.Finish()
   172  
   173  	seg := fst.NewMockSegment(ctrl)
   174  	reader := segment.NewMockReader(ctrl)
   175  	seg.EXPECT().Reader().Return(reader, nil)
   176  
   177  	cache, err := NewPostingsListCache(1, testPostingListCacheOptions)
   178  	require.NoError(t, err)
   179  
   180  	var (
   181  		field = []byte("some-field")
   182  		term  = []byte("some-term")
   183  
   184  		originalPL = roaring.NewPostingsList()
   185  	)
   186  	require.NoError(t, originalPL.Insert(1))
   187  
   188  	readThrough, err := NewReadThroughSegment(seg,
   189  		testReadThroughSegmentCaches(cache),
   190  		defaultReadThroughSegmentOptions).
   191  		Reader()
   192  	require.NoError(t, err)
   193  
   194  	reader.EXPECT().MatchTerm(field, term).Return(originalPL, nil)
   195  
   196  	// Make sure it goes to the segment when the cache misses.
   197  	pl, err := readThrough.MatchTerm(field, term)
   198  	require.NoError(t, err)
   199  	require.True(t, pl.Equal(originalPL))
   200  
   201  	// Make sure it relies on the cache if its present (mock only expects
   202  	// one call.)
   203  	pl, err = readThrough.MatchTerm(field, term)
   204  	require.NoError(t, err)
   205  	require.True(t, pl.Equal(originalPL))
   206  }
   207  
   208  func TestReadThroughSegmentMatchTermCacheDisabled(t *testing.T) {
   209  	ctrl := xtest.NewController(t)
   210  	defer ctrl.Finish()
   211  
   212  	seg := fst.NewMockSegment(ctrl)
   213  	reader := segment.NewMockReader(ctrl)
   214  	seg.EXPECT().Reader().Return(reader, nil)
   215  
   216  	cache, err := NewPostingsListCache(1, testPostingListCacheOptions)
   217  	require.NoError(t, err)
   218  
   219  	var (
   220  		field = []byte("some-field")
   221  		term  = []byte("some-term")
   222  
   223  		originalPL = roaring.NewPostingsList()
   224  	)
   225  	require.NoError(t, originalPL.Insert(1))
   226  
   227  	readThrough, err := NewReadThroughSegment(seg,
   228  		testReadThroughSegmentCaches(cache),
   229  		ReadThroughSegmentOptions{
   230  			CacheTerms: false,
   231  		}).
   232  		Reader()
   233  	require.NoError(t, err)
   234  
   235  	reader.EXPECT().
   236  		MatchTerm(field, term).
   237  		Return(originalPL, nil).
   238  		Times(2)
   239  
   240  	// Make sure it goes to the segment when the cache misses.
   241  	pl, err := readThrough.MatchTerm(field, term)
   242  	require.NoError(t, err)
   243  	require.True(t, pl.Equal(originalPL))
   244  
   245  	// Make sure it goes to the segment the second time - meaning the cache was
   246  	// disabled.
   247  	pl, err = readThrough.MatchTerm(field, term)
   248  	require.NoError(t, err)
   249  	require.True(t, pl.Equal(originalPL))
   250  }
   251  
   252  func TestReadThroughSegmentMatchTermNoCache(t *testing.T) {
   253  	ctrl := xtest.NewController(t)
   254  	defer ctrl.Finish()
   255  
   256  	var (
   257  		seg    = fst.NewMockSegment(ctrl)
   258  		reader = segment.NewMockReader(ctrl)
   259  
   260  		field = []byte("some-field")
   261  		term  = []byte("some-term")
   262  
   263  		originalPL = roaring.NewPostingsList()
   264  	)
   265  	require.NoError(t, originalPL.Insert(1))
   266  
   267  	seg.EXPECT().Reader().Return(reader, nil)
   268  
   269  	readThrough, err := NewReadThroughSegment(seg,
   270  		testReadThroughSegmentCaches(nil),
   271  		defaultReadThroughSegmentOptions).
   272  		Reader()
   273  	require.NoError(t, err)
   274  
   275  	reader.EXPECT().MatchTerm(field, term).Return(originalPL, nil)
   276  
   277  	// Make sure it it works with no cache.
   278  	pl, err := readThrough.MatchTerm(field, term)
   279  	require.NoError(t, err)
   280  	require.True(t, pl.Equal(originalPL))
   281  }
   282  
   283  func TestClose(t *testing.T) {
   284  	ctrl := xtest.NewController(t)
   285  	defer ctrl.Finish()
   286  
   287  	segment := fst.NewMockSegment(ctrl)
   288  	cache, err := NewPostingsListCache(1, testPostingListCacheOptions)
   289  	require.NoError(t, err)
   290  
   291  	readThroughSeg := NewReadThroughSegment(segment,
   292  		testReadThroughSegmentCaches(nil),
   293  		defaultReadThroughSegmentOptions)
   294  
   295  	segmentUUID := readThroughSeg.uuid
   296  
   297  	// Store an entry for the segment in the cache so we can check if it
   298  	// gets purged after.
   299  	cache.PutRegexp(segmentUUID, "some-field", "some-pattern", roaring.NewPostingsList())
   300  
   301  	segment.EXPECT().Close().Return(nil)
   302  	err = readThroughSeg.Close()
   303  	require.NoError(t, err)
   304  	require.True(t, readThroughSeg.closed)
   305  
   306  	// Make sure it does not allow double closes.
   307  	err = readThroughSeg.Close()
   308  	require.Equal(t, errCantCloseClosedSegment, err)
   309  
   310  	// Make sure it does not allow readers to be created after closing.
   311  	_, err = readThroughSeg.Reader()
   312  	require.Equal(t, errCantGetReaderFromClosedSegment, err)
   313  }
   314  
   315  func TestReadThroughSegmentMatchField(t *testing.T) {
   316  	ctrl := xtest.NewController(t)
   317  	defer ctrl.Finish()
   318  
   319  	seg := fst.NewMockSegment(ctrl)
   320  	reader := segment.NewMockReader(ctrl)
   321  	seg.EXPECT().Reader().Return(reader, nil)
   322  
   323  	cache, err := NewPostingsListCache(1, testPostingListCacheOptions)
   324  	require.NoError(t, err)
   325  
   326  	var (
   327  		field = []byte("some-field")
   328  
   329  		originalPL = roaring.NewPostingsList()
   330  	)
   331  	require.NoError(t, originalPL.Insert(1))
   332  
   333  	readThrough, err := NewReadThroughSegment(seg,
   334  		testReadThroughSegmentCaches(cache),
   335  		defaultReadThroughSegmentOptions).
   336  		Reader()
   337  	require.NoError(t, err)
   338  
   339  	reader.EXPECT().MatchField(field).Return(originalPL, nil)
   340  
   341  	// Make sure it goes to the segment when the cache misses.
   342  	pl, err := readThrough.MatchField(field)
   343  	require.NoError(t, err)
   344  	require.True(t, pl.Equal(originalPL))
   345  
   346  	// Make sure it relies on the cache if its present (mock only expects
   347  	// one call.)
   348  	pl, err = readThrough.MatchField(field)
   349  	require.NoError(t, err)
   350  	require.True(t, pl.Equal(originalPL))
   351  }
   352  
   353  func TestReadThroughSegmentMatchFieldCacheDisabled(t *testing.T) {
   354  	ctrl := xtest.NewController(t)
   355  	defer ctrl.Finish()
   356  
   357  	seg := fst.NewMockSegment(ctrl)
   358  	reader := segment.NewMockReader(ctrl)
   359  	seg.EXPECT().Reader().Return(reader, nil)
   360  
   361  	cache, err := NewPostingsListCache(1, testPostingListCacheOptions)
   362  	require.NoError(t, err)
   363  
   364  	var (
   365  		field = []byte("some-field")
   366  
   367  		originalPL = roaring.NewPostingsList()
   368  	)
   369  	require.NoError(t, originalPL.Insert(1))
   370  
   371  	readThrough, err := NewReadThroughSegment(seg,
   372  		testReadThroughSegmentCaches(cache),
   373  		ReadThroughSegmentOptions{
   374  			CacheTerms: false,
   375  		}).
   376  		Reader()
   377  	require.NoError(t, err)
   378  
   379  	reader.EXPECT().
   380  		MatchField(field).
   381  		Return(originalPL, nil).
   382  		Times(2)
   383  
   384  	// Make sure it goes to the segment when the cache misses.
   385  	pl, err := readThrough.MatchField(field)
   386  	require.NoError(t, err)
   387  	require.True(t, pl.Equal(originalPL))
   388  
   389  	// Make sure it goes to the segment the second time - meaning the cache was
   390  	// disabled.
   391  	pl, err = readThrough.MatchField(field)
   392  	require.NoError(t, err)
   393  	require.True(t, pl.Equal(originalPL))
   394  }
   395  
   396  func TestReadThroughSegmentMatchFieldNoCache(t *testing.T) {
   397  	ctrl := xtest.NewController(t)
   398  	defer ctrl.Finish()
   399  
   400  	var (
   401  		seg    = fst.NewMockSegment(ctrl)
   402  		reader = segment.NewMockReader(ctrl)
   403  
   404  		field = []byte("some-field")
   405  
   406  		originalPL = roaring.NewPostingsList()
   407  	)
   408  	require.NoError(t, originalPL.Insert(1))
   409  
   410  	seg.EXPECT().Reader().Return(reader, nil)
   411  
   412  	readThrough, err := NewReadThroughSegment(seg,
   413  		testReadThroughSegmentCaches(nil),
   414  		defaultReadThroughSegmentOptions).
   415  		Reader()
   416  	require.NoError(t, err)
   417  
   418  	reader.EXPECT().MatchField(field).Return(originalPL, nil)
   419  
   420  	// Make sure it it works with no cache.
   421  	pl, err := readThrough.MatchField(field)
   422  	require.NoError(t, err)
   423  	require.True(t, pl.Equal(originalPL))
   424  }
   425  
   426  func TestCloseNoCache(t *testing.T) {
   427  	ctrl := xtest.NewController(t)
   428  	defer ctrl.Finish()
   429  
   430  	seg := fst.NewMockSegment(ctrl)
   431  
   432  	readThrough := NewReadThroughSegment(seg,
   433  		testReadThroughSegmentCaches(nil),
   434  		defaultReadThroughSegmentOptions)
   435  
   436  	seg.EXPECT().Close().Return(nil)
   437  	err := readThrough.Close()
   438  	require.NoError(t, err)
   439  	require.True(t, readThrough.closed)
   440  }