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  }