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  }