github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/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, rp ReaderProvider, bufferPool *BufferPool,
    83  ) (Iterator, error) {
    84  	return v.reader.newCompactionIter(bytesIterated, rp, &v.vState, bufferPool)
    85  }
    86  
    87  // NewIterWithBlockPropertyFiltersAndContextEtc wraps
    88  // Reader.NewIterWithBlockPropertyFiltersAndContext. We assume that the passed
    89  // in [lower, upper) bounds will have at least some overlap with the virtual
    90  // sstable bounds. No overlap is not currently supported in the iterator.
    91  func (v *VirtualReader) NewIterWithBlockPropertyFiltersAndContextEtc(
    92  	ctx context.Context,
    93  	lower, upper []byte,
    94  	filterer *BlockPropertiesFilterer,
    95  	hideObsoletePoints, useFilterBlock bool,
    96  	stats *base.InternalIteratorStats,
    97  	rp ReaderProvider,
    98  ) (Iterator, error) {
    99  	return v.reader.newIterWithBlockPropertyFiltersAndContext(
   100  		ctx, lower, upper, filterer, hideObsoletePoints, useFilterBlock, stats, rp, &v.vState,
   101  	)
   102  }
   103  
   104  // ValidateBlockChecksumsOnBacking will call ValidateBlockChecksumsOnBacking on the underlying reader.
   105  // Note that block checksum validation is NOT restricted to virtual sstable bounds.
   106  func (v *VirtualReader) ValidateBlockChecksumsOnBacking() error {
   107  	return v.reader.ValidateBlockChecksums()
   108  }
   109  
   110  // NewRawRangeDelIter wraps Reader.NewRawRangeDelIter.
   111  func (v *VirtualReader) NewRawRangeDelIter() (keyspan.FragmentIterator, error) {
   112  	iter, err := v.reader.NewRawRangeDelIter()
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	if iter == nil {
   117  		return nil, nil
   118  	}
   119  
   120  	// Truncation of spans isn't allowed at a user key that also contains points
   121  	// in the same virtual sstable, as it would lead to covered points getting
   122  	// uncovered. Set panicOnUpperTruncate to true if the file's upper bound
   123  	// is not an exclusive sentinel.
   124  	//
   125  	// As an example, if an sstable contains a rangedel a-c and point keys at
   126  	// a.SET.2 and b.SET.3, the file bounds [a#2,SET-b#RANGEDELSENTINEL] are
   127  	// allowed (as they exclude b.SET.3), or [a#2,SET-c#RANGEDELSENTINEL] (as it
   128  	// includes both point keys), but not [a#2,SET-b#3,SET] (as it would truncate
   129  	// the rangedel at b and lead to the point being uncovered).
   130  	return keyspan.Truncate(
   131  		v.reader.Compare, iter, v.vState.lower.UserKey, v.vState.upper.UserKey,
   132  		&v.vState.lower, &v.vState.upper, !v.vState.upper.IsExclusiveSentinel(), /* panicOnUpperTruncate */
   133  	), nil
   134  }
   135  
   136  // NewRawRangeKeyIter wraps Reader.NewRawRangeKeyIter.
   137  func (v *VirtualReader) NewRawRangeKeyIter() (keyspan.FragmentIterator, error) {
   138  	iter, err := v.reader.NewRawRangeKeyIter()
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  	if iter == nil {
   143  		return nil, nil
   144  	}
   145  
   146  	// Truncation of spans isn't allowed at a user key that also contains points
   147  	// in the same virtual sstable, as it would lead to covered points getting
   148  	// uncovered. Set panicOnUpperTruncate to true if the file's upper bound
   149  	// is not an exclusive sentinel.
   150  	//
   151  	// As an example, if an sstable contains a range key a-c and point keys at
   152  	// a.SET.2 and b.SET.3, the file bounds [a#2,SET-b#RANGEKEYSENTINEL] are
   153  	// allowed (as they exclude b.SET.3), or [a#2,SET-c#RANGEKEYSENTINEL] (as it
   154  	// includes both point keys), but not [a#2,SET-b#3,SET] (as it would truncate
   155  	// the range key at b and lead to the point being uncovered).
   156  	return keyspan.Truncate(
   157  		v.reader.Compare, iter, v.vState.lower.UserKey, v.vState.upper.UserKey,
   158  		&v.vState.lower, &v.vState.upper, !v.vState.upper.IsExclusiveSentinel(), /* panicOnUpperTruncate */
   159  	), nil
   160  }
   161  
   162  // Constrain bounds will narrow the start, end bounds if they do not fit within
   163  // the virtual sstable. The function will return if the new end key is
   164  // inclusive.
   165  func (v *virtualState) constrainBounds(
   166  	start, end []byte, endInclusive bool,
   167  ) (lastKeyInclusive bool, first []byte, last []byte) {
   168  	first = start
   169  	if start == nil || v.Compare(start, v.lower.UserKey) < 0 {
   170  		first = v.lower.UserKey
   171  	}
   172  
   173  	// Note that we assume that start, end has some overlap with the virtual
   174  	// sstable bounds.
   175  	last = v.upper.UserKey
   176  	lastKeyInclusive = !v.upper.IsExclusiveSentinel()
   177  	if end != nil {
   178  		cmp := v.Compare(end, v.upper.UserKey)
   179  		switch {
   180  		case cmp == 0:
   181  			lastKeyInclusive = !v.upper.IsExclusiveSentinel() && endInclusive
   182  			last = v.upper.UserKey
   183  		case cmp > 0:
   184  			lastKeyInclusive = !v.upper.IsExclusiveSentinel()
   185  			last = v.upper.UserKey
   186  		default:
   187  			lastKeyInclusive = endInclusive
   188  			last = end
   189  		}
   190  	}
   191  	// TODO(bananabrick): What if someone passes in bounds completely outside of
   192  	// virtual sstable bounds?
   193  	return lastKeyInclusive, first, last
   194  }
   195  
   196  // EstimateDiskUsage just calls VirtualReader.reader.EstimateDiskUsage after
   197  // enforcing the virtual sstable bounds.
   198  func (v *VirtualReader) EstimateDiskUsage(start, end []byte) (uint64, error) {
   199  	_, f, l := v.vState.constrainBounds(start, end, true /* endInclusive */)
   200  	return v.reader.EstimateDiskUsage(f, l)
   201  }
   202  
   203  // CommonProperties implements the CommonReader interface.
   204  func (v *VirtualReader) CommonProperties() *CommonProperties {
   205  	return &v.Properties
   206  }