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  }