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 }