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

     1  // Copyright 2019 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  	"errors"
    21  	"fmt"
    22  	"os"
    23  	"testing"
    24  
    25  	"github.com/klaytn/klaytn/common"
    26  	"github.com/stretchr/testify/assert"
    27  )
    28  
    29  const (
    30  	// numDataInsertions is the amount of data to be pre-stored in the DB before it is read
    31  	numDataInsertions = 1000 * 1000 * 2
    32  	// readArea is the range of data to be attempted to read from the data stored in the db.
    33  	readArea = numDataInsertions / 512
    34  	// To read the data cached in the DB, the most recently input data of the DB should be read.
    35  	// This is the offset to specify the starting point of the data to read.
    36  	readCacheOffset = numDataInsertions - readArea
    37  	// primeNumber is used as an interval for reading data.
    38  	// It is a number used to keep the data from being read consecutively.
    39  	// This interval is used because the prime number is small relative to any number except for its multiple,
    40  	// so you can access the whole range of data by cycling the data by a small number of intervals.
    41  	primeNumber = 71887
    42  	// levelDBMemDBSize is the size of internal memory database of LevelDB. Data is first saved to memDB and then moved to persistent storage.
    43  	levelDBMemDBSize = 64
    44  	// numOfGet is the number read per goroutine for the parallel read test.
    45  	numberOfGet = 10000
    46  )
    47  
    48  // initTestDB creates the db and inputs the data in db for valSize
    49  func initTestDB(valueSize int) (string, Database, [][]byte, error) {
    50  	dir, err := os.MkdirTemp("", "bench-DB")
    51  	if err != nil {
    52  		return "", nil, nil, errors.New(fmt.Sprintf("can't create temporary directory: %v", err))
    53  	}
    54  	dbc := &DBConfig{Dir: dir, DBType: LevelDB, LevelDBCacheSize: levelDBMemDBSize, OpenFilesLimit: 0}
    55  	db, err := newDatabase(dbc, 0)
    56  	if err != nil {
    57  		return "", nil, nil, errors.New(fmt.Sprintf("can't create database: %v", err))
    58  	}
    59  	keys, values := genKeysAndValues(valueSize, numDataInsertions)
    60  
    61  	for i, key := range keys {
    62  		if err := db.Put(key, values[i]); err != nil {
    63  			return "", nil, nil, errors.New(fmt.Sprintf("fail to put data to db: %v", err))
    64  		}
    65  	}
    66  
    67  	return dir, db, keys, nil
    68  }
    69  
    70  // benchmarkReadDBFromFile is a benchmark function that reads the data stored in the ldb file.
    71  // Reads the initially entered data to read the value stored in the file.
    72  func benchmarkReadDBFromFile(b *testing.B, valueSize int) {
    73  	dir, db, keys, err := initTestDB(valueSize)
    74  	defer os.RemoveAll(dir)
    75  	defer db.Close()
    76  	if err != nil {
    77  		b.Fatalf("database initialization error: %v", err)
    78  	}
    79  
    80  	b.ResetTimer()
    81  	for i := 0; i < b.N; i++ {
    82  		db.Get(keys[(i*primeNumber)%readArea])
    83  	}
    84  }
    85  
    86  // benchmarkReadDBFromMemDB is a benchmark function that reads data stored in memDB.
    87  // Read the data entered later to read the value stored in memDB, not in the disk storage.
    88  func benchmarkReadDBFromMemDB(b *testing.B, valueSize int) {
    89  	dir, db, keys, err := initTestDB(valueSize)
    90  	defer os.RemoveAll(dir)
    91  	defer db.Close()
    92  	if err != nil {
    93  		b.Fatalf("database initialization error: %v", err)
    94  	}
    95  
    96  	b.ResetTimer()
    97  	for i := 0; i < b.N; i++ {
    98  		db.Get(keys[(readCacheOffset)+(i*primeNumber)%readArea])
    99  	}
   100  }
   101  
   102  // getReadDataOptions is a test case for measuring read performance.
   103  var getReadDataOptions = [...]struct {
   104  	name        string
   105  	valueLength int
   106  	testFunc    func(b *testing.B, valueSize int)
   107  }{
   108  	{"DBFromFile1", 1, benchmarkReadDBFromFile},
   109  	{"DBFromFile128", 128, benchmarkReadDBFromFile},
   110  	{"DBFromFile256", 256, benchmarkReadDBFromFile},
   111  	{"DBFromFile512", 512, benchmarkReadDBFromFile},
   112  
   113  	{"DBFromMem1", 1, benchmarkReadDBFromMemDB},
   114  	{"DBFromMem128", 128, benchmarkReadDBFromMemDB},
   115  	{"DBFromMem256", 256, benchmarkReadDBFromMemDB},
   116  	{"DBFromMem512", 512, benchmarkReadDBFromMemDB},
   117  
   118  	{"LRUCacheSingle1", 1, benchmarkLruCacheGetSingle},
   119  	{"LRUCacheSingle128", 128, benchmarkLruCacheGetSingle},
   120  	{"LRUCacheSingle256", 256, benchmarkLruCacheGetSingle},
   121  	{"LRUCacheSingle512", 512, benchmarkLruCacheGetSingle},
   122  
   123  	{"FIFOCacheSingle1", 1, benchmarkFIFOCacheGetSingle},
   124  	{"FIFOCacheSingle128", 128, benchmarkFIFOCacheGetSingle},
   125  	{"FIFOCacheSingle256", 256, benchmarkFIFOCacheGetSingle},
   126  	{"FIFOCacheSingle512", 512, benchmarkFIFOCacheGetSingle},
   127  
   128  	{"LRUCacheParallel1", 1, benchmarkLruCacheCacheGetParallel},
   129  	{"LRUCacheParallel128", 128, benchmarkLruCacheCacheGetParallel},
   130  	{"LRUCacheParallel256", 256, benchmarkLruCacheCacheGetParallel},
   131  	{"LRUCacheParallel512", 512, benchmarkLruCacheCacheGetParallel},
   132  
   133  	{"FIFOCacheParallel1", 1, benchmarkFIFOCacheGetParallel},
   134  	{"FIFOCacheParallel128", 128, benchmarkFIFOCacheGetParallel},
   135  	{"FIFOCacheParallel256", 256, benchmarkFIFOCacheGetParallel},
   136  	{"FIFOCacheParallel512", 512, benchmarkFIFOCacheGetParallel},
   137  }
   138  
   139  // Benchmark_read_data is a benchmark that measures data read performance in DB and cache.
   140  func Benchmark_read_data(b *testing.B) {
   141  	for _, bm := range getReadDataOptions {
   142  		b.Run(bm.name, func(b *testing.B) {
   143  			bm.testFunc(b, bm.valueLength)
   144  		})
   145  	}
   146  }
   147  
   148  // benchmarkFIFOCacheGetParallel measures the performance of the fifoCache when reading data in parallel
   149  func benchmarkFIFOCacheGetParallel(b *testing.B, valueSize int) {
   150  	cache := common.NewCache(common.FIFOCacheConfig{CacheSize: numDataInsertions})
   151  	benchmarkCacheGetParallel(b, cache, valueSize)
   152  }
   153  
   154  // benchmarkLruCacheCacheGetParallel measures the performance of the lruCache when reading data in parallel
   155  func benchmarkLruCacheCacheGetParallel(b *testing.B, valueSize int) {
   156  	cache := common.NewCache(common.LRUConfig{CacheSize: numDataInsertions})
   157  	benchmarkCacheGetParallel(b, cache, valueSize)
   158  }
   159  
   160  // benchmarkCacheGetParallel is a benchmark for measuring performance
   161  // when cacheSize data is entered into the cache and then read in parallel.
   162  func benchmarkCacheGetParallel(b *testing.B, cache common.Cache, valueSize int) {
   163  	hashKeys := initCacheData(cache, valueSize)
   164  	b.ResetTimer()
   165  	b.RunParallel(func(pb *testing.PB) {
   166  		for pb.Next() {
   167  			for i := 0; i < numberOfGet; i++ {
   168  				key := hashKeys[(i*primeNumber)%numDataInsertions]
   169  				cache.Get(key)
   170  			}
   171  		}
   172  	})
   173  }
   174  
   175  // benchmarkFIFOCacheGetSingle is a benchmark to read fifoCache serially.
   176  func benchmarkFIFOCacheGetSingle(b *testing.B, valueSize int) {
   177  	cache := common.NewCache(common.FIFOCacheConfig{CacheSize: numDataInsertions})
   178  	benchmarkCacheGetSingle(b, cache, valueSize)
   179  }
   180  
   181  // benchmarkLruCacheGetSingle is a benchmark to read lruCache serially.
   182  func benchmarkLruCacheGetSingle(b *testing.B, valueSize int) {
   183  	cache := common.NewCache(common.LRUConfig{CacheSize: numDataInsertions})
   184  	benchmarkCacheGetSingle(b, cache, valueSize)
   185  }
   186  
   187  // benchmarkCacheGetSingle is a benchmark for measuring performance
   188  // when cacheSize data is entered into the cache and then serially read.
   189  func benchmarkCacheGetSingle(b *testing.B, cache common.Cache, valueSize int) {
   190  	hashKeys := initCacheData(cache, valueSize)
   191  	b.ResetTimer()
   192  
   193  	for i := 0; i < b.N; i++ {
   194  		key := hashKeys[(i*primeNumber)%numDataInsertions]
   195  		cache.Get(key)
   196  	}
   197  }
   198  
   199  // initCacheData initializes the cache by entering test data into the cache.
   200  func initCacheData(cache common.Cache, valueSize int) []common.Hash {
   201  	keys, values := genKeysAndValues(valueSize, numDataInsertions)
   202  	hashKeys := make([]common.Hash, 0, numDataInsertions)
   203  
   204  	for i, key := range keys {
   205  		var hashKey common.Hash
   206  		copy(hashKey[:], key[:32])
   207  		hashKeys = append(hashKeys, hashKey)
   208  		cache.Add(hashKey, values[i])
   209  	}
   210  
   211  	return hashKeys
   212  }
   213  
   214  // TestLRUShardCacheAddressKey is a test to make sure add and get commands work when using Address as key.
   215  // Cache hit for all data.
   216  func TestLRUShardCacheAddressKey(t *testing.T) {
   217  	cache := common.NewCache(common.LRUShardConfig{CacheSize: 40960, NumShards: 4096})
   218  
   219  	for i := 0; i < 4096; i++ {
   220  		cache.Add(getAddressKey(i), i)
   221  	}
   222  
   223  	for i := 0; i < 4096; i++ {
   224  		data, ok := cache.Get(getAddressKey(i))
   225  		assert.True(t, ok)
   226  		assert.Equal(t, i, data.(int))
   227  	}
   228  }
   229  
   230  // TestLRUShardCacheHashKey is a test to check whether the add and get commands are working
   231  // when the Address created by SetBytesFromFront is used as key. Cache hit for all data.
   232  func TestLRUShardCacheAddressKeyFromFront(t *testing.T) {
   233  	cache := common.NewCache(common.LRUShardConfig{CacheSize: 40960, NumShards: 4096})
   234  
   235  	for i := 0; i < 4096; i++ {
   236  		cache.Add(getAddressKeyFromFront(i), i)
   237  	}
   238  
   239  	for i := 0; i < 4096; i++ {
   240  		data, ok := cache.Get(getAddressKeyFromFront(i))
   241  		assert.True(t, ok)
   242  		assert.Equal(t, i, data.(int))
   243  	}
   244  }
   245  
   246  // TestLRUShardCacheHashKey is a test to see if add and get commands work when using Hash as key.
   247  // Cache hit for all data.
   248  func TestLRUShardCacheHashKey(t *testing.T) {
   249  	cache := common.NewCache(common.LRUShardConfig{CacheSize: 40960, NumShards: 4096})
   250  
   251  	for i := 0; i < 4096; i++ {
   252  		cache.Add(getHashKey(i), i)
   253  	}
   254  
   255  	for i := 0; i < 4096; i++ {
   256  		data, ok := cache.Get(getHashKey(i))
   257  		assert.True(t, ok)
   258  		assert.Equal(t, i, data.(int))
   259  	}
   260  }
   261  
   262  // getHashKey returns an int converted to a Hash.
   263  func getHashKey(i int) common.Hash {
   264  	var byteArray interface{}
   265  	if i>>8 == 0 {
   266  		byteArray = []byte{byte(i)}
   267  	} else {
   268  		byteArray = []byte{byte(i >> 8), byte(i)}
   269  	}
   270  	return common.BytesToHash(byteArray.([]byte))
   271  }
   272  
   273  // getAddressKey returns an int converted to a Address.
   274  func getAddressKey(i int) common.Address {
   275  	var byteArray interface{}
   276  	if i>>8 == 0 {
   277  		byteArray = []byte{byte(i)}
   278  	} else {
   279  		byteArray = []byte{byte(i >> 8), byte(i)}
   280  	}
   281  	return common.BytesToAddress(byteArray.([]byte))
   282  }
   283  
   284  // getAddressKeyFromFront converts an int to an Address and returns it from 0 in the array.
   285  func getAddressKeyFromFront(i int) common.Address {
   286  	var addr common.Address
   287  	var byteArray interface{}
   288  	if i>>8 == 0 {
   289  		byteArray = []byte{byte(i)}
   290  	} else {
   291  		byteArray = []byte{byte(i), byte(i >> 8)}
   292  	}
   293  	addr.SetBytesFromFront(byteArray.([]byte))
   294  	return addr
   295  }