github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/utils/dbutil/autocompact/store.go (about) 1 package autocompact 2 3 import ( 4 "sync" 5 6 "github.com/status-im/keycard-go/hexutils" 7 8 "github.com/unicornultrafoundation/go-u2u/log" 9 10 "github.com/unicornultrafoundation/go-helios/u2udb" 11 ) 12 13 // Store implements automatic compacting of recently inserted/erased data according to provided strategy 14 type Store struct { 15 u2udb.Store 16 limit uint64 17 cont ContainerI 18 newCont func() ContainerI 19 compMu sync.Mutex 20 name string 21 } 22 23 type Batch struct { 24 u2udb.Batch 25 store *Store 26 cont ContainerI 27 } 28 29 func Wrap(s u2udb.Store, limit uint64, strategy func() ContainerI, name string) *Store { 30 return &Store{ 31 Store: s, 32 limit: limit, 33 newCont: strategy, 34 cont: strategy(), 35 name: name, 36 } 37 } 38 39 func Wrap2(s u2udb.Store, limit1 uint64, limit2 uint64, strategy func() ContainerI, name string) *Store { 40 return Wrap(Wrap(s, limit1, strategy, name), limit2, strategy, name) 41 } 42 43 func Wrap2M(s u2udb.Store, limit1 uint64, limit2 uint64, forward bool, name string) *Store { 44 strategy := NewBackwardsCont 45 if forward { 46 strategy = NewForwardCont 47 } 48 return Wrap2(s, limit1, limit2, strategy, name) 49 } 50 51 func estSize(keyLen int, valLen int) uint64 { 52 // Storage overheads, related to adding/deleting a record, 53 //wouldn't be only proportional to length of key and value. 54 //E.g. if one adds 10 records with length of 2, it will be more expensive than 1 record with length 20 55 // Now, 64 wasn't really calculated but is rather a guesstimation 56 return uint64(keyLen + valLen + 64) 57 } 58 59 func (s *Store) onWrite(key []byte, size uint64) { 60 s.compMu.Lock() 61 defer s.compMu.Unlock() 62 if key != nil { 63 s.cont.Add(key, size) 64 } 65 s.mayCompact(false) 66 } 67 68 func (s *Store) onBatchWrite(batchCont ContainerI) { 69 s.compMu.Lock() 70 defer s.compMu.Unlock() 71 s.cont.Merge(batchCont) 72 s.mayCompact(false) 73 } 74 75 func (s *Store) compact() { 76 s.compMu.Lock() 77 defer s.compMu.Unlock() 78 s.mayCompact(true) 79 } 80 81 func (s *Store) mayCompact(force bool) { 82 // error handling 83 err := s.cont.Error() 84 if err != nil { 85 s.cont.Reset() 86 s.newCont = NewDevnullCont 87 s.cont = s.newCont() 88 log.Warn("Autocompaction failed, which may lead to performance issues", "name", s.name, "err", err) 89 } 90 91 if force || s.cont.Size() > s.limit { 92 for _, r := range s.cont.Ranges() { 93 log.Debug("Autocompact", "name", s.name, "from", hexutils.BytesToHex(r.minKey), "to", hexutils.BytesToHex(r.maxKey)) 94 _ = s.Store.Compact(r.minKey, r.maxKey) 95 } 96 s.cont.Reset() 97 } 98 } 99 100 func (s *Store) Put(key []byte, value []byte) error { 101 defer s.onWrite(key, estSize(len(key), len(value))) 102 return s.Store.Put(key, value) 103 } 104 105 func (s *Store) Delete(key []byte) error { 106 defer s.onWrite(key, estSize(len(key), 0)) 107 return s.Store.Delete(key) 108 } 109 110 func (s *Store) Close() error { 111 s.compact() 112 return s.Store.Close() 113 } 114 115 func (s *Store) NewBatch() u2udb.Batch { 116 batch := s.Store.NewBatch() 117 if batch == nil { 118 return nil 119 } 120 return &Batch{ 121 Batch: batch, 122 store: s, 123 cont: s.newCont(), 124 } 125 } 126 127 func (s *Batch) Put(key []byte, value []byte) error { 128 s.cont.Add(key, estSize(len(key), len(value))) 129 return s.Batch.Put(key, value) 130 } 131 132 func (s *Batch) Delete(key []byte) error { 133 s.cont.Add(key, estSize(len(key), 0)) 134 return s.Batch.Delete(key) 135 } 136 137 func (s *Batch) Reset() { 138 s.cont.Reset() 139 s.Batch.Reset() 140 } 141 142 func (s *Batch) Write() error { 143 defer s.store.onBatchWrite(s.cont) 144 return s.Batch.Write() 145 }