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