github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/internal/base/lazy_value.go (about) 1 // Copyright 2022 The LevelDB-Go and Pebble Authors. All rights reserved. Use 2 // of this source code is governed by a BSD-style license that can be found in 3 // the LICENSE file. 4 5 package base 6 7 import "github.com/cockroachdb/pebble/internal/invariants" 8 9 // A value can have user-defined attributes that are a function of the value 10 // byte slice. For now, we only support "short attributes", which can be 11 // encoded in 3 bits. We will likely extend this to "long attributes" later 12 // for values that are even more expensive to access than those in value 13 // blocks in the same sstable. 14 // 15 // When a sstable writer chooses not to store a value together with the key, 16 // it can call the ShortAttributeExtractor to extract the attribute and store 17 // it together with the key. This allows for cheap retrieval of 18 // AttributeAndLen on the read-path, without doing a more expensive retrieval 19 // of the value. In general, the extraction code may want to also look at the 20 // key to decide how to treat the value, hence the key* parameters. 21 // 22 // Write path performance: The ShortAttributeExtractor func cannot be inlined, 23 // so we will pay the cost of this function call. However, we will only pay 24 // this when (a) the value is not being stored together with the key, and (b) 25 // the key-value pair is being initially written to the DB, or a compaction is 26 // transitioning the key-value pair from being stored together to being stored 27 // separately. 28 29 // ShortAttribute encodes a user-specified attribute of the value. 30 type ShortAttribute uint8 31 32 // MaxShortAttribute is the maximum value of the short attribute (3 bits). 33 const MaxShortAttribute = 7 34 35 // ShortAttributeExtractor is an extractor that given the value, will return 36 // the ShortAttribute. 37 type ShortAttributeExtractor func( 38 key []byte, keyPrefixLen int, value []byte) (ShortAttribute, error) 39 40 // AttributeAndLen represents the pair of value length and the short 41 // attribute. 42 type AttributeAndLen struct { 43 ValueLen int32 44 ShortAttribute ShortAttribute 45 } 46 47 // LazyValue represents a value that may not already have been extracted. 48 // Currently, it can represent either an in-place value (stored with the key) 49 // or a value stored in the value section. However, the interface is general 50 // enough to support values that are stored in separate files. 51 // 52 // LazyValue is used in the InternalIterator interface, such that all 53 // positioning calls return (*InternalKey, LazyValue). It is also exposed via 54 // the public Iterator for callers that need to remember a recent but not 55 // necessarily latest LazyValue, in case they need the actual value in the 56 // future. An example is a caller that is iterating in reverse and looking for 57 // the latest MVCC version for a key -- it cannot identify the latest MVCC 58 // version without stepping to the previous key-value pair e.g. 59 // storage.pebbleMVCCScanner in CockroachDB. 60 // 61 // Performance note: It is important for this struct to not exceed a sizeof 32 62 // bytes, for optimizing the common case of the in-place value. Prior to 63 // introducing LazyValue, we were passing around a []byte which is 24 bytes. 64 // Passing a 40 byte or larger struct causes performance to drop by 75% on 65 // some benchmarks that do tight iteration loops. 66 // 67 // Memory management: 68 // This is subtle, but important for performance. 69 // 70 // A LazyValue returned by an InternalIterator or Iterator is unstable in that 71 // repositioning the iterator will invalidate the memory inside it. A caller 72 // wishing to maintain that LazyValue needs to call LazyValue.Clone(). Note 73 // that this does not fetch the value if it is not in-place. Clone() should 74 // ideally not be called if LazyValue.Value() has been called, since the 75 // cloned LazyValue will forget the extracted/fetched value, and calling 76 // Value() on this clone will cause the value to be extracted again. That is, 77 // Clone() does not make any promise about the memory stability of the 78 // underlying value. 79 // 80 // A user of an iterator that calls LazyValue.Value() wants as much as 81 // possible for the returned value []byte to point to iterator owned memory. 82 // 83 // 1. [P1] The underlying iterator that owns that memory also needs a promise 84 // from that user that at any time there is at most one value []byte slice 85 // that the caller is expecting it to maintain. Otherwise, the underlying 86 // iterator has to maintain multiple such []byte slices which results in 87 // more complicated and inefficient code. 88 // 89 // 2. [P2] The underlying iterator, in order to make the promise that it is 90 // maintaining the one value []byte slice, also needs a way to know when 91 // it is relieved of that promise. One way it is relieved of that promise 92 // is by being told that it is being repositioned. Typically, the owner of 93 // the value []byte slice is a sstable iterator, and it will know that it 94 // is relieved of the promise when it is repositioned. However, consider 95 // the case where the caller has used LazyValue.Clone() and repositioned 96 // the iterator (which is actually a tree of iterators). In this case the 97 // underlying sstable iterator may not even be open. LazyValue.Value() 98 // will still work (at a higher cost), but since the sstable iterator is 99 // not open, it does not have a mechanism to know when the retrieved value 100 // is no longer in use. We refer to this situation as "not satisfying P2". 101 // To handle this situation, the LazyValue.Value() method accepts a caller 102 // owned buffer, that the callee will use if needed. The callee explicitly 103 // tells the caller whether the []byte slice for the value is now owned by 104 // the caller. This will be true if the callee attempted to use buf and 105 // either successfully used it or allocated a new []byte slice. 106 // 107 // To ground the above in reality, we consider three examples of callers of 108 // LazyValue.Value(): 109 // 110 // - Iterator: it calls LazyValue.Value for its own use when merging values. 111 // When merging during reverse iteration, it may have cloned the LazyValue. 112 // In this case it calls LazyValue.Value() on the cloned value, merges it, 113 // and then calls LazyValue.Value() on the current iterator position and 114 // merges it. So it is honoring P1. 115 // 116 // - Iterator on behalf of Iterator clients: The Iterator.Value() method 117 // needs to call LazyValue.Value(). The client of Iterator is satisfying P1 118 // because of the inherent Iterator interface constraint, i.e., it is calling 119 // Iterator.Value() on the current Iterator position. It is possible that 120 // the Iterator has cloned this LazyValue (for the reverse iteration case), 121 // which the client is unaware of, so the underlying sstable iterator may 122 // not be able to satisfy P2. This is ok because Iterator will call 123 // LazyValue.Value with its (reusable) owned buffer. 124 // 125 // - CockroachDB's pebbleMVCCScanner: This will use LazyValues from Iterator 126 // since during reverse iteration in order to find the highest version that 127 // satisfies a read it needs to clone the LazyValue, step back the iterator 128 // and then decide whether it needs the value from the previously cloned 129 // LazyValue. The pebbleMVCCScanner will satisfy P1. The P2 story is 130 // similar to the previous case in that it will call LazyValue.Value with 131 // its (reusable) owned buffer. 132 // 133 // Corollary: callers that directly use InternalIterator can know that they 134 // have done nothing to interfere with promise P2 can pass in a nil buf and be 135 // sure that it will not trigger an allocation. 136 // 137 // Repeated calling of LazyValue.Value: 138 // This is ok as long as the caller continues to satisfy P1. The previously 139 // fetched value will be remembered inside LazyValue to avoid fetching again. 140 // So if the caller's buffer is used the first time the value was fetched, it 141 // is still in use. 142 // 143 // LazyValue fields are visible outside the package for use in 144 // InternalIterator implementations and in Iterator, but not meant for direct 145 // use by users of Pebble. 146 type LazyValue struct { 147 // ValueOrHandle represents a value, or a handle to be passed to ValueFetcher. 148 // - Fetcher == nil: ValueOrHandle is a value. 149 // - Fetcher != nil: ValueOrHandle is a handle and Fetcher.Attribute is 150 // initialized. 151 // The ValueOrHandle exposed by InternalIterator or Iterator may not be stable 152 // if the iterator is stepped. To make it stable, make a copy using Clone. 153 ValueOrHandle []byte 154 // Fetcher provides support for fetching an actually lazy value. 155 Fetcher *LazyFetcher 156 } 157 158 // LazyFetcher supports fetching a lazy value. 159 // 160 // Fetcher and Attribute are to be initialized at creation time. The fields 161 // are arranged to reduce the sizeof this struct. 162 type LazyFetcher struct { 163 // Fetcher, given a handle, returns the value. 164 Fetcher ValueFetcher 165 err error 166 value []byte 167 // Attribute includes the short attribute and value length. 168 Attribute AttributeAndLen 169 fetched bool 170 callerOwned bool 171 } 172 173 // ValueFetcher is an interface for fetching a value. 174 type ValueFetcher interface { 175 // Fetch returns the value, given the handle. It is acceptable to call the 176 // ValueFetcher.Fetch as long as the DB is open. However, one should assume 177 // there is a fast-path when the iterator tree has not moved off the sstable 178 // iterator that initially provided this LazyValue. Hence, to utilize this 179 // fast-path the caller should try to decide whether it needs the value or 180 // not as soon as possible, with minimal possible stepping of the iterator. 181 // 182 // buf will be used if the fetcher cannot satisfy P2 (see earlier comment). 183 // If the fetcher attempted to use buf *and* len(buf) was insufficient, it 184 // will allocate a new slice for the value. In either case it will set 185 // callerOwned to true. 186 Fetch( 187 handle []byte, valLen int32, buf []byte) (val []byte, callerOwned bool, err error) 188 } 189 190 // Value returns the underlying value. 191 func (lv *LazyValue) Value(buf []byte) (val []byte, callerOwned bool, err error) { 192 if lv.Fetcher == nil { 193 return lv.ValueOrHandle, false, nil 194 } 195 // Do the rest of the work in a separate method to attempt mid-stack 196 // inlining of Value(). Unfortunately, this still does not inline since the 197 // cost of 85 exceeds the budget of 80. 198 // 199 // TODO(sumeer): Packing the return values into a struct{[]byte error bool} 200 // causes it to be below the budget. Consider this if we need to recover 201 // more performance. I suspect that inlining this only matters in 202 // micro-benchmarks, and in actual use cases in CockroachDB it will not 203 // matter because there is substantial work done with a fetched value. 204 return lv.fetchValue(buf) 205 } 206 207 // INVARIANT: lv.Fetcher != nil 208 func (lv *LazyValue) fetchValue(buf []byte) (val []byte, callerOwned bool, err error) { 209 f := lv.Fetcher 210 if !f.fetched { 211 f.fetched = true 212 f.value, f.callerOwned, f.err = f.Fetcher.Fetch( 213 lv.ValueOrHandle, lv.Fetcher.Attribute.ValueLen, buf) 214 } 215 return f.value, f.callerOwned, f.err 216 } 217 218 // InPlaceValue returns the value under the assumption that it is in-place. 219 // This is for Pebble-internal code. 220 func (lv *LazyValue) InPlaceValue() []byte { 221 if invariants.Enabled && lv.Fetcher != nil { 222 panic("value must be in-place") 223 } 224 return lv.ValueOrHandle 225 } 226 227 // Len returns the length of the value. 228 func (lv *LazyValue) Len() int { 229 if lv.Fetcher == nil { 230 return len(lv.ValueOrHandle) 231 } 232 return int(lv.Fetcher.Attribute.ValueLen) 233 } 234 235 // TryGetShortAttribute returns the ShortAttribute and a bool indicating 236 // whether the ShortAttribute was populated. 237 func (lv *LazyValue) TryGetShortAttribute() (ShortAttribute, bool) { 238 if lv.Fetcher == nil { 239 return 0, false 240 } 241 return lv.Fetcher.Attribute.ShortAttribute, true 242 } 243 244 // Clone creates a stable copy of the LazyValue, by appending bytes to buf. 245 // The fetcher parameter must be non-nil and may be over-written and used 246 // inside the returned LazyValue -- this is needed to avoid an allocation. 247 // Most callers have at most K cloned LazyValues, where K is hard-coded, so 248 // they can have a pool of exactly K LazyFetcher structs they can reuse in 249 // these calls. The alternative of allocating LazyFetchers from a sync.Pool is 250 // not viable since we have no code trigger for returning to the pool 251 // (LazyValues are simply GC'd). 252 // 253 // NB: It is highly preferable that LazyValue.Value() has not been called, 254 // since the Clone will forget any previously extracted value, and a future 255 // call to Value will cause it to be fetched again. We do this since we don't 256 // want to reason about whether or not to clone an already extracted value 257 // inside the Fetcher (we don't). Property P1 applies here too: if lv1.Value() 258 // has been called, and then lv2 is created as a clone of lv1, then calling 259 // lv2.Value() can invalidate any backing memory maintained inside the fetcher 260 // for lv1 (even though these are the same values). We initially prohibited 261 // calling LazyValue.Clone() if LazyValue.Value() has been called, but there 262 // is at least one complex caller (pebbleMVCCScanner inside CockroachDB) where 263 // it is not easy to prove this invariant. 264 func (lv *LazyValue) Clone(buf []byte, fetcher *LazyFetcher) (LazyValue, []byte) { 265 var lvCopy LazyValue 266 if lv.Fetcher != nil { 267 *fetcher = LazyFetcher{ 268 Fetcher: lv.Fetcher.Fetcher, 269 Attribute: lv.Fetcher.Attribute, 270 // Not copying anything that has been extracted. 271 } 272 lvCopy.Fetcher = fetcher 273 } 274 vLen := len(lv.ValueOrHandle) 275 if vLen == 0 { 276 return lvCopy, buf 277 } 278 bufLen := len(buf) 279 buf = append(buf, lv.ValueOrHandle...) 280 lvCopy.ValueOrHandle = buf[bufLen : bufLen+vLen] 281 return lvCopy, buf 282 } 283 284 // MakeInPlaceValue constructs an in-place value. 285 func MakeInPlaceValue(val []byte) LazyValue { 286 return LazyValue{ValueOrHandle: val} 287 }