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 }