github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/pkg/graveler/sstable/range_manager.go (about)

     1  package sstable
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto"
     7  	"fmt"
     8  
     9  	"github.com/cockroachdb/pebble"
    10  	"github.com/cockroachdb/pebble/sstable"
    11  	"github.com/treeverse/lakefs/pkg/graveler"
    12  	"github.com/treeverse/lakefs/pkg/graveler/committed"
    13  	"github.com/treeverse/lakefs/pkg/logging"
    14  	"github.com/treeverse/lakefs/pkg/pyramid"
    15  )
    16  
    17  type NewSSTableReaderFn func(ctx context.Context, ns committed.Namespace, id committed.ID) (*sstable.Reader, error)
    18  
    19  type Unrefer interface {
    20  	Unref()
    21  }
    22  
    23  type RangeManager struct {
    24  	newReader NewSSTableReaderFn
    25  	fs        pyramid.FS
    26  	hash      crypto.Hash
    27  	cache     Unrefer
    28  }
    29  
    30  func NewPebbleSSTableRangeManager(cache *pebble.Cache, fs pyramid.FS, hash crypto.Hash) *RangeManager {
    31  	if cache != nil { // nil cache allowed (size=0), see sstable.ReaderOptions
    32  		cache.Ref()
    33  	}
    34  	opts := sstable.ReaderOptions{Cache: cache}
    35  	newReader := func(ctx context.Context, ns committed.Namespace, id committed.ID) (*sstable.Reader, error) {
    36  		return newReader(ctx, fs, ns, id, opts)
    37  	}
    38  	return NewPebbleSSTableRangeManagerWithNewReader(newReader, opts.Cache, fs, hash)
    39  }
    40  
    41  func newReader(ctx context.Context, fs pyramid.FS, ns committed.Namespace, id committed.ID, opts sstable.ReaderOptions) (*sstable.Reader, error) {
    42  	file, err := fs.Open(ctx, string(ns), string(id))
    43  	if err != nil {
    44  		return nil, fmt.Errorf("open sstable file %s %s: %w", ns, id, err)
    45  	}
    46  	r, err := sstable.NewReader(file, opts)
    47  	if err != nil {
    48  		return nil, fmt.Errorf("open sstable reader %s %s: %w", ns, id, err)
    49  	}
    50  	return r, nil
    51  }
    52  
    53  func NewPebbleSSTableRangeManagerWithNewReader(newReader NewSSTableReaderFn, cache Unrefer, fs pyramid.FS, hash crypto.Hash) *RangeManager {
    54  	return &RangeManager{
    55  		fs:        fs,
    56  		hash:      hash,
    57  		newReader: newReader,
    58  		cache:     cache,
    59  	}
    60  }
    61  
    62  var (
    63  	// ErrKeyNotFound is the error returned when a path is not found
    64  	ErrKeyNotFound = fmt.Errorf("key: %w", committed.ErrNotFound)
    65  
    66  	_ committed.RangeManager = &RangeManager{}
    67  )
    68  
    69  func (m *RangeManager) Exists(ctx context.Context, ns committed.Namespace, id committed.ID) (bool, error) {
    70  	return m.fs.Exists(ctx, string(ns), string(id))
    71  }
    72  
    73  func (m *RangeManager) GetValueGE(ctx context.Context, ns committed.Namespace, id committed.ID, lookup committed.Key) (*committed.Record, error) {
    74  	reader, err := m.newReader(ctx, ns, id)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	defer m.execAndLog(ctx, reader.Close, "close reader")
    79  
    80  	// TODO(ariels): reader.NewIter(lookup, lookup)?
    81  	it, err := reader.NewIter(nil, nil)
    82  	if err != nil {
    83  		return nil, fmt.Errorf("create iterator: %w", err)
    84  	}
    85  	defer m.execAndLog(ctx, it.Close, "close iterator")
    86  
    87  	// Ranges are keyed by MaxKey, seek to the range that might contain key.
    88  	key, value := it.SeekGE(lookup, sstable.SeekGEFlags(0))
    89  	if key == nil {
    90  		if it.Error() != nil {
    91  			return nil, fmt.Errorf("read metarange from sstable id %s: %w", id, it.Error())
    92  		}
    93  		return nil, ErrKeyNotFound
    94  	}
    95  	vBytes, err := retrieveValue(value)
    96  
    97  	if err != nil {
    98  		return nil, fmt.Errorf("extract value from sstable id %s (key %s): %w", id, key, err)
    99  	}
   100  	return &committed.Record{
   101  		Key:   key.UserKey,
   102  		Value: vBytes,
   103  	}, nil
   104  }
   105  
   106  // GetValue returns the Record matching the key in the SSTable referenced by the id.
   107  // If key is not found, (nil, ErrKeyNotFound) is returned.
   108  func (m *RangeManager) GetValue(ctx context.Context, ns committed.Namespace, id committed.ID, lookup committed.Key) (*committed.Record, error) {
   109  	reader, err := m.newReader(ctx, ns, id)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  	defer m.execAndLog(ctx, reader.Close, "close reader")
   114  
   115  	it, err := reader.NewIter(nil, nil)
   116  	if err != nil {
   117  		return nil, fmt.Errorf("create iterator: %w", err)
   118  	}
   119  	defer m.execAndLog(ctx, it.Close, "close iterator")
   120  
   121  	// actual reading
   122  	key, value := it.SeekGE(lookup, sstable.SeekGEFlags(0))
   123  	if key == nil {
   124  		if it.Error() != nil {
   125  			return nil, fmt.Errorf("read key from sstable id %s: %w", id, it.Error())
   126  		}
   127  
   128  		// lookup path is after the last path in the SSTable
   129  		return nil, ErrKeyNotFound
   130  	}
   131  
   132  	if !bytes.Equal(lookup, key.UserKey) {
   133  		// lookup path in range but key not found
   134  		return nil, ErrKeyNotFound
   135  	}
   136  	vBytes, err := retrieveValue(value)
   137  
   138  	if err != nil {
   139  		return nil, fmt.Errorf("extract value from sstable id %s (key %s): %w", id, key, err)
   140  	}
   141  
   142  	return &committed.Record{
   143  		Key:   key.UserKey,
   144  		Value: vBytes,
   145  	}, nil
   146  }
   147  
   148  // NewRangeIterator takes a given SSTable and returns an EntryIterator seeked to >= "from" path
   149  func (m *RangeManager) NewRangeIterator(ctx context.Context, ns committed.Namespace, tid committed.ID) (committed.ValueIterator, error) {
   150  	reader, err := m.newReader(ctx, ns, tid)
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	iter, err := reader.NewIter(nil, nil)
   156  	if err != nil {
   157  		if e := reader.Close(); e != nil {
   158  			logging.FromContext(ctx).WithError(e).Errorf("Failed de-referencing sstable %s", tid)
   159  		}
   160  		return nil, fmt.Errorf("creating sstable iterator: %w", err)
   161  	}
   162  
   163  	return NewIterator(iter, reader.Close), nil
   164  }
   165  
   166  // GetWriter returns a new SSTable writer instance
   167  func (m *RangeManager) GetWriter(ctx context.Context, ns committed.Namespace, metadata graveler.Metadata) (committed.RangeWriter, error) {
   168  	return NewDiskWriter(ctx, m.fs, ns, m.hash.New(), metadata)
   169  }
   170  
   171  func (m *RangeManager) GetURI(ctx context.Context, ns committed.Namespace, id committed.ID) (string, error) {
   172  	return m.fs.GetRemoteURI(ctx, string(ns), string(id))
   173  }
   174  
   175  func (m *RangeManager) execAndLog(ctx context.Context, f func() error, msg string) {
   176  	if err := f(); err != nil {
   177  		logging.FromContext(ctx).WithError(err).Error(msg)
   178  	}
   179  }
   180  
   181  func (m *RangeManager) Close() error {
   182  	m.cache.Unref()
   183  	return nil
   184  }