github.com/amazechain/amc@v0.1.3/turbo/backup/backup.go (about) 1 package backup 2 3 import ( 4 "context" 5 "encoding/binary" 6 "encoding/hex" 7 "fmt" 8 "github.com/amazechain/amc/utils" 9 "github.com/erigontech/mdbx-go/mdbx" 10 "runtime" 11 "sync" 12 "sync/atomic" 13 "time" 14 15 "github.com/c2h5oh/datasize" 16 "github.com/ledgerwatch/erigon-lib/common/dbg" 17 "github.com/ledgerwatch/erigon-lib/kv" 18 mdbx2 "github.com/ledgerwatch/erigon-lib/kv/mdbx" 19 "github.com/ledgerwatch/log/v3" 20 "golang.org/x/exp/maps" 21 "golang.org/x/sync/errgroup" 22 "golang.org/x/sync/semaphore" 23 ) 24 25 func OpenPair(from, to string, label kv.Label, targetPageSize datasize.ByteSize) (kv.RoDB, kv.RwDB) { 26 const ThreadsHardLimit = 9_000 27 src := mdbx2.NewMDBX(log.New()).Path(from). 28 Label(label). 29 RoTxsLimiter(semaphore.NewWeighted(ThreadsHardLimit)). 30 WithTableCfg(func(_ kv.TableCfg) kv.TableCfg { return kv.TablesCfgByLabel(label) }). 31 Flags(func(flags uint) uint { return flags | mdbx.Readonly | mdbx.Accede }). 32 MustOpen() 33 if targetPageSize <= 0 { 34 targetPageSize = datasize.ByteSize(src.PageSize()) 35 } 36 info, err := src.(*mdbx2.MdbxKV).Env().Info(nil) 37 if err != nil { 38 panic(err) 39 } 40 dst := mdbx2.NewMDBX(log.New()).Path(to). 41 Label(label). 42 PageSize(targetPageSize.Bytes()). 43 MapSize(datasize.ByteSize(info.Geo.Upper)). 44 Flags(func(flags uint) uint { return flags | mdbx.NoMemInit | mdbx.WriteMap }). 45 WithTableCfg(func(_ kv.TableCfg) kv.TableCfg { return kv.TablesCfgByLabel(label) }). 46 MustOpen() 47 return src, dst 48 } 49 50 func Kv2kv(ctx context.Context, src kv.RoDB, dst kv.RwDB, tables []string, readAheadThreads int) error { 51 srcTx, err1 := src.BeginRo(ctx) 52 if err1 != nil { 53 return err1 54 } 55 defer srcTx.Rollback() 56 57 commitEvery := time.NewTicker(5 * time.Minute) 58 defer commitEvery.Stop() 59 logEvery := time.NewTicker(20 * time.Second) 60 defer logEvery.Stop() 61 62 tablesMap := src.AllTables() 63 if len(tables) > 0 { 64 tablesMapCopy := maps.Clone(tablesMap) 65 tablesMap = kv.TableCfg{} 66 for _, name := range tables { 67 tablesMap[name] = tablesMapCopy[name] 68 } 69 } 70 71 for name, b := range tablesMap { 72 if b.IsDeprecated { 73 continue 74 } 75 if err := backupTable(ctx, src, srcTx, dst, name, readAheadThreads, logEvery); err != nil { 76 return err 77 } 78 } 79 log.Info("done") 80 return nil 81 } 82 83 func backupTable(ctx context.Context, src kv.RoDB, srcTx kv.Tx, dst kv.RwDB, table string, readAheadThreads int, logEvery *time.Ticker) error { 84 var total uint64 85 wg := sync.WaitGroup{} 86 defer wg.Wait() 87 warmupCtx, warmupCancel := context.WithCancel(ctx) 88 defer warmupCancel() 89 90 wg.Add(1) 91 go func() { 92 defer wg.Done() 93 WarmupTable(warmupCtx, src, table, log.LvlTrace, readAheadThreads) 94 }() 95 srcC, err := srcTx.Cursor(table) 96 if err != nil { 97 return err 98 } 99 total, _ = srcC.Count() 100 101 dstTx, err1 := dst.BeginRw(ctx) 102 if err1 != nil { 103 return err1 104 } 105 defer dstTx.Rollback() 106 _ = dstTx.ClearBucket(table) 107 108 c, err := dstTx.RwCursor(table) 109 if err != nil { 110 return err 111 } 112 casted, isDupsort := c.(kv.RwCursorDupSort) 113 i := uint64(0) 114 115 for k, v, err := srcC.First(); k != nil; k, v, err = srcC.Next() { 116 if err != nil { 117 return err 118 } 119 120 if isDupsort { 121 if err = casted.AppendDup(k, v); err != nil { 122 panic(err) 123 } 124 } else { 125 if err = c.Append(k, v); err != nil { 126 panic(err) 127 } 128 } 129 130 i++ 131 select { 132 case <-ctx.Done(): 133 return ctx.Err() 134 case <-logEvery.C: 135 var m runtime.MemStats 136 dbg.ReadMemStats(&m) 137 log.Info("Progress", "table", table, "progress", fmt.Sprintf("%.1fm/%.1fm", float64(i)/1_000_000, float64(total)/1_000_000), "key", hex.EncodeToString(k), 138 "alloc", utils.ByteCount(m.Alloc), "sys", utils.ByteCount(m.Sys)) 139 default: 140 } 141 } 142 // migrate bucket sequences to native mdbx implementation 143 //currentID, err := srcTx.Sequence(name, 0) 144 //if err != nil { 145 // return err 146 //} 147 //_, err = dstTx.Sequence(name, currentID) 148 //if err != nil { 149 // return err 150 //} 151 if err2 := dstTx.Commit(); err2 != nil { 152 return err2 153 } 154 return nil 155 } 156 157 const ReadAheadThreads = 128 158 159 func WarmupTable(ctx context.Context, db kv.RoDB, bucket string, lvl log.Lvl, readAheadThreads int) { 160 var ThreadsLimit = readAheadThreads 161 var total uint64 162 db.View(ctx, func(tx kv.Tx) error { 163 c, _ := tx.Cursor(bucket) 164 total, _ = c.Count() 165 return nil 166 }) 167 if total < 10_000 { 168 return 169 } 170 progress := atomic.Int64{} 171 172 logEvery := time.NewTicker(20 * time.Second) 173 defer logEvery.Stop() 174 175 g, ctx := errgroup.WithContext(ctx) 176 g.SetLimit(ThreadsLimit) 177 for i := 0; i < 256; i++ { 178 for j := 0; j < 256; j++ { 179 i := i 180 j := j 181 g.Go(func() error { 182 return db.View(ctx, func(tx kv.Tx) error { 183 it, err := tx.Prefix(bucket, []byte{byte(i), byte(j)}) 184 if err != nil { 185 return err 186 } 187 for it.HasNext() { 188 _, _, err = it.Next() 189 if err != nil { 190 return err 191 } 192 progress.Add(1) 193 select { 194 case <-ctx.Done(): 195 return ctx.Err() 196 case <-logEvery.C: 197 log.Log(lvl, fmt.Sprintf("Progress: %s %.2f%%", bucket, 100*float64(progress.Load())/float64(total))) 198 default: 199 } 200 } 201 return nil 202 }) 203 }) 204 } 205 } 206 for i := 0; i < 1_000; i++ { 207 i := i 208 g.Go(func() error { 209 return db.View(ctx, func(tx kv.Tx) error { 210 seek := make([]byte, 8) 211 binary.BigEndian.PutUint64(seek, uint64(i*100_000)) 212 it, err := tx.Prefix(bucket, seek) 213 if err != nil { 214 return err 215 } 216 for it.HasNext() { 217 _, _, err = it.Next() 218 if err != nil { 219 return err 220 } 221 select { 222 case <-ctx.Done(): 223 return ctx.Err() 224 case <-logEvery.C: 225 log.Log(lvl, fmt.Sprintf("Progress: %s %.2f%%", bucket, 100*float64(progress.Load())/float64(total))) 226 default: 227 } 228 } 229 return nil 230 }) 231 }) 232 } 233 _ = g.Wait() 234 } 235 236 func ClearTables(ctx context.Context, db kv.RoDB, tx kv.RwTx, tables ...string) error { 237 for _, tbl := range tables { 238 if err := ClearTable(ctx, db, tx, tbl); err != nil { 239 return err 240 } 241 } 242 return nil 243 } 244 245 func ClearTable(ctx context.Context, db kv.RoDB, tx kv.RwTx, table string) error { 246 ctx, cancel := context.WithCancel(ctx) 247 clean := warmup(ctx, db, table) 248 defer func() { 249 cancel() 250 clean() 251 }() 252 log.Info("Clear", "table", table) 253 return tx.ClearBucket(table) 254 } 255 256 func warmup(ctx context.Context, db kv.RoDB, bucket string) func() { 257 wg := sync.WaitGroup{} 258 wg.Add(1) 259 go func() { 260 defer wg.Done() 261 WarmupTable(ctx, db, bucket, log.LvlInfo, ReadAheadThreads) 262 }() 263 return func() { wg.Wait() } 264 }