decred.org/dcrdex@v1.0.5/tatanka/db/kvdb.go (about) 1 // This code is available on the terms of the project LICENSE.md file, 2 // also available online at https://blueoakcouncil.org/license/1.0.0. 3 4 package db 5 6 import ( 7 "context" 8 "encoding" 9 "errors" 10 "fmt" 11 "math" 12 "time" 13 14 "decred.org/dcrdex/dex" 15 "github.com/dgraph-io/badger" 16 ) 17 18 var ( 19 seekEnd = []byte{ 20 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 21 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 22 } 23 ) 24 25 type KeyValueDB interface { 26 Get(k []byte, thing encoding.BinaryUnmarshaler) (bool, error) 27 Store(k []byte, thing encoding.BinaryMarshaler) error 28 Close() error 29 ForEach(f func(k, v []byte) error, opts ...*IterationOption) error 30 Run(context.Context) 31 Delete(k []byte) error 32 } 33 34 type IterationOption struct { 35 reverse bool 36 prefix []byte 37 maxEntries uint64 38 deleteOverMax bool 39 } 40 41 func WithReverse() *IterationOption { 42 return &IterationOption{ 43 reverse: true, 44 } 45 } 46 47 func WithPrefix(b []byte) *IterationOption { 48 return &IterationOption{ 49 prefix: b, 50 } 51 } 52 53 func WithMaxEntries(max uint64, deleteExcess bool) *IterationOption { 54 return &IterationOption{ 55 maxEntries: max, 56 deleteOverMax: deleteExcess, 57 } 58 } 59 60 type kvDB struct { 61 *badger.DB 62 log dex.Logger 63 } 64 65 func NewFileDB(filePath string, log dex.Logger) (KeyValueDB, error) { 66 // If memory use is a concern, could try 67 // .WithValueLogLoadingMode(options.FileIO) // default options.MemoryMap 68 // .WithMaxTableSize(sz int64); // bytes, default 6MB 69 // .WithValueLogFileSize(sz int64), bytes, default 1 GB, must be 1MB <= sz <= 1GB 70 opts := badger.DefaultOptions(filePath).WithLogger(&badgerLoggerWrapper{log}) 71 db, err := badger.Open(opts) 72 if err == badger.ErrTruncateNeeded { 73 // Probably a Windows thing. 74 // https://github.com/dgraph-io/badger/issues/744 75 log.Warnf("NewFileDB badger db: %v", err) 76 // Try again with value log truncation enabled. 77 opts.Truncate = true 78 log.Warnf("Attempting to reopen badger DB with the Truncate option set...") 79 db, err = badger.Open(opts) 80 } 81 if err != nil { 82 return nil, err 83 } 84 85 return &kvDB{db, log}, nil 86 } 87 88 // Run starts the garbage collection loop. 89 func (d *kvDB) Run(ctx context.Context) { 90 ticker := time.NewTicker(5 * time.Minute) 91 defer ticker.Stop() 92 for { 93 select { 94 case <-ticker.C: 95 err := d.RunValueLogGC(0.5) 96 if err != nil && !errors.Is(err, badger.ErrNoRewrite) { 97 d.log.Errorf("garbage collection error: %v", err) 98 } 99 case <-ctx.Done(): 100 return 101 } 102 } 103 } 104 105 // Close the database. 106 func (d *kvDB) Close() error { 107 return d.DB.Close() 108 } 109 110 func (d *kvDB) ForEach(f func(k, v []byte) error, iterOpts ...*IterationOption) error { 111 var deletes [][]byte 112 err := d.View(func(txn *badger.Txn) error { 113 opts := badger.DefaultIteratorOptions 114 opts.PrefetchSize = 10 115 var maxEntries uint64 = math.MaxUint64 116 var deleteExcess bool 117 for _, iterOpt := range iterOpts { 118 switch { 119 case iterOpt.reverse: 120 opts.Reverse = true 121 case iterOpt.maxEntries > 0: 122 maxEntries = iterOpt.maxEntries 123 deleteExcess = iterOpt.deleteOverMax 124 case len(iterOpt.prefix) > 0: 125 opts.Prefix = iterOpt.prefix 126 } 127 } 128 129 it := txn.NewIterator(opts) 130 defer it.Close() 131 132 if opts.Reverse { 133 it.Seek(append(opts.Prefix, seekEnd...)) 134 } else { 135 it.Rewind() 136 } 137 var n uint64 138 for ; it.Valid(); it.Next() { 139 n++ 140 item := it.Item() 141 k := item.Key() 142 if maxEntries > 0 && n > maxEntries { 143 deletes = append(deletes, k) 144 continue 145 } 146 err := item.Value(func(v []byte) error { 147 return f(k, v) 148 }) 149 if err != nil { 150 return err 151 } 152 if !deleteExcess && n == maxEntries { 153 break 154 } 155 } 156 return nil 157 }) 158 159 if len(deletes) > 0 { 160 d.log.Tracef("deleting %d entries from db", len(deletes)) 161 if err := d.Update(func(txn *badger.Txn) error { 162 for _, k := range deletes { 163 if err := txn.Delete(k); err != nil { 164 d.log.Errorf("Error deleting db entry: %v", err) 165 } 166 } 167 return nil 168 }); err != nil { 169 d.log.Errorf("Error while deleting keys: %v", err) 170 } 171 } 172 173 return err 174 } 175 176 func (d *kvDB) Delete(k []byte) error { 177 return d.Update(func(tx *badger.Txn) error { 178 return tx.Delete(k) 179 }) 180 } 181 182 func (d *kvDB) Store(k []byte, thing encoding.BinaryMarshaler) error { 183 b, err := thing.MarshalBinary() 184 if err != nil { 185 return err 186 } 187 188 return d.Update(func(tx *badger.Txn) error { 189 return tx.Set(k, b) 190 }) 191 } 192 193 func (d *kvDB) Get(k []byte, thing encoding.BinaryUnmarshaler) (found bool, err error) { 194 return found, d.View(func(txn *badger.Txn) error { 195 item, err := txn.Get(k) 196 if err != nil { 197 if errors.Is(err, badger.ErrKeyNotFound) { 198 return nil 199 } 200 return fmt.Errorf("error reading database: %w", err) 201 } 202 found = true 203 return item.Value(func(b []byte) error { 204 return thing.UnmarshalBinary(b) 205 }) 206 }) 207 } 208 209 // badgerLoggerWrapper wraps dex.Logger and translates Warnf to Warningf to 210 // satisfy badger.Logger. 211 type badgerLoggerWrapper struct { 212 dex.Logger 213 } 214 215 var _ badger.Logger = (*badgerLoggerWrapper)(nil) 216 217 // Warningf -> dex.Logger.Warnf 218 func (log *badgerLoggerWrapper) Warningf(s string, a ...any) { 219 log.Warnf(s, a...) 220 } 221 222 type memoryDB map[string][]byte 223 224 func NewMemoryDB() KeyValueDB { 225 return make(memoryDB) 226 } 227 228 func (m memoryDB) Store(k []byte, v encoding.BinaryMarshaler) error { 229 b, err := v.MarshalBinary() 230 if err != nil { 231 return err 232 } 233 m[string(k)] = b 234 return nil 235 } 236 237 func (m memoryDB) Get(k []byte, thing encoding.BinaryUnmarshaler) (found bool, err error) { 238 b, found := m[string(k)] 239 if !found { 240 return 241 } 242 return true, thing.UnmarshalBinary(b) 243 } 244 245 func (m memoryDB) Close() error { 246 return nil 247 } 248 249 func (m memoryDB) ForEach(f func(k, v []byte) error, iterOpts ...*IterationOption) error { 250 for k, v := range m { 251 if err := f([]byte(k), v); err != nil { 252 return err 253 } 254 } 255 return nil 256 } 257 258 func (m memoryDB) Run(context.Context) {} 259 260 func (m memoryDB) Delete(k []byte) error { 261 delete(m, string(k)) 262 return nil 263 }