github.com/cockroachdb/pebble@v0.0.0-20231214172447-ab4952c5f87b/sstable/reader_virtual.go (about) 1 // Copyright 2011 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 sstable 6 7 import ( 8 "context" 9 10 "github.com/cockroachdb/pebble/internal/base" 11 "github.com/cockroachdb/pebble/internal/keyspan" 12 "github.com/cockroachdb/pebble/internal/manifest" 13 ) 14 15 // VirtualReader wraps Reader. Its purpose is to restrict functionality of the 16 // Reader which should be inaccessible to virtual sstables, and enforce bounds 17 // invariants associated with virtual sstables. All reads on virtual sstables 18 // should go through a VirtualReader. 19 // 20 // INVARIANT: Any iterators created through a virtual reader will guarantee that 21 // they don't expose keys outside the virtual sstable bounds. 22 type VirtualReader struct { 23 vState virtualState 24 reader *Reader 25 Properties CommonProperties 26 } 27 28 // Lightweight virtual sstable state which can be passed to sstable iterators. 29 type virtualState struct { 30 lower InternalKey 31 upper InternalKey 32 fileNum base.FileNum 33 Compare Compare 34 isForeign bool 35 } 36 37 func ceilDiv(a, b uint64) uint64 { 38 return (a + b - 1) / b 39 } 40 41 // MakeVirtualReader is used to contruct a reader which can read from virtual 42 // sstables. 43 func MakeVirtualReader( 44 reader *Reader, meta manifest.VirtualFileMeta, isForeign bool, 45 ) VirtualReader { 46 if reader.fileNum != meta.FileBacking.DiskFileNum { 47 panic("pebble: invalid call to MakeVirtualReader") 48 } 49 50 vState := virtualState{ 51 lower: meta.Smallest, 52 upper: meta.Largest, 53 fileNum: meta.FileNum, 54 Compare: reader.Compare, 55 isForeign: isForeign, 56 } 57 v := VirtualReader{ 58 vState: vState, 59 reader: reader, 60 } 61 62 v.Properties.RawKeySize = ceilDiv(reader.Properties.RawKeySize*meta.Size, meta.FileBacking.Size) 63 v.Properties.RawValueSize = ceilDiv(reader.Properties.RawValueSize*meta.Size, meta.FileBacking.Size) 64 v.Properties.NumEntries = ceilDiv(reader.Properties.NumEntries*meta.Size, meta.FileBacking.Size) 65 v.Properties.NumDeletions = ceilDiv(reader.Properties.NumDeletions*meta.Size, meta.FileBacking.Size) 66 v.Properties.NumRangeDeletions = ceilDiv(reader.Properties.NumRangeDeletions*meta.Size, meta.FileBacking.Size) 67 v.Properties.NumRangeKeyDels = ceilDiv(reader.Properties.NumRangeKeyDels*meta.Size, meta.FileBacking.Size) 68 69 // Note that we rely on NumRangeKeySets for correctness. If the sstable may 70 // contain range keys, then NumRangeKeySets must be > 0. ceilDiv works because 71 // meta.Size will not be 0 for virtual sstables. 72 v.Properties.NumRangeKeySets = ceilDiv(reader.Properties.NumRangeKeySets*meta.Size, meta.FileBacking.Size) 73 v.Properties.ValueBlocksSize = ceilDiv(reader.Properties.ValueBlocksSize*meta.Size, meta.FileBacking.Size) 74 v.Properties.NumSizedDeletions = ceilDiv(reader.Properties.NumSizedDeletions*meta.Size, meta.FileBacking.Size) 75 v.Properties.RawPointTombstoneKeySize = ceilDiv(reader.Properties.RawPointTombstoneKeySize*meta.Size, meta.FileBacking.Size) 76 v.Properties.RawPointTombstoneValueSize = ceilDiv(reader.Properties.RawPointTombstoneValueSize*meta.Size, meta.FileBacking.Size) 77 return v 78 } 79 80 // NewCompactionIter is the compaction iterator function for virtual readers. 81 func (v *VirtualReader) NewCompactionIter( 82 bytesIterated *uint64, 83 categoryAndQoS CategoryAndQoS, 84 statsCollector *CategoryStatsCollector, 85 rp ReaderProvider, 86 bufferPool *BufferPool, 87 ) (Iterator, error) { 88 return v.reader.newCompactionIter( 89 bytesIterated, categoryAndQoS, statsCollector, rp, &v.vState, bufferPool) 90 } 91 92 // NewIterWithBlockPropertyFiltersAndContextEtc wraps 93 // Reader.NewIterWithBlockPropertyFiltersAndContext. We assume that the passed 94 // in [lower, upper) bounds will have at least some overlap with the virtual 95 // sstable bounds. No overlap is not currently supported in the iterator. 96 func (v *VirtualReader) NewIterWithBlockPropertyFiltersAndContextEtc( 97 ctx context.Context, 98 lower, upper []byte, 99 filterer *BlockPropertiesFilterer, 100 hideObsoletePoints, useFilterBlock bool, 101 stats *base.InternalIteratorStats, 102 categoryAndQoS CategoryAndQoS, 103 statsCollector *CategoryStatsCollector, 104 rp ReaderProvider, 105 ) (Iterator, error) { 106 return v.reader.newIterWithBlockPropertyFiltersAndContext( 107 ctx, lower, upper, filterer, hideObsoletePoints, useFilterBlock, stats, 108 categoryAndQoS, statsCollector, rp, &v.vState) 109 } 110 111 // ValidateBlockChecksumsOnBacking will call ValidateBlockChecksumsOnBacking on the underlying reader. 112 // Note that block checksum validation is NOT restricted to virtual sstable bounds. 113 func (v *VirtualReader) ValidateBlockChecksumsOnBacking() error { 114 return v.reader.ValidateBlockChecksums() 115 } 116 117 // NewRawRangeDelIter wraps Reader.NewRawRangeDelIter. 118 func (v *VirtualReader) NewRawRangeDelIter() (keyspan.FragmentIterator, error) { 119 iter, err := v.reader.NewRawRangeDelIter() 120 if err != nil { 121 return nil, err 122 } 123 if iter == nil { 124 return nil, nil 125 } 126 127 // Truncation of spans isn't allowed at a user key that also contains points 128 // in the same virtual sstable, as it would lead to covered points getting 129 // uncovered. Set panicOnUpperTruncate to true if the file's upper bound 130 // is not an exclusive sentinel. 131 // 132 // As an example, if an sstable contains a rangedel a-c and point keys at 133 // a.SET.2 and b.SET.3, the file bounds [a#2,SET-b#RANGEDELSENTINEL] are 134 // allowed (as they exclude b.SET.3), or [a#2,SET-c#RANGEDELSENTINEL] (as it 135 // includes both point keys), but not [a#2,SET-b#3,SET] (as it would truncate 136 // the rangedel at b and lead to the point being uncovered). 137 return keyspan.Truncate( 138 v.reader.Compare, iter, v.vState.lower.UserKey, v.vState.upper.UserKey, 139 &v.vState.lower, &v.vState.upper, !v.vState.upper.IsExclusiveSentinel(), /* panicOnUpperTruncate */ 140 ), nil 141 } 142 143 // NewRawRangeKeyIter wraps Reader.NewRawRangeKeyIter. 144 func (v *VirtualReader) NewRawRangeKeyIter() (keyspan.FragmentIterator, error) { 145 iter, err := v.reader.NewRawRangeKeyIter() 146 if err != nil { 147 return nil, err 148 } 149 if iter == nil { 150 return nil, nil 151 } 152 153 // Truncation of spans isn't allowed at a user key that also contains points 154 // in the same virtual sstable, as it would lead to covered points getting 155 // uncovered. Set panicOnUpperTruncate to true if the file's upper bound 156 // is not an exclusive sentinel. 157 // 158 // As an example, if an sstable contains a range key a-c and point keys at 159 // a.SET.2 and b.SET.3, the file bounds [a#2,SET-b#RANGEKEYSENTINEL] are 160 // allowed (as they exclude b.SET.3), or [a#2,SET-c#RANGEKEYSENTINEL] (as it 161 // includes both point keys), but not [a#2,SET-b#3,SET] (as it would truncate 162 // the range key at b and lead to the point being uncovered). 163 return keyspan.Truncate( 164 v.reader.Compare, iter, v.vState.lower.UserKey, v.vState.upper.UserKey, 165 &v.vState.lower, &v.vState.upper, !v.vState.upper.IsExclusiveSentinel(), /* panicOnUpperTruncate */ 166 ), nil 167 } 168 169 // Constrain bounds will narrow the start, end bounds if they do not fit within 170 // the virtual sstable. The function will return if the new end key is 171 // inclusive. 172 func (v *virtualState) constrainBounds( 173 start, end []byte, endInclusive bool, 174 ) (lastKeyInclusive bool, first []byte, last []byte) { 175 first = start 176 if start == nil || v.Compare(start, v.lower.UserKey) < 0 { 177 first = v.lower.UserKey 178 } 179 180 // Note that we assume that start, end has some overlap with the virtual 181 // sstable bounds. 182 last = v.upper.UserKey 183 lastKeyInclusive = !v.upper.IsExclusiveSentinel() 184 if end != nil { 185 cmp := v.Compare(end, v.upper.UserKey) 186 switch { 187 case cmp == 0: 188 lastKeyInclusive = !v.upper.IsExclusiveSentinel() && endInclusive 189 last = v.upper.UserKey 190 case cmp > 0: 191 lastKeyInclusive = !v.upper.IsExclusiveSentinel() 192 last = v.upper.UserKey 193 default: 194 lastKeyInclusive = endInclusive 195 last = end 196 } 197 } 198 // TODO(bananabrick): What if someone passes in bounds completely outside of 199 // virtual sstable bounds? 200 return lastKeyInclusive, first, last 201 } 202 203 // EstimateDiskUsage just calls VirtualReader.reader.EstimateDiskUsage after 204 // enforcing the virtual sstable bounds. 205 func (v *VirtualReader) EstimateDiskUsage(start, end []byte) (uint64, error) { 206 _, f, l := v.vState.constrainBounds(start, end, true /* endInclusive */) 207 return v.reader.EstimateDiskUsage(f, l) 208 } 209 210 // CommonProperties implements the CommonReader interface. 211 func (v *VirtualReader) CommonProperties() *CommonProperties { 212 return &v.Properties 213 }