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