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 }