github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/index/postings_list_cache_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  	"fmt"
    25  	"sort"
    26  	"strconv"
    27  	"sync"
    28  	"testing"
    29  
    30  	"github.com/m3db/m3/src/m3ninx/postings"
    31  	"github.com/m3db/m3/src/m3ninx/postings/roaring"
    32  	"github.com/m3db/m3/src/x/instrument"
    33  
    34  	"github.com/pborman/uuid"
    35  	"github.com/stretchr/testify/require"
    36  )
    37  
    38  const (
    39  	numTestPlEntries = 1000
    40  )
    41  
    42  var (
    43  	// Filled in by init().
    44  	testPlEntries               []testEntry
    45  	testPostingListCacheOptions = PostingsListCacheOptions{
    46  		InstrumentOptions: instrument.NewOptions(),
    47  	}
    48  )
    49  
    50  func init() {
    51  	// Generate test data.
    52  	for i := 0; i < numTestPlEntries; i++ {
    53  		var (
    54  			segmentUUID = uuid.Parse(
    55  				fmt.Sprintf("00000000-0000-0000-0000-000000000%03d", i))
    56  
    57  			field   = fmt.Sprintf("field_%d", i)
    58  			pattern = fmt.Sprintf("pattern_%d", i)
    59  			pl      = roaring.NewPostingsList()
    60  		)
    61  		pl.Insert(postings.ID(i))
    62  
    63  		patternType := PatternTypeRegexp
    64  		switch i % 3 {
    65  		case 0:
    66  			patternType = PatternTypeTerm
    67  		case 1:
    68  			patternType = PatternTypeField
    69  			pattern = "" // field queries don't have patterns
    70  		}
    71  
    72  		testPlEntries = append(testPlEntries, testEntry{
    73  			segmentUUID:  segmentUUID,
    74  			key:          newKey(field, pattern, patternType),
    75  			postingsList: pl,
    76  		})
    77  	}
    78  }
    79  
    80  type testEntry struct {
    81  	segmentUUID  uuid.UUID
    82  	key          PostingsListCacheKey
    83  	postingsList postings.List
    84  }
    85  
    86  func TestSimpleLRUBehavior(t *testing.T) {
    87  	size := 3
    88  	plCache, err := NewPostingsListCache(size, testPostingListCacheOptions)
    89  	require.NoError(t, err)
    90  
    91  	var (
    92  		e0 = testPlEntries[0]
    93  		e1 = testPlEntries[1]
    94  		e2 = testPlEntries[2]
    95  		e3 = testPlEntries[3]
    96  		e4 = testPlEntries[4]
    97  		e5 = testPlEntries[5]
    98  	)
    99  	putEntry(t, plCache, 0)
   100  	putEntry(t, plCache, 1)
   101  	putEntry(t, plCache, 2)
   102  	requireExpectedOrder(t, plCache, []testEntry{e0, e1, e2})
   103  
   104  	putEntry(t, plCache, 3)
   105  	requireExpectedOrder(t, plCache, []testEntry{e1, e2, e3})
   106  
   107  	putEntry(t, plCache, 4)
   108  	putEntry(t, plCache, 4)
   109  	putEntry(t, plCache, 5)
   110  	putEntry(t, plCache, 5)
   111  	putEntry(t, plCache, 0)
   112  	putEntry(t, plCache, 0)
   113  	requireExpectedOrder(t, plCache, []testEntry{e4, e5, e0})
   114  
   115  	// Miss, no expected change.
   116  	getEntry(t, plCache, 100)
   117  	requireExpectedOrder(t, plCache, []testEntry{e4, e5, e0})
   118  
   119  	// Hit.
   120  	getEntry(t, plCache, 4)
   121  	requireExpectedOrder(t, plCache, []testEntry{e5, e0, e4})
   122  
   123  	// Multiple hits.
   124  	getEntry(t, plCache, 4)
   125  	getEntry(t, plCache, 0)
   126  	getEntry(t, plCache, 5)
   127  	getEntry(t, plCache, 5)
   128  	requireExpectedOrder(t, plCache, []testEntry{e4, e0, e5})
   129  }
   130  
   131  func TestPurgeSegment(t *testing.T) {
   132  	size := len(testPlEntries)
   133  	plCache, err := NewPostingsListCache(size, testPostingListCacheOptions)
   134  	require.NoError(t, err)
   135  
   136  	// Write many entries with the same segment UUID.
   137  	for i := 0; i < 100; i++ {
   138  		if testPlEntries[i].key.PatternType == PatternTypeRegexp {
   139  			plCache.PutRegexp(
   140  				testPlEntries[0].segmentUUID,
   141  				testPlEntries[i].key.Field,
   142  				testPlEntries[i].key.Pattern,
   143  				testPlEntries[i].postingsList,
   144  			)
   145  		} else {
   146  			plCache.PutTerm(
   147  				testPlEntries[0].segmentUUID,
   148  				testPlEntries[i].key.Field,
   149  				testPlEntries[i].key.Pattern,
   150  				testPlEntries[i].postingsList,
   151  			)
   152  		}
   153  	}
   154  
   155  	// Write the remaining entries.
   156  	for i := 100; i < len(testPlEntries); i++ {
   157  		putEntry(t, plCache, i)
   158  	}
   159  
   160  	// Purge all entries related to the segment.
   161  	plCache.PurgeSegment(testPlEntries[0].segmentUUID)
   162  
   163  	// All entries related to the purged segment should be gone.
   164  	require.Equal(t, size-100, plCache.lru.Len())
   165  	for i := 0; i < 100; i++ {
   166  		if testPlEntries[i].key.PatternType == PatternTypeRegexp {
   167  			_, ok := plCache.GetRegexp(
   168  				testPlEntries[0].segmentUUID,
   169  				testPlEntries[i].key.Field,
   170  				testPlEntries[i].key.Pattern,
   171  			)
   172  			require.False(t, ok)
   173  		} else {
   174  			_, ok := plCache.GetTerm(
   175  				testPlEntries[0].segmentUUID,
   176  				testPlEntries[i].key.Field,
   177  				testPlEntries[i].key.Pattern,
   178  			)
   179  			require.False(t, ok)
   180  		}
   181  	}
   182  
   183  	// Remaining entries should still be present.
   184  	for i := 100; i < len(testPlEntries); i++ {
   185  		getEntry(t, plCache, i)
   186  	}
   187  }
   188  
   189  func TestEverthingInsertedCanBeRetrieved(t *testing.T) {
   190  	plCache, err := NewPostingsListCache(len(testPlEntries), testPostingListCacheOptions)
   191  	require.NoError(t, err)
   192  
   193  	for i := range testPlEntries {
   194  		putEntry(t, plCache, i)
   195  	}
   196  
   197  	for i, entry := range testPlEntries {
   198  		cached, ok := getEntry(t, plCache, i)
   199  		require.True(t, ok)
   200  		require.True(t, cached.Equal(entry.postingsList))
   201  	}
   202  }
   203  
   204  func TestConcurrencyWithEviction(t *testing.T) {
   205  	testConcurrency(t, len(testPlEntries)/10, true, false)
   206  }
   207  
   208  func TestConcurrencyVerifyResultsNoEviction(t *testing.T) {
   209  	testConcurrency(t, len(testPlEntries), false, true)
   210  }
   211  
   212  func testConcurrency(t *testing.T, size int, purge bool, verify bool) {
   213  	plCache, err := NewPostingsListCache(size, testPostingListCacheOptions)
   214  	require.NoError(t, err)
   215  
   216  	wg := sync.WaitGroup{}
   217  	// Spin up writers.
   218  	for i := range testPlEntries {
   219  		wg.Add(1)
   220  		go func(i int) {
   221  			for j := 0; j < 100; j++ {
   222  				putEntry(t, plCache, i)
   223  			}
   224  			wg.Done()
   225  		}(i)
   226  	}
   227  
   228  	// Spin up readers.
   229  	for i := range testPlEntries {
   230  		wg.Add(1)
   231  		go func(i int) {
   232  			for j := 0; j < 100; j++ {
   233  				getEntry(t, plCache, j)
   234  			}
   235  			wg.Done()
   236  		}(i)
   237  	}
   238  
   239  	stopPurge := make(chan struct{})
   240  	if purge {
   241  		go func() {
   242  			for {
   243  				select {
   244  				case <-stopPurge:
   245  				default:
   246  					for _, entry := range testPlEntries {
   247  						plCache.PurgeSegment(entry.segmentUUID)
   248  					}
   249  				}
   250  			}
   251  		}()
   252  	}
   253  
   254  	wg.Wait()
   255  	close(stopPurge)
   256  
   257  	if !verify {
   258  		return
   259  	}
   260  
   261  	for i, entry := range testPlEntries {
   262  		cached, ok := getEntry(t, plCache, i)
   263  		if !ok {
   264  			// Debug.
   265  			printSortedKeys(t, plCache)
   266  		}
   267  		require.True(t, ok)
   268  		require.True(t, cached.Equal(entry.postingsList))
   269  	}
   270  }
   271  
   272  func putEntry(t *testing.T, cache *PostingsListCache, i int) {
   273  	// Do each put twice to test the logic that avoids storing
   274  	// multiple entries for the same value.
   275  	switch testPlEntries[i].key.PatternType {
   276  	case PatternTypeRegexp:
   277  		cache.PutRegexp(
   278  			testPlEntries[i].segmentUUID,
   279  			testPlEntries[i].key.Field,
   280  			testPlEntries[i].key.Pattern,
   281  			testPlEntries[i].postingsList,
   282  		)
   283  		cache.PutRegexp(
   284  			testPlEntries[i].segmentUUID,
   285  			testPlEntries[i].key.Field,
   286  			testPlEntries[i].key.Pattern,
   287  			testPlEntries[i].postingsList,
   288  		)
   289  	case PatternTypeTerm:
   290  		cache.PutTerm(
   291  			testPlEntries[i].segmentUUID,
   292  			testPlEntries[i].key.Field,
   293  			testPlEntries[i].key.Pattern,
   294  			testPlEntries[i].postingsList,
   295  		)
   296  		cache.PutTerm(
   297  			testPlEntries[i].segmentUUID,
   298  			testPlEntries[i].key.Field,
   299  			testPlEntries[i].key.Pattern,
   300  			testPlEntries[i].postingsList,
   301  		)
   302  	case PatternTypeField:
   303  		cache.PutField(
   304  			testPlEntries[i].segmentUUID,
   305  			testPlEntries[i].key.Field,
   306  			testPlEntries[i].postingsList,
   307  		)
   308  		cache.PutField(
   309  			testPlEntries[i].segmentUUID,
   310  			testPlEntries[i].key.Field,
   311  			testPlEntries[i].postingsList,
   312  		)
   313  	default:
   314  		require.FailNow(t, "unknown pattern type", testPlEntries[i].key.PatternType)
   315  	}
   316  }
   317  
   318  func getEntry(t *testing.T, cache *PostingsListCache, i int) (postings.List, bool) {
   319  	switch testPlEntries[i].key.PatternType {
   320  	case PatternTypeRegexp:
   321  		return cache.GetRegexp(
   322  			testPlEntries[i].segmentUUID,
   323  			testPlEntries[i].key.Field,
   324  			testPlEntries[i].key.Pattern,
   325  		)
   326  	case PatternTypeTerm:
   327  		return cache.GetTerm(
   328  			testPlEntries[i].segmentUUID,
   329  			testPlEntries[i].key.Field,
   330  			testPlEntries[i].key.Pattern,
   331  		)
   332  	case PatternTypeField:
   333  		return cache.GetField(
   334  			testPlEntries[i].segmentUUID,
   335  			testPlEntries[i].key.Field,
   336  		)
   337  	default:
   338  		require.FailNow(t, "unknown pattern type", testPlEntries[i].key.PatternType)
   339  	}
   340  	return nil, false
   341  }
   342  
   343  func requireExpectedOrder(t *testing.T, plCache *PostingsListCache, expectedOrder []testEntry) {
   344  	for i, key := range plCache.lru.keys() {
   345  		require.Equal(t, expectedOrder[i].key, key)
   346  	}
   347  }
   348  
   349  func printSortedKeys(t *testing.T, cache *PostingsListCache) {
   350  	keys := cache.lru.keys()
   351  	sort.Slice(keys, func(i, j int) bool {
   352  		iIdx, err := strconv.ParseInt(keys[i].Field, 10, 64)
   353  		if err != nil {
   354  			t.Fatalf("unable to parse: %s into int", keys[i].Field)
   355  		}
   356  
   357  		jIdx, err := strconv.ParseInt(keys[j].Field, 10, 64)
   358  		if err != nil {
   359  			t.Fatalf("unable to parse: %s into int", keys[i].Field)
   360  		}
   361  
   362  		return iIdx < jIdx
   363  	})
   364  
   365  	for _, key := range keys {
   366  		fmt.Println("key: ", key)
   367  	}
   368  }