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  }