github.com/klaytn/klaytn@v1.12.1/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  	defer dstBatch.Release()
    40  
    41  	// vars for log
    42  	start := time.Now()
    43  	fetched := 0
    44  
    45  	for fetched = 0; srcIter.Next(); fetched++ {
    46  		// fetch keys and values
    47  		// Contents of srcIter.Key() and srcIter.Value() should not be modified, and
    48  		// only valid until the next call to Next.
    49  		key := make([]byte, len(srcIter.Key()))
    50  		val := make([]byte, len(srcIter.Value()))
    51  		copy(key, srcIter.Key())
    52  		copy(val, srcIter.Value())
    53  
    54  		// write fetched keys and values to DB
    55  		// If dstDB is dynamoDB, Put will Write when the number items reach dynamoBatchSize.
    56  		if err := dstBatch.Put(key, val); err != nil {
    57  			return errors.WithMessage(err, "failed to put batch")
    58  		}
    59  
    60  		if dstBatch.ValueSize() > IdealBatchSize {
    61  			if err := dstBatch.Write(); err != nil {
    62  				return err
    63  			}
    64  			dstBatch.Reset()
    65  		}
    66  
    67  		// make a report
    68  		if fetched%reportCycle == 0 {
    69  			logger.Info("DB migrated",
    70  				"db", name, "fetchedTotal", fetched, "elapsedTotal", time.Since(start))
    71  		}
    72  
    73  		// check for quit signal from OS
    74  		select {
    75  		case <-quit:
    76  			logger.Warn("exit called", "db", name, "fetchedTotal", fetched, "elapsedTotal", time.Since(start))
    77  			return nil
    78  		default:
    79  		}
    80  	}
    81  
    82  	if err := dstBatch.Write(); err != nil {
    83  		return errors.WithMessage(err, "failed to write items")
    84  	}
    85  	dstBatch.Reset()
    86  
    87  	logger.Info("Finish DB migration", "db", name, "fetchedTotal", fetched, "elapsedTotal", time.Since(start))
    88  
    89  	srcIter.Release()
    90  	if err := srcIter.Error(); err != nil { // any accumulated error from iterator
    91  		return errors.WithMessage(err, "failed to iterate")
    92  	}
    93  
    94  	return nil
    95  }
    96  
    97  // StartDBMigration migrates a DB to another DB.
    98  // (e.g. LevelDB -> LevelDB, LevelDB -> BadgerDB, LevelDB -> DynamoDB)
    99  // Do not migrate db while a node is executing.
   100  func (dbm *databaseManager) StartDBMigration(dstdbm DBManager) error {
   101  	// settings for quit signal from os
   102  	quit := make(chan struct{})
   103  	go func() {
   104  		sigc := make(chan os.Signal, 1)
   105  		signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM)
   106  		defer signal.Stop(sigc)
   107  		<-sigc
   108  		logger.Info("Got interrupt, shutting down...")
   109  		close(quit)
   110  		for i := 10; i > 0; i-- {
   111  			<-sigc
   112  			if i > 1 {
   113  				logger.Info("Already shutting down, interrupt more to panic.", "times", i-1)
   114  			}
   115  		}
   116  	}()
   117  
   118  	// from non single DB
   119  	if !dbm.config.SingleDB {
   120  		errChan := make(chan error, databaseEntryTypeSize)
   121  		for et := MiscDB; et < databaseEntryTypeSize; et++ {
   122  			srcDB := dbm.getDatabase(et)
   123  
   124  			dstDB := dstdbm.getDatabase(MiscDB)
   125  			if !dstdbm.GetDBConfig().SingleDB {
   126  				dstDB = dstdbm.getDatabase(et)
   127  			}
   128  
   129  			if srcDB == nil {
   130  				logger.Warn("skip nil src db", "db", dbBaseDirs[et])
   131  				errChan <- nil
   132  				continue
   133  			}
   134  
   135  			if dstDB == nil {
   136  				logger.Warn("skip nil dst db", "db", dbBaseDirs[et])
   137  				errChan <- nil
   138  				continue
   139  			}
   140  
   141  			dbIdx := et
   142  			go func() {
   143  				errChan <- copyDB(dbBaseDirs[dbIdx], srcDB, dstDB, quit)
   144  			}()
   145  		}
   146  
   147  		for et := MiscDB; et < databaseEntryTypeSize; et++ {
   148  			err := <-errChan
   149  			if err != nil {
   150  				logger.Error("copyDB got an error", "err", err)
   151  			}
   152  		}
   153  
   154  		// Reset state trie DB path if migrated state trie path ("statetrie_migrated_XXXXXX") is set
   155  		dstdbm.setDBDir(DBEntryType(StateTrieDB), "")
   156  
   157  		return nil
   158  	}
   159  
   160  	// single DB -> single DB
   161  	srcDB := dbm.getDatabase(0)
   162  	dstDB := dstdbm.getDatabase(0)
   163  
   164  	if err := copyDB("single", srcDB, dstDB, quit); err != nil {
   165  		return err
   166  	}
   167  
   168  	// If the current src DB is misc DB, clear all db dir on dst
   169  	// TODO: If DB Migration supports non-single db, change the checking logic
   170  	if path.Base(dbm.config.Dir) == dbBaseDirs[MiscDB] {
   171  		for i := uint8(MiscDB); i < uint8(databaseEntryTypeSize); i++ {
   172  			dstdbm.setDBDir(DBEntryType(i), "")
   173  		}
   174  	}
   175  
   176  	return nil
   177  }