github.com/amazechain/amc@v0.1.3/modules/ethdb/olddb/mapmutation.go (about) 1 package olddb 2 3 import ( 4 "context" 5 "encoding/binary" 6 "fmt" 7 "github.com/amazechain/amc/modules" 8 "sync" 9 "time" 10 "unsafe" 11 12 "github.com/ledgerwatch/erigon-lib/etl" 13 "github.com/ledgerwatch/erigon-lib/kv" 14 "github.com/ledgerwatch/log/v3" 15 16 "github.com/amazechain/amc/modules/ethdb" 17 ) 18 19 type mapmutation struct { 20 puts map[string]map[string][]byte // table -> key -> value ie. blocks -> hash -> blockBod 21 db kv.RwTx 22 quit <-chan struct{} 23 clean func() 24 mu sync.RWMutex 25 size int 26 count uint64 27 tmpdir string 28 } 29 30 // NewBatch - starts in-mem batch 31 // 32 // Common pattern: 33 // 34 // batch := db.NewBatch() 35 // defer batch.Rollback() 36 // ... some calculations on `batch` 37 // batch.Commit() 38 func NewHashBatch(tx kv.RwTx, quit <-chan struct{}, tmpdir string) *mapmutation { 39 clean := func() {} 40 if quit == nil { 41 ch := make(chan struct{}) 42 clean = func() { close(ch) } 43 quit = ch 44 } 45 46 return &mapmutation{ 47 db: tx, 48 puts: make(map[string]map[string][]byte), 49 quit: quit, 50 clean: clean, 51 tmpdir: tmpdir, 52 } 53 } 54 55 func (m *mapmutation) RwKV() kv.RwDB { 56 if casted, ok := m.db.(ethdb.HasRwKV); ok { 57 return casted.RwKV() 58 } 59 return nil 60 } 61 62 func (m *mapmutation) getMem(table string, key []byte) ([]byte, bool) { 63 m.mu.RLock() 64 defer m.mu.RUnlock() 65 if _, ok := m.puts[table]; !ok { 66 return nil, false 67 } 68 if value, ok := m.puts[table][*(*string)(unsafe.Pointer(&key))]; ok { 69 return value, ok 70 } 71 72 return nil, false 73 } 74 75 func (m *mapmutation) IncrementSequence(bucket string, amount uint64) (res uint64, err error) { 76 v, ok := m.getMem(modules.Sequence, []byte(bucket)) 77 if !ok && m.db != nil { 78 v, err = m.db.GetOne(modules.Sequence, []byte(bucket)) 79 if err != nil { 80 return 0, err 81 } 82 } 83 84 var currentV uint64 = 0 85 if len(v) > 0 { 86 currentV = binary.BigEndian.Uint64(v) 87 } 88 89 newVBytes := make([]byte, 8) 90 binary.BigEndian.PutUint64(newVBytes, currentV+amount) 91 if err = m.Put(modules.Sequence, []byte(bucket), newVBytes); err != nil { 92 return 0, err 93 } 94 95 return currentV, nil 96 } 97 func (m *mapmutation) ReadSequence(bucket string) (res uint64, err error) { 98 v, ok := m.getMem(modules.Sequence, []byte(bucket)) 99 if !ok && m.db != nil { 100 v, err = m.db.GetOne(modules.Sequence, []byte(bucket)) 101 if err != nil { 102 return 0, err 103 } 104 } 105 var currentV uint64 = 0 106 if len(v) > 0 { 107 currentV = binary.BigEndian.Uint64(v) 108 } 109 110 return currentV, nil 111 } 112 113 // Can only be called from the worker thread 114 func (m *mapmutation) GetOne(table string, key []byte) ([]byte, error) { 115 if value, ok := m.getMem(table, key); ok { 116 return value, nil 117 } 118 if m.db != nil { 119 // TODO: simplify when tx can no longer be parent of mutation 120 value, err := m.db.GetOne(table, key) 121 if err != nil { 122 return nil, err 123 } 124 return value, nil 125 } 126 return nil, nil 127 } 128 129 // Can only be called from the worker thread 130 func (m *mapmutation) Get(table string, key []byte) ([]byte, error) { 131 value, err := m.GetOne(table, key) 132 if err != nil { 133 return nil, err 134 } 135 136 if value == nil { 137 return nil, ethdb.ErrKeyNotFound 138 } 139 140 return value, nil 141 } 142 143 func (m *mapmutation) Last(table string) ([]byte, []byte, error) { 144 c, err := m.db.Cursor(table) 145 if err != nil { 146 return nil, nil, err 147 } 148 defer c.Close() 149 return c.Last() 150 } 151 152 func (m *mapmutation) Has(table string, key []byte) (bool, error) { 153 if _, ok := m.getMem(table, key); ok { 154 return ok, nil 155 } 156 if m.db != nil { 157 return m.db.Has(table, key) 158 } 159 return false, nil 160 } 161 162 // puts a table key with a value and if the table is not found then it appends a table 163 func (m *mapmutation) Put(table string, k, v []byte) error { 164 m.mu.Lock() 165 defer m.mu.Unlock() 166 if _, ok := m.puts[table]; !ok { 167 m.puts[table] = make(map[string][]byte) 168 } 169 170 stringKey := string(k) 171 172 var ok bool 173 if _, ok = m.puts[table][stringKey]; !ok { 174 m.size += len(v) - len(m.puts[table][stringKey]) 175 m.puts[table][stringKey] = v 176 return nil 177 } 178 m.puts[table][stringKey] = v 179 m.size += len(k) + len(v) 180 m.count++ 181 182 return nil 183 } 184 185 func (m *mapmutation) Append(table string, key []byte, value []byte) error { 186 return m.Put(table, key, value) 187 } 188 189 func (m *mapmutation) AppendDup(table string, key []byte, value []byte) error { 190 return m.Put(table, key, value) 191 } 192 193 func (m *mapmutation) BatchSize() int { 194 m.mu.RLock() 195 defer m.mu.RUnlock() 196 return m.size 197 } 198 199 func (m *mapmutation) ForEach(bucket string, fromPrefix []byte, walker func(k, v []byte) error) error { 200 m.panicOnEmptyDB() 201 return m.db.ForEach(bucket, fromPrefix, walker) 202 } 203 204 func (m *mapmutation) ForPrefix(bucket string, prefix []byte, walker func(k, v []byte) error) error { 205 m.panicOnEmptyDB() 206 return m.db.ForPrefix(bucket, prefix, walker) 207 } 208 209 func (m *mapmutation) ForAmount(bucket string, prefix []byte, amount uint32, walker func(k, v []byte) error) error { 210 m.panicOnEmptyDB() 211 return m.db.ForAmount(bucket, prefix, amount, walker) 212 } 213 214 func (m *mapmutation) Delete(table string, k []byte) error { 215 return m.Put(table, k, nil) 216 } 217 218 func (m *mapmutation) doCommit(tx kv.RwTx) error { 219 logEvery := time.NewTicker(30 * time.Second) 220 defer logEvery.Stop() 221 count := 0 222 total := float64(m.count) 223 for table, bucket := range m.puts { 224 logger := log.New() 225 collector := etl.NewCollector("", m.tmpdir, etl.NewSortableBuffer(etl.BufferOptimalSize), logger) 226 defer collector.Close() 227 for key, value := range bucket { 228 collector.Collect([]byte(key), value) 229 count++ 230 select { 231 default: 232 case <-logEvery.C: 233 progress := fmt.Sprintf("%.1fM/%.1fM", float64(count)/1_000_000, total/1_000_000) 234 log.Info("Write to db", "progress", progress, "current table", table) 235 tx.CollectMetrics() 236 } 237 } 238 if err := collector.Load(m.db, table, etl.IdentityLoadFunc, etl.TransformArgs{Quit: m.quit}); err != nil { 239 return err 240 } 241 } 242 243 tx.CollectMetrics() 244 return nil 245 } 246 247 func (m *mapmutation) Commit() error { 248 if m.db == nil { 249 return nil 250 } 251 m.mu.Lock() 252 defer m.mu.Unlock() 253 if err := m.doCommit(m.db); err != nil { 254 return err 255 } 256 257 m.puts = map[string]map[string][]byte{} 258 m.size = 0 259 m.count = 0 260 m.clean() 261 return nil 262 } 263 264 func (m *mapmutation) Rollback() { 265 m.mu.Lock() 266 defer m.mu.Unlock() 267 m.puts = map[string]map[string][]byte{} 268 m.size = 0 269 m.count = 0 270 m.size = 0 271 m.clean() 272 } 273 274 func (m *mapmutation) Close() { 275 m.Rollback() 276 } 277 278 func (m *mapmutation) Begin(ctx context.Context, flags ethdb.TxFlags) (ethdb.DbWithPendingMutations, error) { 279 panic("mutation can't start transaction, because doesn't own it") 280 } 281 282 func (m *mapmutation) panicOnEmptyDB() { 283 if m.db == nil { 284 panic("Not implemented") 285 } 286 } 287 288 func (m *mapmutation) SetRwKV(kv kv.RwDB) { 289 hasRwKV, ok := m.db.(ethdb.HasRwKV) 290 if !ok { 291 log.Warn("Failed to convert mapmutation type to HasRwKV interface") 292 } 293 hasRwKV.SetRwKV(kv) 294 }