github.com/ledgerwatch/erigon-lib@v1.0.0/kv/helpers.go (about)

     1  /*
     2     Copyright 2022 Erigon contributors
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package kv
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"os"
    23  	"sync"
    24  	"sync/atomic"
    25  	"time"
    26  
    27  	"github.com/erigontech/mdbx-go/mdbx"
    28  	"github.com/ledgerwatch/erigon-lib/common"
    29  )
    30  
    31  func DefaultPageSize() uint64 {
    32  	osPageSize := os.Getpagesize()
    33  	if osPageSize < 4096 { // reduce further may lead to errors (because some data is just big)
    34  		osPageSize = 4096
    35  	} else if osPageSize > mdbx.MaxPageSize {
    36  		osPageSize = mdbx.MaxPageSize
    37  	}
    38  	osPageSize = osPageSize / 4096 * 4096 // ensure it's rounded
    39  	return uint64(osPageSize)
    40  }
    41  
    42  // BigChunks - read `table` by big chunks - restart read transaction after each 1 minutes
    43  func BigChunks(db RoDB, table string, from []byte, walker func(tx Tx, k, v []byte) (bool, error)) error {
    44  	rollbackEvery := time.NewTicker(1 * time.Minute)
    45  
    46  	var stop bool
    47  	for !stop {
    48  		if err := db.View(context.Background(), func(tx Tx) error {
    49  			c, err := tx.Cursor(table)
    50  			if err != nil {
    51  				return err
    52  			}
    53  			defer c.Close()
    54  
    55  			k, v, err := c.Seek(from)
    56  		Loop:
    57  			for ; k != nil; k, v, err = c.Next() {
    58  				if err != nil {
    59  					return err
    60  				}
    61  
    62  				// break loop before walker() call, to make sure all keys are received by walker() exactly once
    63  				select {
    64  				case <-rollbackEvery.C:
    65  
    66  					break Loop
    67  				default:
    68  				}
    69  
    70  				ok, err := walker(tx, k, v)
    71  				if err != nil {
    72  					return err
    73  				}
    74  				if !ok {
    75  					stop = true
    76  					break
    77  				}
    78  			}
    79  
    80  			if k == nil {
    81  				stop = true
    82  			}
    83  
    84  			from = common.Copy(k) // next transaction will start from this key
    85  
    86  			return nil
    87  		}); err != nil {
    88  			return err
    89  		}
    90  	}
    91  	return nil
    92  }
    93  
    94  var (
    95  	bytesTrue  = []byte{1}
    96  	bytesFalse = []byte{0}
    97  )
    98  
    99  func bytes2bool(in []byte) bool {
   100  	if len(in) < 1 {
   101  		return false
   102  	}
   103  	return in[0] == 1
   104  }
   105  
   106  var ErrChanged = fmt.Errorf("key must not change")
   107  
   108  // EnsureNotChangedBool - used to store immutable config flags in db. protects from human mistakes
   109  func EnsureNotChangedBool(tx GetPut, bucket string, k []byte, value bool) (ok, enabled bool, err error) {
   110  	vBytes, err := tx.GetOne(bucket, k)
   111  	if err != nil {
   112  		return false, enabled, err
   113  	}
   114  	if vBytes == nil {
   115  		if value {
   116  			vBytes = bytesTrue
   117  		} else {
   118  			vBytes = bytesFalse
   119  		}
   120  		if err := tx.Put(bucket, k, vBytes); err != nil {
   121  			return false, enabled, err
   122  		}
   123  	}
   124  
   125  	enabled = bytes2bool(vBytes)
   126  	return value == enabled, enabled, nil
   127  }
   128  
   129  func GetBool(tx Getter, bucket string, k []byte) (enabled bool, err error) {
   130  	vBytes, err := tx.GetOne(bucket, k)
   131  	if err != nil {
   132  		return false, err
   133  	}
   134  	return bytes2bool(vBytes), nil
   135  }
   136  
   137  func ReadAhead(ctx context.Context, db RoDB, progress *atomic.Bool, table string, from []byte, amount uint32) (clean func()) {
   138  	if db == nil {
   139  		return func() {}
   140  	}
   141  	if ok := progress.CompareAndSwap(false, true); !ok {
   142  		return func() {}
   143  	}
   144  	ctx, cancel := context.WithCancel(ctx)
   145  	wg := sync.WaitGroup{}
   146  	clean = func() {
   147  		cancel()
   148  		wg.Wait()
   149  	}
   150  	wg.Add(1)
   151  	go func() {
   152  		defer wg.Done()
   153  		defer progress.Store(false)
   154  		_ = db.View(ctx, func(tx Tx) error {
   155  			c, err := tx.Cursor(table)
   156  			if err != nil {
   157  				return err
   158  			}
   159  			defer c.Close()
   160  
   161  			for k, v, err := c.Seek(from); k != nil && amount > 0; k, v, err = c.Next() {
   162  				if err != nil {
   163  					return err
   164  				}
   165  				if len(v) > 0 {
   166  					_, _ = v[0], v[len(v)-1]
   167  				}
   168  				amount--
   169  				select {
   170  				case <-ctx.Done():
   171  					return ctx.Err()
   172  				default:
   173  				}
   174  			}
   175  			return nil
   176  		})
   177  	}()
   178  	return clean
   179  }
   180  
   181  // FirstKey - candidate on move to kv.Tx interface
   182  func FirstKey(tx Tx, table string) ([]byte, error) {
   183  	c, err := tx.Cursor(table)
   184  	if err != nil {
   185  		return nil, err
   186  	}
   187  	defer c.Close()
   188  	k, _, err := c.First()
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  	return k, nil
   193  }
   194  
   195  // LastKey - candidate on move to kv.Tx interface
   196  func LastKey(tx Tx, table string) ([]byte, error) {
   197  	c, err := tx.Cursor(table)
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  	defer c.Close()
   202  	k, _, err := c.Last()
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  	return k, nil
   207  }
   208  
   209  // NextSubtree does []byte++. Returns false if overflow.
   210  func NextSubtree(in []byte) ([]byte, bool) {
   211  	r := make([]byte, len(in))
   212  	copy(r, in)
   213  	for i := len(r) - 1; i >= 0; i-- {
   214  		if r[i] != 255 {
   215  			r[i]++
   216  			return r, true
   217  		}
   218  
   219  		r = r[:i] // make it shorter, because in tries after 11ff goes 12, but not 1200
   220  	}
   221  	return nil, false
   222  }