github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/index_insert_queue_test.go (about)

     1  // Copyright (c) 2018 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 storage
    22  
    23  import (
    24  	"fmt"
    25  	"sync"
    26  	"sync/atomic"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/m3db/m3/src/dbnode/namespace"
    31  	"github.com/m3db/m3/src/dbnode/storage/index"
    32  	"github.com/m3db/m3/src/m3ninx/doc"
    33  	"github.com/m3db/m3/src/x/ident"
    34  	xsync "github.com/m3db/m3/src/x/sync"
    35  	xtest "github.com/m3db/m3/src/x/test"
    36  	xtime "github.com/m3db/m3/src/x/time"
    37  
    38  	"github.com/fortytw2/leaktest"
    39  	"github.com/stretchr/testify/assert"
    40  	"github.com/stretchr/testify/require"
    41  	"github.com/uber-go/tally"
    42  )
    43  
    44  func newTestIndexInsertQueue(
    45  	namespace namespace.Metadata,
    46  ) *nsIndexInsertQueue {
    47  	var (
    48  		nsIndexInsertBatchFn = func(inserts *index.WriteBatch) {}
    49  		nowFn                = time.Now
    50  		coreFn               = xsync.CPUCore
    51  		scope                = tally.NoopScope
    52  	)
    53  
    54  	q := newNamespaceIndexInsertQueue(nsIndexInsertBatchFn,
    55  		namespace, nowFn, coreFn, scope).(*nsIndexInsertQueue)
    56  	q.indexBatchBackoff = 10 * time.Millisecond
    57  	return q
    58  }
    59  
    60  func testID(i int) ident.ID {
    61  	return ident.StringID(fmt.Sprintf("foo%d", i))
    62  }
    63  
    64  func testTags(i int) ident.Tags {
    65  	return ident.NewTags(ident.Tag{Name: testID(i), Value: testID(i)})
    66  }
    67  
    68  func TestIndexInsertQueueStopBeforeStart(t *testing.T) {
    69  	q := newTestIndexInsertQueue(newTestNamespaceMetadata(t))
    70  	assert.Error(t, q.Stop())
    71  
    72  	q = newTestIndexInsertQueue(newTestNamespaceMetadata(t))
    73  	assert.NoError(t, q.Start())
    74  	assert.Error(t, q.Start())
    75  	assert.NoError(t, q.Stop())
    76  	assert.Error(t, q.Stop())
    77  	assert.Error(t, q.Start())
    78  }
    79  
    80  func TestIndexInsertQueueLifecycleLeaks(t *testing.T) {
    81  	defer leaktest.CheckTimeout(t, time.Second)()
    82  	q := newTestIndexInsertQueue(newTestNamespaceMetadata(t))
    83  	assert.NoError(t, q.Start())
    84  	assert.NoError(t, q.Stop())
    85  }
    86  
    87  func TestIndexInsertQueueCallback(t *testing.T) {
    88  	defer leaktest.CheckTimeout(t, time.Second)()
    89  	ctrl := xtest.NewController(t)
    90  	defer ctrl.Finish()
    91  
    92  	var (
    93  		q               = newTestIndexInsertQueue(newTestNamespaceMetadata(t))
    94  		insertLock      sync.Mutex
    95  		insertedBatches []*index.WriteBatch
    96  		callback        = doc.NewMockOnIndexSeries(ctrl)
    97  	)
    98  	q.indexBatchFn = func(inserts *index.WriteBatch) {
    99  		insertLock.Lock()
   100  		insertedBatches = append(insertedBatches, inserts)
   101  		insertLock.Unlock()
   102  	}
   103  
   104  	assert.NoError(t, q.Start())
   105  	defer q.Stop()
   106  
   107  	now := xtime.Now()
   108  	batch := index.NewWriteBatch(index.WriteBatchOptions{})
   109  	batch.Append(testWriteBatchEntry(testID(1), testTags(1), now, callback))
   110  	wg, err := q.InsertBatch(batch)
   111  	assert.NoError(t, err)
   112  	wg.Wait()
   113  
   114  	insertLock.Lock()
   115  	defer insertLock.Unlock()
   116  	assert.Len(t, insertedBatches, 1)
   117  	assert.Equal(t, 1, insertedBatches[0].Len())
   118  	assert.Equal(t, testID(1).Bytes(), insertedBatches[0].PendingDocs()[0].ID)
   119  	assert.Equal(t, now, insertedBatches[0].PendingEntries()[0].Timestamp)
   120  }
   121  
   122  func TestIndexInsertQueueBatchBackoff(t *testing.T) {
   123  	ctrl := xtest.NewController(t)
   124  	defer ctrl.Finish()
   125  	var (
   126  		inserts  []*index.WriteBatch
   127  		currTime = time.Now()
   128  		timeLock = sync.Mutex{}
   129  		addTime  = func(d time.Duration) {
   130  			timeLock.Lock()
   131  			defer timeLock.Unlock()
   132  			currTime = currTime.Add(d)
   133  		}
   134  		backoff           = 10 * time.Millisecond
   135  		insertWgs         [3]sync.WaitGroup
   136  		insertProgressWgs [3]sync.WaitGroup
   137  	)
   138  	for i := range insertWgs {
   139  		insertWgs[i].Add(1)
   140  	}
   141  	for i := range insertProgressWgs {
   142  		insertProgressWgs[i].Add(1)
   143  	}
   144  	q := newTestIndexInsertQueue(newTestNamespaceMetadata(t))
   145  	q.nowFn = func() time.Time {
   146  		timeLock.Lock()
   147  		defer timeLock.Unlock()
   148  		return currTime
   149  	}
   150  	q.indexBatchFn = func(values *index.WriteBatch) {
   151  		inserts = append(inserts, values)
   152  		insertWgs[len(inserts)-1].Done()
   153  		insertProgressWgs[len(inserts)-1].Wait()
   154  	}
   155  
   156  	q.indexBatchBackoff = backoff
   157  	callback := doc.NewMockOnIndexSeries(ctrl)
   158  
   159  	var slept time.Duration
   160  	var numSleeps int
   161  	q.sleepFn = func(d time.Duration) {
   162  		assert.Equal(t, backoff, d)
   163  
   164  		slept += d
   165  		numSleeps++
   166  		addTime(d)
   167  	}
   168  
   169  	require.NoError(t, q.Start())
   170  	defer func() {
   171  		require.NoError(t, q.Stop())
   172  	}()
   173  
   174  	// first insert
   175  	_, err := q.InsertBatch(testWriteBatch(testWriteBatchEntry(testID(0),
   176  		testTags(0), 0, callback)))
   177  	require.NoError(t, err)
   178  
   179  	// wait for first insert batch to complete
   180  	insertWgs[0].Wait()
   181  
   182  	// now next batch will need to wait as we haven't progressed time
   183  	_, err = q.InsertBatch(testWriteBatch(testWriteBatchEntry(testID(1),
   184  		testTags(1), 0, callback)))
   185  	require.NoError(t, err)
   186  	_, err = q.InsertBatch(testWriteBatch(testWriteBatchEntry(testID(2),
   187  		testTags(2), 0, callback)))
   188  	require.NoError(t, err)
   189  
   190  	// allow first insert to finish
   191  	insertProgressWgs[0].Done()
   192  
   193  	// wait for second batch to complete
   194  	insertWgs[1].Wait()
   195  
   196  	assert.Equal(t, backoff, slept)
   197  	assert.Equal(t, 1, numSleeps)
   198  
   199  	// insert third batch, will also need to wait
   200  	_, err = q.InsertBatch(testWriteBatch(testWriteBatchEntry(testID(3),
   201  		testTags(3), 0, callback)))
   202  	require.NoError(t, err)
   203  
   204  	// allow second batch to finish
   205  	insertProgressWgs[1].Done()
   206  
   207  	// wait for third batch to complete
   208  	insertWgs[2].Wait()
   209  
   210  	assert.Equal(t, 2*backoff, slept)
   211  	assert.Equal(t, 2, numSleeps)
   212  
   213  	assert.Equal(t, 3, len(inserts))
   214  
   215  	// allow third batch to complete
   216  	insertProgressWgs[2].Done()
   217  }
   218  
   219  func TestIndexInsertQueueFlushedOnClose(t *testing.T) {
   220  	defer leaktest.CheckTimeout(t, 5*time.Second)()
   221  
   222  	var (
   223  		numInsertExpected = 10
   224  		numInsertObserved int64
   225  		currTime          = time.Now().Truncate(time.Second)
   226  	)
   227  
   228  	q := newNamespaceIndexInsertQueue(
   229  		func(values *index.WriteBatch) {
   230  			atomic.AddInt64(&numInsertObserved, int64(values.Len()))
   231  		},
   232  		newTestNamespaceMetadata(t),
   233  		func() time.Time {
   234  			return currTime
   235  		},
   236  		xsync.CPUCore,
   237  		tally.NoopScope)
   238  
   239  	require.NoError(t, q.Start())
   240  
   241  	for i := 0; i < numInsertExpected; i++ {
   242  		_, err := q.InsertBatch(testWriteBatch(testWriteBatchEntry(testID(1),
   243  			testTags(1), 0, nil)))
   244  		require.NoError(t, err)
   245  	}
   246  
   247  	require.NoError(t, q.Stop())
   248  	require.Equal(t, int64(numInsertExpected), atomic.LoadInt64(&numInsertObserved))
   249  }