0

Update deps

This commit is contained in:
eleksir
2024-04-19 21:30:16 +03:00
parent 3b6dd3fdef
commit 442142291c
585 changed files with 21099 additions and 15328 deletions

View File

@@ -113,11 +113,28 @@ func ingestSynthesizeShared(
meta.FileBacking.Size = sm.Size
if sm.LargestRangeKey.Valid() && sm.LargestRangeKey.UserKey != nil {
// Initialize meta.{HasRangeKeys,Smallest,Largest}, etc.
meta.ExtendRangeKeyBounds(opts.Comparer.Compare, sm.SmallestRangeKey, sm.LargestRangeKey)
//
// NB: We create new internal keys and pass them into ExternalRangeKeyBounds
// so that we can sub a zero sequence number into the bounds. We can set
// the sequence number to anything here; it'll be reset in ingestUpdateSeqNum
// anyway. However we do need to use the same sequence number across all
// bound keys at this step so that we end up with bounds that are consistent
// across point/range keys.
smallestRangeKey := base.MakeInternalKey(sm.SmallestRangeKey.UserKey, 0, sm.SmallestRangeKey.Kind())
largestRangeKey := base.MakeExclusiveSentinelKey(sm.LargestRangeKey.Kind(), sm.LargestRangeKey.UserKey)
meta.ExtendRangeKeyBounds(opts.Comparer.Compare, smallestRangeKey, largestRangeKey)
}
if sm.LargestPointKey.Valid() && sm.LargestPointKey.UserKey != nil {
// Initialize meta.{HasPointKeys,Smallest,Largest}, etc.
meta.ExtendPointKeyBounds(opts.Comparer.Compare, sm.SmallestPointKey, sm.LargestPointKey)
//
// See point above in the ExtendRangeKeyBounds call on why we use a zero
// sequence number here.
smallestPointKey := base.MakeInternalKey(sm.SmallestPointKey.UserKey, 0, sm.SmallestPointKey.Kind())
largestPointKey := base.MakeInternalKey(sm.LargestPointKey.UserKey, 0, sm.LargestPointKey.Kind())
if sm.LargestPointKey.IsExclusiveSentinel() {
largestPointKey = base.MakeRangeDeleteSentinelKey(sm.LargestPointKey.UserKey)
}
meta.ExtendPointKeyBounds(opts.Comparer.Compare, smallestPointKey, largestPointKey)
}
if err := meta.Validate(opts.Comparer.Compare, opts.Comparer.FormatKey); err != nil {
return nil, err
@@ -780,16 +797,21 @@ func overlapWithIterator(
return computeOverlapWithSpans(*rangeDelIter)
}
// ingestTargetLevel returns the target level for a file being ingested.
// If suggestSplit is true, it accounts for ingest-time splitting as part of
// its target level calculation, and if a split candidate is found, that file
// is returned as the splitFile.
func ingestTargetLevel(
newIters tableNewIters,
newRangeKeyIter keyspan.TableNewSpanIter,
iterOps IterOptions,
cmp Compare,
comparer *Comparer,
v *version,
baseLevel int,
compactions map[*compaction]struct{},
meta *fileMetadata,
) (int, error) {
suggestSplit bool,
) (targetLevel int, splitFile *fileMetadata, err error) {
// Find the lowest level which does not have any files which overlap meta. We
// search from L0 to L6 looking for whether there are any files in the level
// which overlap meta. We want the "lowest" level (where lower means
@@ -804,6 +826,14 @@ func ingestTargetLevel(
// violate the sequence number invariant.
// - no file boundary overlap with level i, since that will violate the
// invariant that files do not overlap in levels i > 0.
// - if there is only a file overlap at a given level, and no data overlap,
// we can still slot a file at that level. We return the fileMetadata with
// which we have file boundary overlap (must be only one file, as sstable
// bounds are usually tight on user keys) and the caller is expected to split
// that sstable into two virtual sstables, allowing this file to go into that
// level. Note that if we have file boundary overlap with two files, which
// should only happen on rare occasions, we treat it as data overlap and
// don't use this optimization.
//
// The file boundary overlap check is simpler to conceptualize. Consider the
// following example, in which the ingested file lies completely before or
@@ -848,16 +878,14 @@ func ingestTargetLevel(
// existing point that falls within the ingested table bounds as being "data
// overlap".
targetLevel := 0
// This assertion implicitly checks that we have the current version of
// the metadata.
if v.L0Sublevels == nil {
return 0, errors.AssertionFailedf("could not read L0 sublevels")
return 0, nil, errors.AssertionFailedf("could not read L0 sublevels")
}
// Check for overlap over the keys of L0 by iterating over the sublevels.
for subLevel := 0; subLevel < len(v.L0SublevelFiles); subLevel++ {
iter := newLevelIter(iterOps, cmp, nil /* split */, newIters,
iter := newLevelIter(iterOps, comparer, newIters,
v.L0Sublevels.Levels[subLevel].Iter(), manifest.Level(0), internalIterOpts{})
var rangeDelIter keyspan.FragmentIterator
@@ -867,7 +895,7 @@ func ingestTargetLevel(
levelIter := keyspan.LevelIter{}
levelIter.Init(
keyspan.SpanIterOptions{}, cmp, newRangeKeyIter,
keyspan.SpanIterOptions{}, comparer.Compare, newRangeKeyIter,
v.L0Sublevels.Levels[subLevel].Iter(), manifest.Level(0), manifest.KeyTypeRange,
)
@@ -875,20 +903,20 @@ func ingestTargetLevel(
smallest: meta.Smallest,
largest: meta.Largest,
}
overlap := overlapWithIterator(iter, &rangeDelIter, &levelIter, kr, cmp)
overlap := overlapWithIterator(iter, &rangeDelIter, &levelIter, kr, comparer.Compare)
err := iter.Close() // Closes range del iter as well.
err = firstError(err, levelIter.Close())
if err != nil {
return 0, err
return 0, nil, err
}
if overlap {
return targetLevel, nil
return targetLevel, nil, nil
}
}
level := baseLevel
for ; level < numLevels; level++ {
levelIter := newLevelIter(iterOps, cmp, nil /* split */, newIters,
levelIter := newLevelIter(iterOps, comparer, newIters,
v.Levels[level].Iter(), manifest.Level(level), internalIterOpts{})
var rangeDelIter keyspan.FragmentIterator
// Pass in a non-nil pointer to rangeDelIter so that levelIter.findFileGE
@@ -897,7 +925,7 @@ func ingestTargetLevel(
rkeyLevelIter := &keyspan.LevelIter{}
rkeyLevelIter.Init(
keyspan.SpanIterOptions{}, cmp, newRangeKeyIter,
keyspan.SpanIterOptions{}, comparer.Compare, newRangeKeyIter,
v.Levels[level].Iter(), manifest.Level(level), manifest.KeyTypeRange,
)
@@ -905,46 +933,68 @@ func ingestTargetLevel(
smallest: meta.Smallest,
largest: meta.Largest,
}
overlap := overlapWithIterator(levelIter, &rangeDelIter, rkeyLevelIter, kr, cmp)
overlap := overlapWithIterator(levelIter, &rangeDelIter, rkeyLevelIter, kr, comparer.Compare)
err := levelIter.Close() // Closes range del iter as well.
err = firstError(err, rkeyLevelIter.Close())
if err != nil {
return 0, err
return 0, nil, err
}
if overlap {
return targetLevel, nil
return targetLevel, splitFile, nil
}
// Check boundary overlap.
boundaryOverlaps := v.Overlaps(level, cmp, meta.Smallest.UserKey,
var candidateSplitFile *fileMetadata
boundaryOverlaps := v.Overlaps(level, comparer.Compare, meta.Smallest.UserKey,
meta.Largest.UserKey, meta.Largest.IsExclusiveSentinel())
if !boundaryOverlaps.Empty() {
continue
// We are already guaranteed to not have any data overlaps with files
// in boundaryOverlaps, otherwise we'd have returned in the above if
// statements. Use this, plus boundaryOverlaps.Len() == 1 to detect for
// the case where we can slot this file into the current level despite
// a boundary overlap, by splitting one existing file into two virtual
// sstables.
if suggestSplit && boundaryOverlaps.Len() == 1 {
iter := boundaryOverlaps.Iter()
candidateSplitFile = iter.First()
} else {
// We either don't want to suggest ingest-time splits (i.e.
// !suggestSplit), or we boundary-overlapped with more than one file.
continue
}
}
// Check boundary overlap with any ongoing compactions.
// Check boundary overlap with any ongoing compactions. We consider an
// overlapping compaction that's writing files to an output level as
// equivalent to boundary overlap with files in that output level.
//
// We cannot check for data overlap with the new SSTs compaction will
// produce since compaction hasn't been done yet. However, there's no need
// to check since all keys in them will either be from c.startLevel or
// c.outputLevel, both levels having their data overlap already tested
// negative (else we'd have returned earlier).
// We cannot check for data overlap with the new SSTs compaction will produce
// since compaction hasn't been done yet. However, there's no need to check
// since all keys in them will be from levels in [c.startLevel,
// c.outputLevel], and all those levels have already had their data overlap
// tested negative (else we'd have returned earlier).
//
// An alternative approach would be to cancel these compactions and proceed
// with an ingest-time split on this level if necessary. However, compaction
// cancellation can result in significant wasted effort and is best avoided
// unless necessary.
overlaps := false
for c := range compactions {
if c.outputLevel == nil || level != c.outputLevel.level {
continue
}
if cmp(meta.Smallest.UserKey, c.largest.UserKey) <= 0 &&
cmp(meta.Largest.UserKey, c.smallest.UserKey) >= 0 {
if comparer.Compare(meta.Smallest.UserKey, c.largest.UserKey) <= 0 &&
comparer.Compare(meta.Largest.UserKey, c.smallest.UserKey) >= 0 {
overlaps = true
break
}
}
if !overlaps {
targetLevel = level
splitFile = candidateSplitFile
}
}
return targetLevel, nil
return targetLevel, splitFile, nil
}
// Ingest ingests a set of sstables into the DB. Ingestion of the files is
@@ -1126,7 +1176,7 @@ func (d *DB) newIngestedFlushableEntry(
return nil, err
}
f := newIngestedFlushable(meta, d.cmp, d.split, d.newIters, d.tableNewRangeKeyIter)
f := newIngestedFlushable(meta, d.opts.Comparer, d.newIters, d.tableNewRangeKeyIter)
// NB: The logNum/seqNum are the WAL number which we're writing this entry
// to and the sequence number within the WAL which we'll write this entry
@@ -1226,7 +1276,7 @@ func (d *DB) ingest(
if len(shared) > 0 && d.opts.Experimental.RemoteStorage == nil {
panic("cannot ingest shared sstables with nil SharedStorage")
}
if (exciseSpan.Valid() || len(shared) > 0 || len(external) > 0) && d.FormatMajorVersion() < ExperimentalFormatVirtualSSTables {
if (exciseSpan.Valid() || len(shared) > 0 || len(external) > 0) && d.FormatMajorVersion() < FormatVirtualSSTables {
return IngestOperationStats{}, errors.New("pebble: format major version too old for excise, shared or external sstable ingestion")
}
// Allocate file numbers for all of the files being ingested and mark them as
@@ -1558,7 +1608,7 @@ func (d *DB) excise(
var iter internalIterator
var rangeDelIter keyspan.FragmentIterator
var rangeKeyIter keyspan.FragmentIterator
backingTableCreated := false
needsBacking := false
// Create a file to the left of the excise span, if necessary.
// The bounds of this file will be [m.Smallest, lastKeyBefore(exciseSpan.Start)].
//
@@ -1672,14 +1722,20 @@ func (d *DB) excise(
leftFile.ValidateVirtual(m)
d.checkVirtualBounds(leftFile)
ve.NewFiles = append(ve.NewFiles, newFileEntry{Level: level, Meta: leftFile})
ve.CreatedBackingTables = append(ve.CreatedBackingTables, leftFile.FileBacking)
backingTableCreated = true
needsBacking = true
numCreatedFiles++
}
}
// Create a file to the right, if necessary.
if exciseSpan.Contains(d.cmp, m.Largest) {
// No key exists to the right of the excise span in this file.
if needsBacking && !m.Virtual {
// If m is virtual, then its file backing is already known to the manifest.
// We don't need to create another file backing. Note that there must be
// only one CreatedBackingTables entry per backing sstable. This is
// indicated by the VersionEdit.CreatedBackingTables invariant.
ve.CreatedBackingTables = append(ve.CreatedBackingTables, m.FileBacking)
}
return ve.NewFiles[len(ve.NewFiles)-numCreatedFiles:], nil
}
// Create a new file, rightFile, between [firstKeyAfter(exciseSpan.End), m.Largest].
@@ -1734,7 +1790,8 @@ func (d *DB) excise(
smallestPointKey.UserKey = firstRangeDel
rightFile.ExtendPointKeyBounds(d.cmp, smallestPointKey, largestPointKey)
}
} else if m.HasRangeKeys && !exciseSpan.Contains(d.cmp, m.LargestRangeKey) {
}
if m.HasRangeKeys && !exciseSpan.Contains(d.cmp, m.LargestRangeKey) {
// This file will contain range keys.
largestRangeKey := m.LargestRangeKey
if rangeKeyIter == nil {
@@ -1781,13 +1838,18 @@ func (d *DB) excise(
rightFile.ValidateVirtual(m)
d.checkVirtualBounds(rightFile)
ve.NewFiles = append(ve.NewFiles, newFileEntry{Level: level, Meta: rightFile})
if !backingTableCreated {
ve.CreatedBackingTables = append(ve.CreatedBackingTables, rightFile.FileBacking)
backingTableCreated = true
}
needsBacking = true
numCreatedFiles++
}
if needsBacking && !m.Virtual {
// If m is virtual, then its file backing is already known to the manifest.
// We don't need to create another file backing. Note that there must be
// only one CreatedBackingTables entry per backing sstable. This is
// indicated by the VersionEdit.CreatedBackingTables invariant.
ve.CreatedBackingTables = append(ve.CreatedBackingTables, m.FileBacking)
}
if err := rightFile.Validate(d.cmp, d.opts.Comparer.FormatKey); err != nil {
return nil, err
}
@@ -1798,12 +1860,129 @@ type ingestTargetLevelFunc func(
newIters tableNewIters,
newRangeKeyIter keyspan.TableNewSpanIter,
iterOps IterOptions,
cmp Compare,
comparer *Comparer,
v *version,
baseLevel int,
compactions map[*compaction]struct{},
meta *fileMetadata,
) (int, error)
suggestSplit bool,
) (int, *fileMetadata, error)
type ingestSplitFile struct {
// ingestFile is the file being ingested.
ingestFile *fileMetadata
// splitFile is the file that needs to be split to allow ingestFile to slot
// into `level` level.
splitFile *fileMetadata
// The level where ingestFile will go (and where splitFile already is).
level int
}
// ingestSplit splits files specified in `files` and updates ve in-place to
// account for existing files getting split into two virtual sstables. The map
// `replacedFiles` contains an in-progress map of all files that have been
// replaced with new virtual sstables in this version edit so far, which is also
// updated in-place.
//
// d.mu as well as the manifest lock must be held when calling this method.
func (d *DB) ingestSplit(
ve *versionEdit,
updateMetrics func(*fileMetadata, int, []newFileEntry),
files []ingestSplitFile,
replacedFiles map[base.FileNum][]newFileEntry,
) error {
for _, s := range files {
// replacedFiles can be thought of as a tree, where we start iterating with
// s.splitFile and run its fileNum through replacedFiles, then find which of
// the replaced files overlaps with s.ingestFile, which becomes the new
// splitFile, then we check splitFile's replacements in replacedFiles again
// for overlap with s.ingestFile, and so on until we either can't find the
// current splitFile in replacedFiles (i.e. that's the file that now needs to
// be split), or we don't find a file that overlaps with s.ingestFile, which
// means a prior ingest split already produced enough room for s.ingestFile
// to go into this level without necessitating another ingest split.
splitFile := s.splitFile
for splitFile != nil {
replaced, ok := replacedFiles[splitFile.FileNum]
if !ok {
break
}
updatedSplitFile := false
for i := range replaced {
if replaced[i].Meta.Overlaps(d.cmp, s.ingestFile.Smallest.UserKey, s.ingestFile.Largest.UserKey, s.ingestFile.Largest.IsExclusiveSentinel()) {
if updatedSplitFile {
// This should never happen because the earlier ingestTargetLevel
// function only finds split file candidates that are guaranteed to
// have no data overlap, only boundary overlap. See the comments
// in that method to see the definitions of data vs boundary
// overlap. That, plus the fact that files in `replaced` are
// guaranteed to have file bounds that are tight on user keys
// (as that's what `d.excise` produces), means that the only case
// where we overlap with two or more files in `replaced` is if we
// actually had data overlap all along, or if the ingestion files
// were overlapping, either of which is an invariant violation.
panic("updated with two files in ingestSplit")
}
splitFile = replaced[i].Meta
updatedSplitFile = true
}
}
if !updatedSplitFile {
// None of the replaced files overlapped with the file being ingested.
// This can happen if we've already excised a span overlapping with
// this file, or if we have consecutive ingested files that can slide
// within the same gap between keys in an existing file. For instance,
// if an existing file has keys a and g and we're ingesting b-c, d-e,
// the first loop iteration will split the existing file into one that
// ends in a and another that starts at g, and the second iteration will
// fall into this case and require no splitting.
//
// No splitting necessary.
splitFile = nil
}
}
if splitFile == nil {
continue
}
// NB: excise operates on [start, end). We're splitting at [start, end]
// (assuming !s.ingestFile.Largest.IsExclusiveSentinel()). The conflation
// of exclusive vs inclusive end bounds should not make a difference here
// as we're guaranteed to not have any data overlap between splitFile and
// s.ingestFile, so panic if we do see a newly added file with an endKey
// equalling s.ingestFile.Largest, and !s.ingestFile.Largest.IsExclusiveSentinel()
added, err := d.excise(KeyRange{Start: s.ingestFile.Smallest.UserKey, End: s.ingestFile.Largest.UserKey}, splitFile, ve, s.level)
if err != nil {
return err
}
if _, ok := ve.DeletedFiles[deletedFileEntry{
Level: s.level,
FileNum: splitFile.FileNum,
}]; !ok {
panic("did not split file that was expected to be split")
}
replacedFiles[splitFile.FileNum] = added
for i := range added {
if s.ingestFile.Overlaps(d.cmp, added[i].Meta.Smallest.UserKey, added[i].Meta.Largest.UserKey, added[i].Meta.Largest.IsExclusiveSentinel()) {
panic("ingest-time split produced a file that overlaps with ingested file")
}
}
updateMetrics(splitFile, s.level, added)
}
// Flatten the version edit by removing any entries from ve.NewFiles that
// are also in ve.DeletedFiles.
newNewFiles := ve.NewFiles[:0]
for i := range ve.NewFiles {
fn := ve.NewFiles[i].Meta.FileNum
deEntry := deletedFileEntry{Level: ve.NewFiles[i].Level, FileNum: fn}
if _, ok := ve.DeletedFiles[deEntry]; ok {
delete(ve.DeletedFiles, deEntry)
} else {
newNewFiles = append(newNewFiles, ve.NewFiles[i])
}
}
ve.NewFiles = newNewFiles
return nil
}
func (d *DB) ingestApply(
jobID int,
@@ -1818,7 +1997,7 @@ func (d *DB) ingestApply(
ve := &versionEdit{
NewFiles: make([]newFileEntry, lr.fileCount),
}
if exciseSpan.Valid() {
if exciseSpan.Valid() || (d.opts.Experimental.IngestSplit != nil && d.opts.Experimental.IngestSplit()) {
ve.DeletedFiles = map[manifest.DeletedFileEntry]*manifest.FileMetadata{}
}
metrics := make(map[int]*LevelMetrics)
@@ -1843,9 +2022,17 @@ func (d *DB) ingestApply(
}
}
shouldIngestSplit := d.opts.Experimental.IngestSplit != nil &&
d.opts.Experimental.IngestSplit() && d.FormatMajorVersion() >= FormatVirtualSSTables
current := d.mu.versions.currentVersion()
baseLevel := d.mu.versions.picker.getBaseLevel()
iterOps := IterOptions{logger: d.opts.Logger}
// filesToSplit is a list where each element is a pair consisting of a file
// being ingested and a file being split to make room for an ingestion into
// that level. Each ingested file will appear at most once in this list. It
// is possible for split files to appear twice in this list.
filesToSplit := make([]ingestSplitFile, 0)
checkCompactions := false
for i := 0; i < lr.fileCount; i++ {
// Determine the lowest level in the LSM for which the sstable doesn't
// overlap any existing files in the level.
@@ -1878,6 +2065,7 @@ func (d *DB) ingestApply(
if externalFile {
ve.CreatedBackingTables = append(ve.CreatedBackingTables, m.FileBacking)
}
var splitFile *fileMetadata
if exciseSpan.Valid() && exciseSpan.Contains(d.cmp, m.Smallest) && exciseSpan.Contains(d.cmp, m.Largest) {
// This file fits perfectly within the excise span. We can slot it at
// L6, or sharedLevelsStart - 1 if we have shared files.
@@ -1890,7 +2078,31 @@ func (d *DB) ingestApply(
f.Level = 6
}
} else {
f.Level, err = findTargetLevel(d.newIters, d.tableNewRangeKeyIter, iterOps, d.cmp, current, baseLevel, d.mu.compact.inProgress, m)
// TODO(bilal): findTargetLevel does disk IO (reading files for data
// overlap) even though we're holding onto d.mu. Consider unlocking
// d.mu while we do this. We already hold versions.logLock so we should
// not see any version applications while we're at this. The one
// complication here would be pulling out the mu.compact.inProgress
// check from findTargetLevel, as that requires d.mu to be held.
f.Level, splitFile, err = findTargetLevel(
d.newIters, d.tableNewRangeKeyIter, iterOps, d.opts.Comparer, current, baseLevel, d.mu.compact.inProgress, m, shouldIngestSplit)
}
if splitFile != nil {
if invariants.Enabled {
if lf := current.Levels[f.Level].Find(d.cmp, splitFile); lf == nil {
panic("splitFile returned is not in level it should be")
}
}
// We take advantage of the fact that we won't drop the db mutex
// between now and the call to logAndApply. So, no files should
// get added to a new in-progress compaction at this point. We can
// avoid having to iterate on in-progress compactions to cancel them
// if none of the files being split have a compacting state.
if splitFile.IsCompacting() {
checkCompactions = true
}
filesToSplit = append(filesToSplit, ingestSplitFile{ingestFile: m, splitFile: splitFile, level: f.Level})
}
}
if err != nil {
@@ -1908,6 +2120,26 @@ func (d *DB) ingestApply(
levelMetrics.BytesIngested += m.Size
levelMetrics.TablesIngested++
}
// replacedFiles maps files excised due to exciseSpan (or splitFiles returned
// by ingestTargetLevel), to files that were created to replace it. This map
// is used to resolve references to split files in filesToSplit, as it is
// possible for a file that we want to split to no longer exist or have a
// newer fileMetadata due to a split induced by another ingestion file, or an
// excise.
replacedFiles := make(map[base.FileNum][]newFileEntry)
updateLevelMetricsOnExcise := func(m *fileMetadata, level int, added []newFileEntry) {
levelMetrics := metrics[level]
if levelMetrics == nil {
levelMetrics = &LevelMetrics{}
metrics[level] = levelMetrics
}
levelMetrics.NumFiles--
levelMetrics.Size -= int64(m.Size)
for i := range added {
levelMetrics.NumFiles++
levelMetrics.Size += int64(added[i].Meta.Size)
}
}
if exciseSpan.Valid() {
// Iterate through all levels and find files that intersect with exciseSpan.
//
@@ -1929,7 +2161,7 @@ func (d *DB) ingestApply(
iter := overlaps.Iter()
for m := iter.First(); m != nil; m = iter.Next() {
excised, err := d.excise(exciseSpan, m, ve, level)
newFiles, err := d.excise(exciseSpan, m, ve, level)
if err != nil {
return nil, err
}
@@ -1941,19 +2173,19 @@ func (d *DB) ingestApply(
// We did not excise this file.
continue
}
levelMetrics := metrics[level]
if levelMetrics == nil {
levelMetrics = &LevelMetrics{}
metrics[level] = levelMetrics
}
levelMetrics.NumFiles--
levelMetrics.Size -= int64(m.Size)
for i := range excised {
levelMetrics.NumFiles++
levelMetrics.Size += int64(excised[i].Meta.Size)
}
replacedFiles[m.FileNum] = newFiles
updateLevelMetricsOnExcise(m, level, newFiles)
}
}
}
if len(filesToSplit) > 0 {
// For the same reasons as the above call to excise, we hold the db mutex
// while calling this method.
if err := d.ingestSplit(ve, updateLevelMetricsOnExcise, filesToSplit, replacedFiles); err != nil {
return nil, err
}
}
if len(filesToSplit) > 0 || exciseSpan.Valid() {
for c := range d.mu.compact.inProgress {
if c.versionEditApplied {
continue
@@ -1968,22 +2200,41 @@ func (d *DB) ingestApply(
if exciseSpan.OverlapsInternalKeyRange(d.cmp, c.smallest, c.largest) {
c.cancel.Store(true)
}
// Check if this compaction's inputs have been replaced due to an
// ingest-time split. In that case, cancel the compaction as a newly picked
// compaction would need to include any new files that slid in between
// previously-existing files. Note that we cancel any compaction that has a
// file that was ingest-split as an input, even if it started before this
// ingestion.
if checkCompactions {
for i := range c.inputs {
iter := c.inputs[i].files.Iter()
for f := iter.First(); f != nil; f = iter.Next() {
if _, ok := replacedFiles[f.FileNum]; ok {
c.cancel.Store(true)
break
}
}
}
}
}
// Check for any EventuallyFileOnlySnapshots that could be watching for
// an excise on this span.
for s := d.mu.snapshots.root.next; s != &d.mu.snapshots.root; s = s.next {
if s.efos == nil {
continue
}
efos := s.efos
// TODO(bilal): We can make this faster by taking advantage of the sorted
// nature of protectedRanges to do a sort.Search, or even maintaining a
// global list of all protected ranges instead of having to peer into every
// snapshot.
for i := range efos.protectedRanges {
if efos.protectedRanges[i].OverlapsKeyRange(d.cmp, exciseSpan) {
efos.excised.Store(true)
break
if exciseSpan.Valid() {
for s := d.mu.snapshots.root.next; s != &d.mu.snapshots.root; s = s.next {
if s.efos == nil {
continue
}
efos := s.efos
// TODO(bilal): We can make this faster by taking advantage of the sorted
// nature of protectedRanges to do a sort.Search, or even maintaining a
// global list of all protected ranges instead of having to peer into every
// snapshot.
for i := range efos.protectedRanges {
if efos.protectedRanges[i].OverlapsKeyRange(d.cmp, exciseSpan) {
efos.excised.Store(true)
break
}
}
}
}
@@ -2004,28 +2255,31 @@ func (d *DB) ingestApply(
// The ingestion may have pushed a level over the threshold for compaction,
// so check to see if one is necessary and schedule it.
d.maybeScheduleCompaction()
d.maybeValidateSSTablesLocked(ve.NewFiles)
var toValidate []manifest.NewFileEntry
dedup := make(map[base.DiskFileNum]struct{})
for _, entry := range ve.NewFiles {
if _, ok := dedup[entry.Meta.FileBacking.DiskFileNum]; !ok {
toValidate = append(toValidate, entry)
dedup[entry.Meta.FileBacking.DiskFileNum] = struct{}{}
}
}
d.maybeValidateSSTablesLocked(toValidate)
return ve, nil
}
// maybeValidateSSTablesLocked adds the slice of newFileEntrys to the pending
// queue of files to be validated, when the feature is enabled.
// DB.mu must be locked when calling.
//
// TODO(bananabrick): Make sure that the ingestion step only passes in the
// physical sstables for validation here.
// Note that if two entries with the same backing file are added twice, then the
// block checksums for the backing file will be validated twice.
//
// DB.mu must be locked when calling.
func (d *DB) maybeValidateSSTablesLocked(newFiles []newFileEntry) {
// Only add to the validation queue when the feature is enabled.
if !d.opts.Experimental.ValidateOnIngest {
return
}
for _, f := range newFiles {
if f.Meta.Virtual {
panic("pebble: invalid call to maybeValidateSSTablesLocked")
}
}
d.mu.tableValidation.pending = append(d.mu.tableValidation.pending, newFiles...)
if d.shouldValidateSSTablesLocked() {
go d.validateSSTables()
@@ -2085,10 +2339,19 @@ func (d *DB) validateSSTables() {
}
}
err := d.tableCache.withReader(
f.Meta.PhysicalMeta(), func(r *sstable.Reader) error {
return r.ValidateBlockChecksums()
})
var err error
if f.Meta.Virtual {
err = d.tableCache.withVirtualReader(
f.Meta.VirtualMeta(), func(v sstable.VirtualReader) error {
return v.ValidateBlockChecksumsOnBacking()
})
} else {
err = d.tableCache.withReader(
f.Meta.PhysicalMeta(), func(r *sstable.Reader) error {
return r.ValidateBlockChecksums()
})
}
if err != nil {
// TODO(travers): Hook into the corruption reporting pipeline, once
// available. See pebble#1192.