decred.org/dcrdex@v1.0.3/client/asset/kvdb/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 kvdb 5 6 import ( 7 "context" 8 "encoding" 9 "errors" 10 "time" 11 12 "decred.org/dcrdex/dex" 13 "github.com/dgraph-io/badger" 14 ) 15 16 type KeyValueDB interface { 17 Store(k []byte, v encoding.BinaryMarshaler) error 18 Close() error 19 ForEach(f func(k, v []byte) error) error 20 Run(context.Context) 21 Delete(k []byte) error 22 } 23 24 type kvDB struct { 25 *badger.DB 26 log dex.Logger 27 } 28 29 func NewFileDB(filePath string, log dex.Logger) (KeyValueDB, error) { 30 // If memory use is a concern, could try 31 // .WithValueLogLoadingMode(options.FileIO) // default options.MemoryMap 32 // .WithMaxTableSize(sz int64); // bytes, default 6MB 33 // .WithValueLogFileSize(sz int64), bytes, default 1 GB, must be 1MB <= sz <= 1GB 34 opts := badger.DefaultOptions(filePath).WithLogger(&badgerLoggerWrapper{log}) 35 db, err := badger.Open(opts) 36 if err == badger.ErrTruncateNeeded { 37 // Probably a Windows thing. 38 // https://github.com/dgraph-io/badger/issues/744 39 log.Warnf("NewFileDB badger db: %v", err) 40 // Try again with value log truncation enabled. 41 opts.Truncate = true 42 log.Warnf("Attempting to reopen badger DB with the Truncate option set...") 43 db, err = badger.Open(opts) 44 } 45 if err != nil { 46 return nil, err 47 } 48 49 return &kvDB{db, log}, nil 50 } 51 52 // Run starts the garbage collection loop. 53 func (d *kvDB) Run(ctx context.Context) { 54 ticker := time.NewTicker(5 * time.Minute) 55 defer ticker.Stop() 56 for { 57 select { 58 case <-ticker.C: 59 err := d.RunValueLogGC(0.5) 60 if err != nil && !errors.Is(err, badger.ErrNoRewrite) { 61 d.log.Errorf("garbage collection error: %v", err) 62 } 63 case <-ctx.Done(): 64 return 65 } 66 } 67 } 68 69 // Close the database. 70 func (d *kvDB) Close() error { 71 return d.DB.Close() 72 } 73 74 func (d *kvDB) ForEach(f func(k, v []byte) error) error { 75 return d.View(func(txn *badger.Txn) error { 76 opts := badger.DefaultIteratorOptions 77 opts.PrefetchSize = 10 78 it := txn.NewIterator(opts) 79 defer it.Close() 80 for it.Rewind(); it.Valid(); it.Next() { 81 item := it.Item() 82 k := item.Key() 83 err := item.Value(func(v []byte) error { 84 return f(k, v) 85 }) 86 if err != nil { 87 return err 88 } 89 } 90 return nil 91 }) 92 } 93 94 func (d *kvDB) Delete(k []byte) error { 95 return d.Update(func(tx *badger.Txn) error { 96 return tx.Delete(k) 97 }) 98 } 99 100 func (d *kvDB) Store(k []byte, thing encoding.BinaryMarshaler) error { 101 b, err := thing.MarshalBinary() 102 if err != nil { 103 return err 104 } 105 106 return d.Update(func(tx *badger.Txn) error { 107 return tx.Set(k, b) 108 }) 109 } 110 111 // badgerLoggerWrapper wraps dex.Logger and translates Warnf to Warningf to 112 // satisfy badger.Logger. 113 type badgerLoggerWrapper struct { 114 dex.Logger 115 } 116 117 var _ badger.Logger = (*badgerLoggerWrapper)(nil) 118 119 // Warningf -> dex.Logger.Warnf 120 func (log *badgerLoggerWrapper) Warningf(s string, a ...any) { 121 log.Warnf(s, a...) 122 } 123 124 type memoryDB map[string][]byte 125 126 func NewMemoryDB() KeyValueDB { 127 return make(memoryDB) 128 } 129 130 func (m memoryDB) Store(k []byte, v encoding.BinaryMarshaler) error { 131 b, err := v.MarshalBinary() 132 if err != nil { 133 return err 134 } 135 m[string(k)] = b 136 return nil 137 } 138 139 func (m memoryDB) Close() error { 140 return nil 141 } 142 143 func (m memoryDB) ForEach(f func(k, v []byte) error) error { 144 for k, v := range m { 145 if err := f([]byte(k), v); err != nil { 146 return err 147 } 148 } 149 return nil 150 } 151 152 func (m memoryDB) Run(context.Context) {} 153 154 func (m memoryDB) Delete(k []byte) error { 155 delete(m, string(k)) 156 return nil 157 }