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  }