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 }