github.com/klaytn/klaytn@v1.12.1/storage/database/sharded_database_test.go (about) 1 // Copyright 2021 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 package database 17 18 import ( 19 "bytes" 20 "context" 21 "os" 22 "reflect" 23 "sort" 24 "sync" 25 "testing" 26 27 "github.com/klaytn/klaytn/common" 28 "github.com/stretchr/testify/assert" 29 ) 30 31 var ShardedDBConfig = []*DBConfig{ 32 {DBType: LevelDB, SingleDB: false, NumStateTrieShards: 2, ParallelDBWrite: true}, 33 {DBType: LevelDB, SingleDB: false, NumStateTrieShards: 4, ParallelDBWrite: true}, 34 } 35 36 // testIterator tests if given iterator iterates all entries 37 func testIterator(t *testing.T, checkOrder bool, entryNums []uint, dbConfig []*DBConfig, entriesFromIterator func(t *testing.T, db shardedDB, entryNum uint) []common.Entry) { 38 for _, entryNum := range entryNums { 39 entries := common.CreateEntries(int(entryNum)) 40 dbs := make([]shardedDB, len(dbConfig)) 41 42 // create DB and write data for testing 43 for i, config := range dbConfig { 44 config.Dir, _ = os.MkdirTemp(os.TempDir(), "test-shardedDB-iterator") 45 defer func(dir string) { 46 if err := os.RemoveAll(dir); err != nil { 47 t.Fatalf("fail to delete file %v", err) 48 } 49 }(config.Dir) 50 51 // create sharded DB 52 db, err := newShardedDB(config, 0, config.NumStateTrieShards) 53 if err != nil { 54 t.Log("Error occured while creating DB :", err) 55 t.FailNow() 56 } 57 dbs[i] = *db 58 59 // write entries data in DB 60 batch := db.NewBatch() 61 for _, entry := range entries { 62 assert.NoError(t, batch.Put(entry.Key, entry.Val)) 63 } 64 assert.NoError(t, batch.Write()) 65 } 66 67 // sort entries for each compare 68 sort.Slice(entries, func(i, j int) bool { return bytes.Compare(entries[i].Key, entries[j].Key) < 0 }) 69 70 // get data from iterator and compare 71 for _, db := range dbs { 72 // create iterator 73 entriesFromIt := entriesFromIterator(t, db, entryNum) 74 if !checkOrder { 75 sort.Slice(entriesFromIt, func(i, j int) bool { return bytes.Compare(entriesFromIt[i].Key, entriesFromIt[j].Key) < 0 }) 76 } 77 78 // compare if entries generated and entries from iterator is same 79 assert.Equal(t, len(entries), len(entriesFromIt)) 80 assert.True(t, reflect.DeepEqual(entries, entriesFromIt)) 81 } 82 } 83 } 84 85 // TestShardedDBIterator tests if shardedDBIterator iterates all entries with diverse shard size 86 func TestShardedDBIterator(t *testing.T) { 87 testIterator(t, true, []uint{100}, ShardedDBConfig, newShardedDBIterator) 88 } 89 90 // TestShardedDBIteratorUnsorted tests if shardedDBIteratorUnsorted iterates all entries with diverse shard size 91 func TestShardedDBIteratorUnsorted(t *testing.T) { 92 testIterator(t, false, []uint{100}, ShardedDBConfig, newShardedDBIteratorUnsorted) 93 } 94 95 // TestShardedDBParallelIterator tests if shardedDBParallelIterator iterates all entries with diverse shard size 96 func TestShardedDBParallelIterator(t *testing.T) { 97 testIterator(t, false, []uint{100}, ShardedDBConfig, newShardedDBParallelIterator) 98 } 99 100 // TestShardedDBIteratorSize tests if shardedDBIterator iterates all entries for different 101 // entry sizes 102 func TestShardedDBIteratorSize(t *testing.T) { 103 config := ShardedDBConfig[0] 104 size := config.NumStateTrieShards 105 testIterator(t, true, []uint{size - 1, size, size + 1}, []*DBConfig{config}, newShardedDBIterator) 106 } 107 108 // TestShardedDBIteratorUnsortedSize tests if shardedDBIteratorUnsorted iterates all entries 109 func TestShardedDBIteratorUnsortedSize(t *testing.T) { 110 config := ShardedDBConfig[0] 111 size := config.NumStateTrieShards 112 testIterator(t, false, []uint{size - 1, size, size + 1}, []*DBConfig{config}, newShardedDBIteratorUnsorted) 113 } 114 115 // TestShardedDBParallelIteratorSize tests if shardedDBParallelIterator iterates all entries 116 func TestShardedDBParallelIteratorSize(t *testing.T) { 117 config := ShardedDBConfig[0] 118 size := config.NumStateTrieShards 119 testIterator(t, false, []uint{size - 1, size, size + 1}, []*DBConfig{config}, newShardedDBParallelIterator) 120 } 121 122 func newShardedDBIterator(t *testing.T, db shardedDB, entryNum uint) []common.Entry { 123 entries := make([]common.Entry, 0, entryNum) 124 it := db.NewIterator(nil, nil) 125 126 for it.Next() { 127 entries = append(entries, common.Entry{Key: it.Key(), Val: it.Value()}) 128 } 129 it.Release() 130 assert.NoError(t, it.Error()) 131 return entries 132 } 133 134 func newShardedDBIteratorUnsorted(t *testing.T, db shardedDB, entryNum uint) []common.Entry { 135 entries := make([]common.Entry, 0, entryNum) 136 it := db.NewIteratorUnsorted(nil, nil) 137 138 for it.Next() { 139 entries = append(entries, common.Entry{Key: it.Key(), Val: it.Value()}) 140 } 141 it.Release() 142 assert.NoError(t, it.Error()) 143 return entries 144 } 145 146 func newShardedDBParallelIterator(t *testing.T, db shardedDB, entryNum uint) []common.Entry { 147 entries := make([]common.Entry, 0, entryNum) // store all items 148 var l sync.RWMutex // mutex for entries 149 150 // create chan Iterator and get channels 151 it := db.NewParallelIterator(context.Background(), nil, nil, nil) 152 chans := it.Channels() 153 154 // listen all channels and get key/value 155 done := make(chan struct{}) 156 for _, ch := range chans { 157 go func(ch chan common.Entry) { 158 for e := range ch { 159 l.Lock() 160 entries = append(entries, e) 161 l.Unlock() 162 } 163 done <- struct{}{} // tell 164 }(ch) 165 } 166 // wait for all iterators to finish 167 for range chans { 168 <-done 169 } 170 close(done) 171 it.Release() 172 return entries 173 } 174 175 func testShardedIterator_Release(t *testing.T, entryNum int, checkFunc func(db shardedDB)) { 176 entries := common.CreateEntries(entryNum) 177 178 // create DB and write data for testing 179 for _, config := range ShardedDBConfig { 180 config.Dir, _ = os.MkdirTemp(os.TempDir(), "test-shardedDB-iterator") 181 defer func(dir string) { 182 if err := os.RemoveAll(dir); err != nil { 183 t.Fatalf("fail to delete file %v", err) 184 } 185 }(config.Dir) 186 187 // create sharded DB 188 db, err := newShardedDB(config, MiscDB, config.NumStateTrieShards) 189 if err != nil { 190 t.Log("Error occured while creating DB :", err) 191 t.FailNow() 192 } 193 194 // write entries data in DB 195 batch := db.NewBatch() 196 for _, entry := range entries { 197 assert.NoError(t, batch.Put(entry.Key, entry.Val)) 198 } 199 assert.NoError(t, batch.Write()) 200 201 // check if Release quits iterator 202 checkFunc(*db) 203 } 204 } 205 206 func TestShardedDBIterator_Release(t *testing.T) { 207 testShardedIterator_Release(t, shardedDBCombineChanSize+10, func(db shardedDB) { 208 // Next() returns True if Release() is not called 209 { 210 it := db.NewIterator(nil, nil) 211 defer it.Release() 212 213 // check if data exists 214 for i := 0; i < shardedDBCombineChanSize+1; i++ { 215 assert.True(t, it.Next()) 216 } 217 } 218 219 // Next() returns False if Release() is called 220 { 221 it := db.NewIterator(nil, nil) 222 it.Release() 223 224 // flush data in channel 225 for i := 0; i < shardedDBCombineChanSize+1; i++ { 226 it.Next() 227 } 228 229 // check if Next returns false 230 assert.False(t, it.Next()) 231 } 232 }) 233 } 234 235 func TestShardedDBIteratorUnsorted_Release(t *testing.T) { 236 testShardedIterator_Release(t, shardedDBCombineChanSize+10, func(db shardedDB) { 237 // Next() returns True if Release() is not called 238 { 239 it := db.NewIteratorUnsorted(nil, nil) 240 defer it.Release() 241 242 // check if data exists 243 for i := 0; i < shardedDBCombineChanSize+1; i++ { 244 assert.True(t, it.Next()) 245 } 246 } 247 248 // Next() returns False if Release() is called 249 { 250 it := db.NewIteratorUnsorted(nil, nil) 251 it.Release() 252 253 // flush data in channel 254 for i := 0; i < shardedDBCombineChanSize+1; i++ { 255 it.Next() 256 } 257 258 // check if Next returns false 259 assert.False(t, it.Next()) 260 } 261 }) 262 } 263 264 func TestShardedDBParallelIterator_Release(t *testing.T) { 265 testShardedIterator_Release(t, 266 int(ShardedDBConfig[len(ShardedDBConfig)-1].NumStateTrieShards*shardedDBSubChannelSize*2), 267 func(db shardedDB) { 268 // Next() returns True if Release() is not called 269 { 270 it := db.NewParallelIterator(context.Background(), nil, nil, nil) 271 defer it.Release() 272 273 for _, ch := range it.Channels() { 274 // check if channel is not closed 275 for i := 0; i < shardedDBSubChannelSize+1; i++ { 276 e, ok := <-ch 277 assert.NotNil(t, e) 278 assert.True(t, ok) 279 } 280 } 281 } 282 283 // Next() returns False if Release() is called 284 { 285 it := db.NewParallelIterator(context.Background(), nil, nil, nil) 286 it.Release() 287 for _, ch := range it.Channels() { 288 289 // flush data in channel 290 for i := 0; i < shardedDBSubChannelSize+1; i++ { 291 <-ch 292 } 293 294 // check if channel is closed 295 _, ok := <-ch 296 assert.False(t, ok) 297 } 298 } 299 }) 300 }