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 }