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  }