github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/pkg/kv/local/store.go (about)

     1  package local
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"time"
     8  
     9  	"github.com/dgraph-io/badger/v4"
    10  	"github.com/treeverse/lakefs/pkg/kv"
    11  	"github.com/treeverse/lakefs/pkg/logging"
    12  )
    13  
    14  func partitionRange(partitionKey []byte) []byte {
    15  	result := make([]byte, len(partitionKey)+1)
    16  	copy(result, partitionKey)
    17  	result[len(result)-1] = kv.PathDelimiter[0]
    18  	return result
    19  }
    20  
    21  func composeKey(partitionKey, key []byte) []byte {
    22  	pr := partitionRange(partitionKey)
    23  	result := make([]byte, len(pr)+len(key))
    24  	copy(result, pr)
    25  	copy(result[len(pr):], key)
    26  	return result
    27  }
    28  
    29  type Store struct {
    30  	db           *badger.DB
    31  	logger       logging.Logger
    32  	prefetchSize int
    33  	refCount     int
    34  	path         string
    35  }
    36  
    37  func (s *Store) Get(ctx context.Context, partitionKey, key []byte) (*kv.ValueWithPredicate, error) {
    38  	k := composeKey(partitionKey, key)
    39  	start := time.Now()
    40  	log := s.logger.WithField("key", string(k)).WithField("op", "get").WithContext(ctx)
    41  	log.Trace("performing operation")
    42  	if len(partitionKey) == 0 {
    43  		log.WithError(kv.ErrMissingPartitionKey).Warn("got empty partition key")
    44  		return nil, kv.ErrMissingPartitionKey
    45  	}
    46  	if len(key) == 0 {
    47  		log.WithError(kv.ErrMissingKey).Warn("got empty key")
    48  		return nil, kv.ErrMissingKey
    49  	}
    50  
    51  	var value []byte
    52  	err := s.db.View(func(txn *badger.Txn) error {
    53  		item, err := txn.Get(k)
    54  		if errors.Is(err, badger.ErrKeyNotFound) {
    55  			return kv.ErrNotFound
    56  		}
    57  		if err != nil {
    58  			log.WithError(err).Error("error getting key")
    59  			return err
    60  		}
    61  		value, err = item.ValueCopy(nil)
    62  		if err != nil {
    63  			log.WithError(err).Error("error getting value for key")
    64  			return err
    65  		}
    66  		return nil
    67  	})
    68  	log.WithField("took", time.Since(start)).WithError(err).WithField("size", len(value)).Trace("operation complete")
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  	return &kv.ValueWithPredicate{
    73  		Value:     value,
    74  		Predicate: kv.Predicate(value),
    75  	}, nil
    76  }
    77  
    78  func (s *Store) Set(ctx context.Context, partitionKey, key, value []byte) error {
    79  	k := composeKey(partitionKey, key)
    80  	start := time.Now()
    81  	log := s.logger.WithField("key", string(k)).WithField("op", "set").WithContext(ctx)
    82  	log.Trace("performing operation")
    83  	if len(partitionKey) == 0 {
    84  		log.WithError(kv.ErrMissingPartitionKey).Warn("got empty partition key")
    85  		return kv.ErrMissingPartitionKey
    86  	}
    87  	if len(key) == 0 {
    88  		log.WithError(kv.ErrMissingKey).Warn("got empty key")
    89  		return kv.ErrMissingKey
    90  	}
    91  	if value == nil {
    92  		log.WithError(kv.ErrMissingValue).Warn("got nil value")
    93  		return kv.ErrMissingValue
    94  	}
    95  	err := s.db.Update(func(txn *badger.Txn) error {
    96  		return txn.Set(k, value)
    97  	})
    98  	if err != nil {
    99  		log.WithError(err).Error("error setting value")
   100  		return err
   101  	}
   102  	log.WithField("took", time.Since(start)).Trace("done setting value")
   103  	return nil
   104  }
   105  
   106  func (s *Store) SetIf(ctx context.Context, partitionKey, key, value []byte, valuePredicate kv.Predicate) error {
   107  	k := composeKey(partitionKey, key)
   108  	start := time.Now()
   109  	log := s.logger.WithField("key", string(k)).WithField("op", "set_if").WithContext(ctx)
   110  	log.Trace("performing operation")
   111  	if len(partitionKey) == 0 {
   112  		log.WithError(kv.ErrMissingPartitionKey).Warn("got empty partition key")
   113  		return kv.ErrMissingPartitionKey
   114  	}
   115  	if len(key) == 0 {
   116  		log.WithError(kv.ErrMissingKey).Warn("got empty key")
   117  		return kv.ErrMissingKey
   118  	}
   119  	if value == nil {
   120  		log.WithError(kv.ErrMissingValue).Warn("got nil value")
   121  		return kv.ErrMissingValue
   122  	}
   123  
   124  	err := s.db.Update(func(txn *badger.Txn) error {
   125  		item, err := txn.Get(k)
   126  		if err != nil && !errors.Is(err, badger.ErrKeyNotFound) {
   127  			log.WithError(err).Error("could not get key for predicate")
   128  			return err
   129  		}
   130  
   131  		if valuePredicate != nil {
   132  			if errors.Is(err, badger.ErrKeyNotFound) {
   133  				log.WithField("predicate", nil).Trace("predicate condition failed")
   134  				return kv.ErrPredicateFailed
   135  			}
   136  			if valuePredicate != kv.PrecondConditionalExists {
   137  				val, err := item.ValueCopy(nil)
   138  				if err != nil {
   139  					log.WithError(err).Error("could not get byte value for predicate")
   140  					return err
   141  				}
   142  				if !bytes.Equal(val, valuePredicate.([]byte)) {
   143  					log.WithField("predicate", valuePredicate).WithField("value", val).Trace("predicate condition failed")
   144  					return kv.ErrPredicateFailed
   145  				}
   146  			}
   147  		} else if !errors.Is(err, badger.ErrKeyNotFound) {
   148  			log.WithField("predicate", valuePredicate).Trace("predicate condition failed (key not found)")
   149  			return kv.ErrPredicateFailed
   150  		}
   151  
   152  		return txn.Set(k, value)
   153  	})
   154  	if errors.Is(err, badger.ErrConflict) { // Return predicate failed on transaction conflict - to retry
   155  		log.WithError(err).Trace("transaction conflict")
   156  		err = kv.ErrPredicateFailed
   157  	}
   158  	took := time.Since(start)
   159  	log.WithField("took", took).Trace("operation complete")
   160  
   161  	return err
   162  }
   163  
   164  func (s *Store) Delete(ctx context.Context, partitionKey, key []byte) error {
   165  	k := composeKey(partitionKey, key)
   166  	start := time.Now()
   167  	log := s.logger.
   168  		WithField("key", string(k)).
   169  		WithField("op", "delete").
   170  		WithContext(ctx)
   171  	log.Trace("performing operation")
   172  	if len(partitionKey) == 0 {
   173  		log.WithError(kv.ErrMissingPartitionKey).Warn("got empty partition key")
   174  		return kv.ErrMissingPartitionKey
   175  	}
   176  	if len(key) == 0 {
   177  		log.WithError(kv.ErrMissingKey).Warn("got empty key")
   178  		return kv.ErrMissingKey
   179  	}
   180  	err := s.db.Update(func(txn *badger.Txn) error {
   181  		return txn.Delete(k)
   182  	})
   183  	took := time.Since(start)
   184  	log = log.WithField("took", took)
   185  	if err != nil {
   186  		log.WithError(err).Trace("operation failed")
   187  		return err
   188  	}
   189  	log.Trace("operation complete")
   190  	return nil
   191  }
   192  
   193  func (s *Store) Scan(ctx context.Context, partitionKey []byte, options kv.ScanOptions) (kv.EntriesIterator, error) {
   194  	log := s.logger.WithFields(logging.Fields{
   195  		"partition_key": string(partitionKey),
   196  		"start_key":     string(options.KeyStart),
   197  		"op":            "scan",
   198  	}).WithContext(ctx)
   199  	log.Trace("performing operation")
   200  	if len(partitionKey) == 0 {
   201  		log.WithError(kv.ErrMissingPartitionKey).Warn("got empty partition key")
   202  		return nil, kv.ErrMissingPartitionKey
   203  	}
   204  
   205  	prefix := partitionRange(partitionKey)
   206  	txn := s.db.NewTransaction(false)
   207  	opts := badger.DefaultIteratorOptions
   208  	opts.PrefetchSize = s.prefetchSize
   209  	if options.BatchSize != 0 && opts.PrefetchSize != 0 && options.BatchSize < opts.PrefetchSize {
   210  		opts.PrefetchSize = options.BatchSize
   211  	}
   212  	if opts.PrefetchSize > 0 {
   213  		opts.PrefetchValues = true
   214  	}
   215  	opts.Prefix = prefix
   216  	iter := txn.NewIterator(opts)
   217  	return &EntriesIterator{
   218  		iter:         iter,
   219  		partitionKey: partitionKey,
   220  		start:        composeKey(partitionKey, options.KeyStart),
   221  		logger:       log,
   222  		txn:          txn,
   223  	}, nil
   224  }
   225  
   226  func (s *Store) Close() {
   227  	driverLock.Lock()
   228  	defer driverLock.Unlock()
   229  	s.refCount--
   230  	if s.refCount <= 0 {
   231  		_ = s.db.Close()
   232  		delete(dbMap, s.path)
   233  	}
   234  }