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 }