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 }