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  }