github.com/klaytn/klaytn@v1.10.2/storage/database/db_migration.go (about)

     1  // Copyright 2020 The klaytn Authors
     2  // This file is part of the klaytn library.
     3  //
     4  // go-ethereum is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU 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  // go-ethereum 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 General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU General Public License
    15  // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package database
    18  
    19  import (
    20  	"os"
    21  	"os/signal"
    22  	"path"
    23  	"syscall"
    24  	"time"
    25  
    26  	"github.com/pkg/errors"
    27  )
    28  
    29  const (
    30  	reportCycle = IdealBatchSize * 20
    31  )
    32  
    33  // copyDB migrates a DB to another DB.
    34  // This feature uses Iterator. A src DB should have implementation of Iteratee to use this function.
    35  func copyDB(name string, srcDB, dstDB Database, quit chan struct{}) error {
    36  	// create src iterator and dst batch
    37  	srcIter := srcDB.NewIterator(nil, nil)
    38  	dstBatch := dstDB.NewBatch()
    39  
    40  	// vars for log
    41  	start := time.Now()
    42  	fetched := 0
    43  
    44  	for fetched = 0; srcIter.Next(); fetched++ {
    45  		// fetch keys and values
    46  		// Contents of srcIter.Key() and srcIter.Value() should not be modified, and
    47  		// only valid until the next call to Next.
    48  		key := make([]byte, len(srcIter.Key()))
    49  		val := make([]byte, len(srcIter.Value()))
    50  		copy(key, srcIter.Key())
    51  		copy(val, srcIter.Value())
    52  
    53  		// write fetched keys and values to DB
    54  		// If dstDB is dynamoDB, Put will Write when the number items reach dynamoBatchSize.
    55  		if err := dstBatch.Put(key, val); err != nil {
    56  			return errors.WithMessage(err, "failed to put batch")
    57  		}
    58  
    59  		if dstBatch.ValueSize() > IdealBatchSize {
    60  			if err := dstBatch.Write(); err != nil {
    61  				return err
    62  			}
    63  			dstBatch.Reset()
    64  		}
    65  
    66  		// make a report
    67  		if fetched%reportCycle == 0 {
    68  			logger.Info("DB migrated",
    69  				"db", name, "fetchedTotal", fetched, "elapsedTotal", time.Since(start))
    70  		}
    71  
    72  		// check for quit signal from OS
    73  		select {
    74  		case <-quit:
    75  			logger.Warn("exit called", "db", name, "fetchedTotal", fetched, "elapsedTotal", time.Since(start))
    76  			return nil
    77  		default:
    78  		}
    79  	}
    80  
    81  	if err := dstBatch.Write(); err != nil {
    82  		return errors.WithMessage(err, "failed to write items")
    83  	}
    84  	dstBatch.Reset()
    85  
    86  	logger.Info("Finish DB migration", "db", name, "fetchedTotal", fetched, "elapsedTotal", time.Since(start))
    87  
    88  	srcIter.Release()
    89  	if err := srcIter.Error(); err != nil { // any accumulated error from iterator
    90  		return errors.WithMessage(err, "failed to iterate")
    91  	}
    92  
    93  	return nil
    94  }
    95  
    96  // StartDBMigration migrates a DB to another DB.
    97  // (e.g. LevelDB -> LevelDB, LevelDB -> BadgerDB, LevelDB -> DynamoDB)
    98  // Do not migrate db while a node is executing.
    99  func (dbm *databaseManager) StartDBMigration(dstdbm DBManager) error {
   100  	// settings for quit signal from os
   101  	quit := make(chan struct{})
   102  	go func() {
   103  		sigc := make(chan os.Signal, 1)
   104  		signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM)
   105  		defer signal.Stop(sigc)
   106  		<-sigc
   107  		logger.Info("Got interrupt, shutting down...")
   108  		close(quit)
   109  		for i := 10; i > 0; i-- {
   110  			<-sigc
   111  			if i > 1 {
   112  				logger.Info("Already shutting down, interrupt more to panic.", "times", i-1)
   113  			}
   114  		}
   115  	}()
   116  
   117  	// from non single DB
   118  	if !dbm.config.SingleDB {
   119  		errChan := make(chan error, databaseEntryTypeSize)
   120  		for et := MiscDB; et < databaseEntryTypeSize; et++ {
   121  			srcDB := dbm.getDatabase(et)
   122  
   123  			dstDB := dstdbm.getDatabase(MiscDB)
   124  			if !dstdbm.GetDBConfig().SingleDB {
   125  				dstDB = dstdbm.getDatabase(et)
   126  			}
   127  
   128  			if srcDB == nil {
   129  				logger.Warn("skip nil src db", "db", dbBaseDirs[et])
   130  				errChan <- nil
   131  				continue
   132  			}
   133  
   134  			if dstDB == nil {
   135  				logger.Warn("skip nil dst db", "db", dbBaseDirs[et])
   136  				errChan <- nil
   137  				continue
   138  			}
   139  
   140  			dbIdx := et
   141  			go func() {
   142  				errChan <- copyDB(dbBaseDirs[dbIdx], srcDB, dstDB, quit)
   143  			}()
   144  		}
   145  
   146  		for et := MiscDB; et < databaseEntryTypeSize; et++ {
   147  			err := <-errChan
   148  			if err != nil {
   149  				logger.Error("copyDB got an error", "err", err)
   150  			}
   151  		}
   152  
   153  		// Reset state trie DB path if migrated state trie path ("statetrie_migrated_XXXXXX") is set
   154  		dstdbm.setDBDir(DBEntryType(StateTrieDB), "")
   155  
   156  		return nil
   157  	}
   158  
   159  	// single DB -> single DB
   160  	srcDB := dbm.getDatabase(0)
   161  	dstDB := dstdbm.getDatabase(0)
   162  
   163  	if err := copyDB("single", srcDB, dstDB, quit); err != nil {
   164  		return err
   165  	}
   166  
   167  	// If the current src DB is misc DB, clear all db dir on dst
   168  	// TODO: If DB Migration supports non-single db, change the checking logic
   169  	if path.Base(dbm.config.Dir) == dbBaseDirs[MiscDB] {
   170  		for i := uint8(MiscDB); i < uint8(databaseEntryTypeSize); i++ {
   171  			dstdbm.setDBDir(DBEntryType(i), "")
   172  		}
   173  	}
   174  
   175  	return nil
   176  }