github.com/zuoyebang/bitalosdb@v1.1.1-0.20240516111551-79a8c4d8ce20/looptest/loop_test.go (about) 1 // Copyright 2021 The Bitalosdb author(hustxrb@163.com) and other contributors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package looptest 16 17 import ( 18 "bytes" 19 "fmt" 20 "math/rand" 21 "os" 22 "strconv" 23 "strings" 24 "sync" 25 "sync/atomic" 26 "testing" 27 "time" 28 29 "github.com/cockroachdb/errors/oserror" 30 "github.com/stretchr/testify/require" 31 "github.com/zuoyebang/bitalosdb" 32 "github.com/zuoyebang/bitalosdb/internal/consts" 33 "github.com/zuoyebang/bitalosdb/internal/options" 34 "github.com/zuoyebang/bitalosdb/internal/sortedkv" 35 "github.com/zuoyebang/bitalosdb/internal/utils" 36 ) 37 38 var ( 39 testOptsDisableWAL = false 40 testOptsUseMapIndex = false 41 testOptsUsePrefixCompress = false 42 testOptsUseBlockCompress = false 43 testOptsUseBitable = false 44 testOptsCacheType = consts.CacheTypeLru 45 testOptsCacheSize = 0 46 testLoopRunTime = 300 47 ) 48 49 var defaultLargeVal = utils.FuncRandBytes(1024) 50 var defaultSmallVal = utils.FuncRandBytes(128) 51 52 var testOptsParams = [][]bool{ 53 {true, true, false, false, false}, 54 {true, true, false, true, false}, 55 {true, true, true, false, false}, 56 {true, true, true, true, false}, 57 {false, true, false, false, true}, 58 {false, true, false, true, true}, 59 {false, true, true, false, true}, 60 {false, true, true, true, true}, 61 } 62 63 type BitalosDB struct { 64 db *bitalosdb.DB 65 ro *bitalosdb.IterOptions 66 wo *bitalosdb.WriteOptions 67 opts *bitalosdb.Options 68 } 69 70 func openTestBitalosDB(dir string) (*BitalosDB, error) { 71 compactInfo := bitalosdb.CompactEnv{ 72 StartHour: 0, 73 EndHour: 23, 74 DeletePercent: 0.2, 75 BitreeMaxSize: 2 << 30, 76 Interval: 60, 77 } 78 opts := &bitalosdb.Options{ 79 BytesPerSync: consts.DefaultBytesPerSync, 80 MemTableSize: 1 << 30, 81 MemTableStopWritesThreshold: 8, 82 CacheType: testOptsCacheType, 83 CacheSize: int64(testOptsCacheSize), 84 CacheHashSize: 10000, 85 Verbose: true, 86 CompactInfo: compactInfo, 87 Logger: bitalosdb.DefaultLogger, 88 UseBithash: true, 89 UseBitable: testOptsUseBitable, 90 UseMapIndex: testOptsUseMapIndex, 91 UsePrefixCompress: testOptsUsePrefixCompress, 92 UseBlockCompress: testOptsUseBlockCompress, 93 DisableWAL: testOptsDisableWAL, 94 KeyHashFunc: options.TestKeyHashFunc, 95 KeyPrefixDeleteFunc: options.TestKeyPrefixDeleteFunc, 96 } 97 _, err := os.Stat(dir) 98 if oserror.IsNotExist(err) { 99 if err = os.MkdirAll(dir, 0775); err != nil { 100 return nil, err 101 } 102 } 103 104 pdb, err := bitalosdb.Open(dir, opts) 105 if err != nil { 106 return nil, err 107 } 108 109 return &BitalosDB{ 110 db: pdb, 111 ro: &bitalosdb.IterOptions{}, 112 wo: bitalosdb.NoSync, 113 opts: opts, 114 }, nil 115 } 116 117 func testGetKeyIndex(key []byte) int { 118 sk := strings.Split(string(key), "_") 119 index, _ := strconv.Atoi(sk[len(sk)-1]) 120 return index 121 } 122 123 func testMakeIndexKey(i int, kprefix string) (key []byte) { 124 key = sortedkv.MakeKey([]byte(fmt.Sprintf("%skey_%d", kprefix, i))) 125 return key 126 } 127 128 func testMakeIndexVal(i int, key []byte, vprefix string) (val []byte) { 129 if i%2 == 0 { 130 val = []byte(fmt.Sprintf("%s_%s%s", key, defaultLargeVal, vprefix)) 131 } else { 132 val = []byte(fmt.Sprintf("%s_%s%s", key, defaultSmallVal, vprefix)) 133 } 134 135 return val 136 } 137 138 func testWriteKVRange(t *testing.T, db *bitalosdb.DB, start, end int, kprefix, vprefix string) { 139 for i := start; i <= end; i++ { 140 newKey := testMakeIndexKey(i, kprefix) 141 newValue := testMakeIndexVal(i, newKey, vprefix) 142 if err := db.Set(newKey, newValue, bitalosdb.NoSync); err != nil { 143 t.Fatal("set err", string(newKey), err) 144 } 145 } 146 } 147 148 func testDeleteKVRange(t *testing.T, db *bitalosdb.DB, start, end, gap int, kprefix string) { 149 for i := start; i <= end; i++ { 150 if i%gap != 0 { 151 continue 152 } 153 newKey := testMakeIndexKey(i, kprefix) 154 if err := db.Delete(newKey, bitalosdb.NoSync); err != nil { 155 t.Fatal("delete err", string(newKey), err) 156 } 157 } 158 } 159 160 func testBitalosdbLoop(t *testing.T, dir string) { 161 defer os.RemoveAll(dir) 162 os.RemoveAll(dir) 163 164 bitalosDB, err := openTestBitalosDB(dir) 165 require.NoError(t, err) 166 defer func() { 167 require.NoError(t, bitalosDB.db.Close()) 168 }() 169 170 closeCh := make(chan struct{}) 171 wKeyIndex := uint32(0) 172 rKeyIndex := uint32(0) 173 step := uint32(100000) 174 deleteGap := 9 175 runTime := time.Duration(testLoopRunTime) * time.Second 176 readConcurrency := 2 177 wdConcurrency := 2 178 179 wg := sync.WaitGroup{} 180 wg.Add(4) 181 go func() { 182 defer func() { 183 wg.Done() 184 fmt.Println("write goroutine exit...") 185 }() 186 187 wd := func() { 188 wIndex := atomic.AddUint32(&wKeyIndex, step) 189 start := wIndex - step + 1 190 end := wIndex 191 testWriteKVRange(t, bitalosDB.db, int(start), int(end), "", "") 192 testDeleteKVRange(t, bitalosDB.db, int(start), int(end), deleteGap, "") 193 rIndex := atomic.LoadUint32(&rKeyIndex) 194 if rIndex < start { 195 atomic.CompareAndSwapUint32(&rKeyIndex, rIndex, start) 196 } 197 } 198 199 wwg := sync.WaitGroup{} 200 for i := 0; i < wdConcurrency; i++ { 201 wwg.Add(1) 202 go func(index int) { 203 defer wwg.Done() 204 for { 205 select { 206 case <-closeCh: 207 return 208 default: 209 wd() 210 } 211 } 212 }(i) 213 } 214 wwg.Wait() 215 }() 216 217 go func() { 218 defer func() { 219 defer wg.Done() 220 fmt.Println("compact goroutine exit...") 221 }() 222 job := 1 223 ticker := time.NewTicker(10 * time.Second) 224 defer ticker.Stop() 225 for { 226 select { 227 case <-closeCh: 228 return 229 case <-ticker.C: 230 bitalosDB.db.CheckAndCompact(job) 231 bitalosDB.db.SetCheckpointHighPriority(true) 232 time.Sleep(2 * time.Second) 233 bitalosDB.db.SetCheckpointHighPriority(false) 234 job++ 235 } 236 } 237 }() 238 239 go func() { 240 var readIterCount atomic.Uint64 241 defer func() { 242 defer wg.Done() 243 fmt.Println("iter read goroutine exit readIterCount=", readIterCount.Load()) 244 }() 245 rwg := sync.WaitGroup{} 246 readIter := func() { 247 readIterCount.Add(1) 248 ms := time.Duration(rand.Intn(200) + 1) 249 time.Sleep(ms * time.Millisecond) 250 it := bitalosDB.db.NewIter(nil) 251 defer it.Close() 252 isEmpty := true 253 for it.First(); it.Valid(); it.Next() { 254 if isEmpty { 255 isEmpty = false 256 } 257 index := testGetKeyIndex(it.Key()) 258 if index%deleteGap == 0 { 259 continue 260 } 261 newValue := testMakeIndexVal(index, it.Key(), "") 262 if !bytes.Equal(newValue, it.Value()) { 263 t.Errorf("readIter fail key:%s newValue:%s v:%s", string(it.Key()), string(newValue), string(it.Value())) 264 } 265 } 266 if isEmpty { 267 t.Log("readIter isEmpty") 268 } 269 } 270 for i := 0; i < readConcurrency; i++ { 271 rwg.Add(1) 272 go func(index int) { 273 defer rwg.Done() 274 time.Sleep(5 * time.Second) 275 for { 276 select { 277 case <-closeCh: 278 return 279 default: 280 readIter() 281 } 282 } 283 }(i) 284 } 285 rwg.Wait() 286 }() 287 288 go func() { 289 var readCount atomic.Uint64 290 defer func() { 291 defer wg.Done() 292 fmt.Println("get read goroutine exit readCount=", readCount.Load()) 293 }() 294 rgwg := sync.WaitGroup{} 295 readGet := func() { 296 ms := time.Duration(rand.Intn(10) + 1) 297 time.Sleep(ms * time.Millisecond) 298 keyIndex := atomic.LoadUint32(&rKeyIndex) 299 if keyIndex <= step+1 { 300 time.Sleep(1 * time.Second) 301 return 302 } 303 readCount.Add(1) 304 kindex := rand.Intn(int(keyIndex-step-1)) + 1 305 newKey := testMakeIndexKey(kindex, "") 306 newValue := testMakeIndexVal(kindex, newKey, "") 307 v, closer, err := bitalosDB.db.Get(newKey) 308 if kindex%deleteGap == 0 { 309 if err != bitalosdb.ErrNotFound { 310 t.Errorf("get deleteKey fail key:%s keyIndex:%d err:%v\n", string(newKey), keyIndex, err) 311 } 312 } else { 313 if err != nil { 314 t.Errorf("readGet existKey fail key:%s keyIndex:%d err:%v\n", string(newKey), keyIndex, err) 315 } else if !bytes.Equal(newValue, v) { 316 t.Errorf("get existKey fail val not eq key:%s keyIndex:%d newValue:%s v:%s\n", string(newKey), keyIndex, string(newValue), string(v)) 317 } 318 } 319 if closer != nil { 320 closer() 321 } 322 } 323 for i := 0; i < readConcurrency; i++ { 324 rgwg.Add(1) 325 go func(index int) { 326 defer rgwg.Done() 327 time.Sleep(5 * time.Second) 328 for { 329 select { 330 case <-closeCh: 331 return 332 default: 333 readGet() 334 } 335 } 336 }(i) 337 } 338 rgwg.Wait() 339 }() 340 341 time.Sleep(runTime) 342 close(closeCh) 343 wg.Wait() 344 } 345 346 func TestBitalosdbLoop1(t *testing.T) { 347 dir := "data_1_1" 348 for _, params := range testOptsParams { 349 testOptsDisableWAL = params[0] 350 testOptsUseMapIndex = params[1] 351 testOptsUsePrefixCompress = params[2] 352 testOptsUseBlockCompress = params[3] 353 testOptsUseBitable = params[4] 354 testOptsCacheType = 0 355 testOptsCacheSize = 0 356 fmt.Println("TestBitalosdbLoop1 params=", params) 357 testBitalosdbLoop(t, dir) 358 } 359 } 360 361 func TestBitalosdbLoop2(t *testing.T) { 362 dir := "data_1_2" 363 for _, params := range testOptsParams { 364 testOptsDisableWAL = params[0] 365 testOptsUseMapIndex = params[1] 366 testOptsUsePrefixCompress = params[2] 367 testOptsUseBlockCompress = params[3] 368 testOptsUseBitable = params[4] 369 testOptsCacheType = consts.CacheTypeLru 370 testOptsCacheSize = 1 << 30 371 fmt.Println("TestBitalosdbLoop2 params=", params) 372 testBitalosdbLoop(t, dir) 373 } 374 }