github.com/klaytn/klaytn@v1.12.1/storage/database/leveldb_bench_options_test.go (about)

     1  // Copyright 2018 The klaytn Authors
     2  // This file is part of the klaytn library.
     3  //
     4  // The klaytn library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The klaytn library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the klaytn library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package database
    18  
    19  import (
    20  	"math/rand"
    21  	"os"
    22  	"strconv"
    23  	"sync"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/stretchr/testify/require"
    28  	"github.com/syndtr/goleveldb/leveldb/opt"
    29  )
    30  
    31  func genTempDirForTestDB(b *testing.B) string {
    32  	dir, err := os.MkdirTemp("", "klaytn-db-bench")
    33  	if err != nil {
    34  		b.Fatalf("cannot create temporary directory: %v", err)
    35  	}
    36  	return dir
    37  }
    38  
    39  func getKlayLDBOptions() *opt.Options {
    40  	return getLevelDBOptions(&DBConfig{LevelDBCacheSize: 128, OpenFilesLimit: 128})
    41  }
    42  
    43  func getKlayLDBOptionsForGetX(x int) *opt.Options {
    44  	opts := getKlayLDBOptions()
    45  	opts.WriteBuffer *= x
    46  	opts.BlockCacheCapacity *= x
    47  	opts.OpenFilesCacheCapacity *= x
    48  	opts.DisableBlockCache = true
    49  
    50  	return opts
    51  }
    52  
    53  func getKlayLDBOptionsForPutX(x int) *opt.Options {
    54  	opts := getKlayLDBOptions()
    55  	opts.BlockCacheCapacity *= x
    56  	opts.BlockRestartInterval *= x
    57  
    58  	opts.BlockSize *= x
    59  	opts.CompactionExpandLimitFactor *= x
    60  	opts.CompactionL0Trigger *= x
    61  	opts.CompactionTableSize *= x
    62  
    63  	opts.CompactionSourceLimitFactor *= x
    64  	opts.Compression = opt.DefaultCompression
    65  
    66  	return opts
    67  }
    68  
    69  func getKlayLDBOptionsForBatchX(x int) *opt.Options {
    70  	opts := getKlayLDBOptions()
    71  	opts.BlockCacheCapacity *= x
    72  	opts.BlockRestartInterval *= x
    73  
    74  	opts.BlockSize *= x
    75  	opts.CompactionExpandLimitFactor *= x
    76  	opts.CompactionL0Trigger *= x
    77  	opts.CompactionTableSize *= x
    78  
    79  	opts.CompactionSourceLimitFactor *= x
    80  	opts.Compression = opt.DefaultCompression
    81  
    82  	return opts
    83  }
    84  
    85  // readTypeFunc determines requested index
    86  func benchmarkKlayOptionsGet(b *testing.B, opts *opt.Options, valueLength, numInsertions, numGets int, readTypeFunc func(int, int) int) {
    87  	b.StopTimer()
    88  	b.ReportAllocs()
    89  
    90  	dir := genTempDirForTestDB(b)
    91  	defer os.RemoveAll(dir)
    92  
    93  	db, err := NewLevelDBWithOption(dir, opts)
    94  	require.NoError(b, err)
    95  	defer db.Close()
    96  
    97  	for i := 0; i < numInsertions; i++ {
    98  		bs := []byte(strconv.Itoa(i))
    99  		db.Put(bs, randStrBytes(valueLength))
   100  	}
   101  
   102  	b.StartTimer()
   103  	for k := 0; k < b.N; k++ {
   104  		for i := 0; i < numGets; i++ {
   105  			bs := []byte(strconv.Itoa(readTypeFunc(i, numInsertions)))
   106  			_, err := db.Get(bs)
   107  			if err != nil {
   108  				b.Fatalf("get failed: %v", err)
   109  			}
   110  		}
   111  	}
   112  }
   113  
   114  func randomRead(currIndex, numInsertions int) int {
   115  	return rand.Intn(numInsertions)
   116  }
   117  
   118  func sequentialRead(currIndex, numInsertions int) int {
   119  	return numInsertions - currIndex - 1
   120  }
   121  
   122  var r = rand.New(rand.NewSource(time.Now().UnixNano()))
   123  
   124  func zipfRead(currIndex, numInsertions int) int {
   125  	zipf := rand.NewZipf(r, 3.14, 2.72, uint64(numInsertions))
   126  	zipfNum := zipf.Uint64()
   127  	return numInsertions - int(zipfNum) - 1
   128  }
   129  
   130  const (
   131  	getKlayValueLegnth   = 250
   132  	getKlayNumInsertions = 1000 * 100
   133  	getKlaynumGets       = 1000
   134  )
   135  
   136  var getKlayOptions = [...]struct {
   137  	name          string
   138  	valueLength   int
   139  	numInsertions int
   140  	numGets       int
   141  	opts          *opt.Options
   142  }{
   143  	{"X1", getKlayValueLegnth, getKlayNumInsertions, getKlaynumGets, getKlayLDBOptionsForGetX(1)},
   144  	{"X2", getKlayValueLegnth, getKlayNumInsertions, getKlaynumGets, getKlayLDBOptionsForGetX(2)},
   145  	{"X4", getKlayValueLegnth, getKlayNumInsertions, getKlaynumGets, getKlayLDBOptionsForGetX(4)},
   146  	{"X8", getKlayValueLegnth, getKlayNumInsertions, getKlaynumGets, getKlayLDBOptionsForGetX(8)},
   147  	//{"X16", getKlayValueLegnth, getKlayNumInsertions, getKlaynumGets, getKlayLDBOptionsForGetX(16)},
   148  	//{"X32", getKlayValueLegnth, getKlayNumInsertions, getKlaynumGets, getKlayLDBOptionsForGetX(32)},
   149  	//{"X64", getKlayValueLegnth, getKlayNumInsertions, getKlaynumGets, getKlayLDBOptionsForGetX(64)},
   150  }
   151  
   152  func Benchmark_KlayOptions_Get_Random(b *testing.B) {
   153  	for _, bm := range getKlayOptions {
   154  		b.Run(bm.name, func(b *testing.B) {
   155  			benchmarkKlayOptionsGet(b, bm.opts, bm.valueLength, bm.numInsertions, bm.numGets, randomRead)
   156  		})
   157  	}
   158  }
   159  
   160  func Benchmark_KlayOptions_Get_Sequential(b *testing.B) {
   161  	for _, bm := range getKlayOptions {
   162  		b.Run(bm.name, func(b *testing.B) {
   163  			benchmarkKlayOptionsGet(b, bm.opts, bm.valueLength, bm.numInsertions, bm.numGets, sequentialRead)
   164  		})
   165  	}
   166  }
   167  
   168  func Benchmark_KlayOptions_Get_Zipf(b *testing.B) {
   169  	for _, bm := range getKlayOptions {
   170  		b.Run(bm.name, func(b *testing.B) {
   171  			benchmarkKlayOptionsGet(b, bm.opts, bm.valueLength, bm.numInsertions, bm.numGets, zipfRead)
   172  		})
   173  	}
   174  }
   175  
   176  ///////////////////////////////////////////////////////////////////////////////////////////
   177  ////////////////////////////// Put Insertion Tests Beginning //////////////////////////////
   178  ///////////////////////////////////////////////////////////////////////////////////////////
   179  
   180  func benchmarkKlayOptionsPut(b *testing.B, opts *opt.Options, valueLength, numInsertions int) {
   181  	b.StopTimer()
   182  
   183  	dir := genTempDirForTestDB(b)
   184  	defer os.RemoveAll(dir)
   185  
   186  	db, err := NewLevelDBWithOption(dir, opts)
   187  	require.NoError(b, err)
   188  	defer db.Close()
   189  
   190  	b.StartTimer()
   191  	for i := 0; i < b.N; i++ {
   192  		for k := 0; k < numInsertions; k++ {
   193  			db.Put(randStrBytes(32), randStrBytes(valueLength))
   194  		}
   195  	}
   196  }
   197  
   198  func Benchmark_KlayOptions_Put(b *testing.B) {
   199  	b.StopTimer()
   200  	b.ReportAllocs()
   201  	const (
   202  		putKlayValueLegnth   = 250
   203  		putKlayNumInsertions = 1000 * 10
   204  	)
   205  
   206  	putKlayOptions := [...]struct {
   207  		name          string
   208  		valueLength   int
   209  		numInsertions int
   210  		opts          *opt.Options
   211  	}{
   212  		{"X1", putKlayValueLegnth, putKlayNumInsertions, getKlayLDBOptionsForPutX(1)},
   213  		{"X2", putKlayValueLegnth, putKlayNumInsertions, getKlayLDBOptionsForPutX(2)},
   214  		{"X4", putKlayValueLegnth, putKlayNumInsertions, getKlayLDBOptionsForPutX(4)},
   215  		{"X8", putKlayValueLegnth, putKlayNumInsertions, getKlayLDBOptionsForPutX(8)},
   216  		//{"X16", putKlayValueLegnth, putKlayNumInsertions, getKlayLDBOptionsForPutX(16)},
   217  		//{"X32", putKlayValueLegnth, putKlayNumInsertions, getKlayLDBOptionsForPutX(32)},
   218  		//{"X64", putKlayValueLegnth, putKlayNumInsertions, getKlayLDBOptionsForPutX(64)},
   219  	}
   220  	for _, bm := range putKlayOptions {
   221  		b.Run(bm.name, func(b *testing.B) {
   222  			benchmarkKlayOptionsPut(b, bm.opts, bm.valueLength, bm.numInsertions)
   223  		})
   224  	}
   225  }
   226  
   227  ///////////////////////////////////////////////////////////////////////////////////////////
   228  ///////////////////////////////// Put Insertion Tests End /////////////////////////////////
   229  ///////////////////////////////////////////////////////////////////////////////////////////
   230  
   231  ///////////////////////////////////////////////////////////////////////////////////////////
   232  ////////////////////////// SHARDED PUT INSERTION TESTS BEGINNING //////////////////////////
   233  ///////////////////////////////////////////////////////////////////////////////////////////
   234  
   235  func removeDirs(dirs []string) {
   236  	for _, dir := range dirs {
   237  		os.RemoveAll(dir)
   238  	}
   239  }
   240  
   241  func genDatabases(b *testing.B, dirs []string, opts *opt.Options) []*levelDB {
   242  	databases := make([]*levelDB, len(dirs), len(dirs))
   243  	for i := 0; i < len(dirs); i++ {
   244  		databases[i], _ = NewLevelDBWithOption(dirs[i], opts)
   245  	}
   246  	return databases
   247  }
   248  
   249  func closeDBs(databases []*levelDB) {
   250  	for _, db := range databases {
   251  		db.Close()
   252  	}
   253  }
   254  
   255  func genKeysAndValues(valueLength, numInsertions int) ([][]byte, [][]byte) {
   256  	keys := make([][]byte, numInsertions, numInsertions)
   257  	values := make([][]byte, numInsertions, numInsertions)
   258  	for i := 0; i < numInsertions; i++ {
   259  		keys[i] = randStrBytes(32)
   260  		values[i] = randStrBytes(valueLength)
   261  	}
   262  	return keys, values
   263  }
   264  
   265  ///////////////////////////////////////////////////////////////////////////////////////////
   266  ///////////////////////////// Batch Insertion Tests Beginning /////////////////////////////
   267  ///////////////////////////////////////////////////////////////////////////////////////////
   268  
   269  func benchmarkKlayOptionsBatch(b *testing.B, opts *opt.Options, valueLength, numInsertions int) {
   270  	b.StopTimer()
   271  	dir := genTempDirForTestDB(b)
   272  	defer os.RemoveAll(dir)
   273  
   274  	db, err := NewLevelDBWithOption(dir, opts)
   275  	require.NoError(b, err)
   276  	defer db.Close()
   277  
   278  	for i := 0; i < b.N; i++ {
   279  		b.StopTimer()
   280  		keys, values := genKeysAndValues(valueLength, numInsertions)
   281  		b.StartTimer()
   282  		batch := db.NewBatch()
   283  		for k := 0; k < numInsertions; k++ {
   284  			batch.Put(keys[k], values[k])
   285  		}
   286  		batch.Write()
   287  	}
   288  }
   289  
   290  func Benchmark_KlayOptions_Batch(b *testing.B) {
   291  	b.StopTimer()
   292  	b.ReportAllocs()
   293  
   294  	const (
   295  		batchValueLength       = 250
   296  		batchKlayNumInsertions = 1000 * 10
   297  	)
   298  
   299  	putKlayOptions := [...]struct {
   300  		name          string
   301  		valueLength   int
   302  		numInsertions int
   303  		opts          *opt.Options
   304  	}{
   305  		{"X1", batchValueLength, batchKlayNumInsertions, getKlayLDBOptionsForBatchX(1)},
   306  		{"X2", batchValueLength, batchKlayNumInsertions, getKlayLDBOptionsForBatchX(2)},
   307  		{"X4", batchValueLength, batchKlayNumInsertions, getKlayLDBOptionsForBatchX(4)},
   308  		{"X8", batchValueLength, batchKlayNumInsertions, getKlayLDBOptionsForBatchX(8)},
   309  		//{"X16", batchValueLength, batchKlayNumInsertions, getKlayLDBOptionsForBatchX(16)},
   310  		//{"X32", batchValueLength, batchKlayNumInsertions, getKlayLDBOptionsForBatchX(32)},
   311  		//{"X64", batchValueLength, batchKlayNumInsertions, getKlayLDBOptionsForBatchX(64)},
   312  	}
   313  	for _, bm := range putKlayOptions {
   314  		b.Run(bm.name, func(b *testing.B) {
   315  			benchmarkKlayOptionsBatch(b, bm.opts, bm.valueLength, bm.numInsertions)
   316  		})
   317  	}
   318  }
   319  
   320  // TODO-Klaytn = Add a test for checking GoRoutine Overhead
   321  
   322  ///////////////////////////////////////////////////////////////////////////////////////////
   323  /////////////////////////// Batch Insertion Tests End /////////////////////////////////////
   324  ///////////////////////////////////////////////////////////////////////////////////////////
   325  
   326  ///////////////////////////////////////////////////////////////////////////////////////////
   327  ////////////////////////// Ideal Batch Size Tests Begins //////////////////////////////////
   328  ///////////////////////////////////////////////////////////////////////////////////////////
   329  
   330  type idealBatchBM struct {
   331  	name      string
   332  	totalSize int
   333  	batchSize int
   334  	rowSize   int
   335  }
   336  
   337  func benchmarkIdealBatchSize(b *testing.B, bm idealBatchBM) {
   338  	b.StopTimer()
   339  
   340  	dir := genTempDirForTestDB(b)
   341  	defer os.RemoveAll(dir)
   342  
   343  	opts := getKlayLDBOptions()
   344  	db, err := NewLevelDBWithOption(dir, opts)
   345  	require.NoError(b, err)
   346  	defer db.Close()
   347  
   348  	b.StartTimer()
   349  
   350  	var wg sync.WaitGroup
   351  	numBatches := bm.totalSize / bm.batchSize
   352  	wg.Add(numBatches)
   353  	for i := 0; i < numBatches; i++ {
   354  		batch := db.NewBatch()
   355  		for k := 0; k < bm.batchSize; k++ {
   356  			batch.Put(randStrBytes(32), randStrBytes(bm.rowSize))
   357  		}
   358  
   359  		go func(currBatch Batch) {
   360  			defer wg.Done()
   361  			currBatch.Write()
   362  		}(batch)
   363  	}
   364  	wg.Wait()
   365  }
   366  
   367  func Benchmark_IdealBatchSize(b *testing.B) {
   368  	b.StopTimer()
   369  	// please change below rowSize to change the size of an input row
   370  	// key = 32 bytes, value = rowSize bytes
   371  	const rowSize = 250
   372  
   373  	benchmarks := []idealBatchBM{
   374  		// to run test with total size smaller than 1,000 rows
   375  		// go test -bench=Benchmark_IdealBatchSize/SmallBatches
   376  		{"SmallBatches_100Rows_10Batch_250Bytes", 100, 10, rowSize},
   377  		{"SmallBatches_100Rows_20Batch_250Bytes", 100, 20, rowSize},
   378  		{"SmallBatches_100Rows_25Batch_250Bytes", 100, 25, rowSize},
   379  		{"SmallBatches_100Rows_50Batch_250Bytes", 100, 50, rowSize},
   380  		{"SmallBatches_100Rows_100Batch_250Bytes", 100, 100, rowSize},
   381  
   382  		{"SmallBatches_200Rows_10Batch_250Bytes", 200, 10, rowSize},
   383  		{"SmallBatches_200Rows_20Batch_250Bytes", 200, 20, rowSize},
   384  		{"SmallBatches_200Rows_25Batch_250Bytes", 200, 25, rowSize},
   385  		{"SmallBatches_200Rows_50Batch_250Bytes", 200, 50, rowSize},
   386  		{"SmallBatches_200Rows_100Batch_250Bytes", 200, 100, rowSize},
   387  
   388  		{"SmallBatches_400Rows_10Batch_250Bytes", 400, 10, rowSize},
   389  		{"SmallBatches_400Rows_20Batch_250Bytes", 400, 20, rowSize},
   390  		{"SmallBatches_400Rows_25Batch_250Bytes", 400, 25, rowSize},
   391  		{"SmallBatches_400Rows_50Batch_250Bytes", 400, 50, rowSize},
   392  		{"SmallBatches_400Rows_100Batch_250Bytes", 400, 100, rowSize},
   393  
   394  		{"SmallBatches_800Rows_10Batch_250Bytes", 800, 10, rowSize},
   395  		{"SmallBatches_800Rows_20Batch_250Bytes", 800, 20, rowSize},
   396  		{"SmallBatches_800Rows_25Batch_250Bytes", 800, 25, rowSize},
   397  		{"SmallBatches_800Rows_50Batch_250Bytes", 800, 50, rowSize},
   398  		{"SmallBatches_800Rows_100Batch_250Bytes", 800, 100, rowSize},
   399  
   400  		// to run test with total size between than 1k rows ~ 10k rows
   401  		// go test -bench=Benchmark_IdealBatchSize/LargeBatches
   402  		{"LargeBatches_1kRows_100Batch_250Bytes", 1000, 100, rowSize},
   403  		{"LargeBatches_1kRows_200Batch_250Bytes", 1000, 200, rowSize},
   404  		{"LargeBatches_1kRows_250Batch_250Bytes", 1000, 250, rowSize},
   405  		{"LargeBatches_1kRows_500Batch_250Bytes", 1000, 500, rowSize},
   406  		{"LargeBatches_1kRows_1000Batch_250Bytes", 1000, 1000, rowSize},
   407  
   408  		{"LargeBatches_2kRows_100Batch_250Bytes", 2000, 100, rowSize},
   409  		{"LargeBatches_2kRows_200Batch_250Bytes", 2000, 200, rowSize},
   410  		{"LargeBatches_2kRows_250Batch_250Bytes", 2000, 250, rowSize},
   411  		{"LargeBatches_2kRows_500Batch_250Bytes", 2000, 500, rowSize},
   412  		{"LargeBatches_2kRows_1000Batch_250Bytes", 2000, 1000, rowSize},
   413  
   414  		{"LargeBatches_4kRows_100Batch_250Bytes", 4000, 100, rowSize},
   415  		{"LargeBatches_4kRows_200Batch_250Bytes", 4000, 200, rowSize},
   416  		{"LargeBatches_4kRows_250Batch_250Bytes", 4000, 250, rowSize},
   417  		{"LargeBatches_4kRows_500Batch_250Bytes", 4000, 500, rowSize},
   418  		{"LargeBatches_4kRows_1000Batch_250Bytes", 4000, 1000, rowSize},
   419  
   420  		{"LargeBatches_8kRows_100Batch_250Bytes", 8000, 100, rowSize},
   421  		{"LargeBatches_8kRows_200Batch_250Bytes", 8000, 200, rowSize},
   422  		{"LargeBatches_8kRows_250Batch_250Bytes", 8000, 250, rowSize},
   423  		{"LargeBatches_8kRows_500Batch_250Bytes", 8000, 500, rowSize},
   424  		{"LargeBatches_8kRows_1000Batch_250Bytes", 8000, 1000, rowSize},
   425  	}
   426  
   427  	for _, bm := range benchmarks {
   428  		b.Run(bm.name, func(b *testing.B) {
   429  			for m := 0; m < b.N; m++ {
   430  				benchmarkIdealBatchSize(b, bm)
   431  			}
   432  		})
   433  	}
   434  }
   435  
   436  const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789"
   437  const (
   438  	letterIdxBits = 6                    // 6 bits to represent a letter index
   439  	letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
   440  	letterIdxMax  = 63 / letterIdxBits   // # of letter indices fitting in 63 bits
   441  )
   442  
   443  func randStrBytes(n int) []byte {
   444  	src := rand.NewSource(time.Now().UnixNano())
   445  	b := make([]byte, n)
   446  	// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
   447  	for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
   448  		if remain == 0 {
   449  			cache, remain = src.Int63(), letterIdxMax
   450  		}
   451  		if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
   452  			b[i] = letterBytes[idx]
   453  			i--
   454  		}
   455  		cache >>= letterIdxBits
   456  		remain--
   457  	}
   458  
   459  	return b
   460  }
   461  
   462  func getShardForTest(keys [][]byte, index, numShards int) int64 {
   463  	return int64(index % numShards)
   464  	// TODO-Klaytn: CHANGE BELOW LOGIC FROM ROUND-ROBIN TO USE getShardForTest
   465  	//key := keys[index]
   466  	//hashString := strings.TrimPrefix(common.Bytes2Hex(key),"0x")
   467  	//if len(hashString) > 15 {
   468  	//	hashString = hashString[:15]
   469  	//}
   470  	//seed, _ := strconv.ParseInt(hashString, 16, 64)
   471  	//shard := seed % int64(numShards)
   472  	//
   473  	//return shard
   474  }