Skip to content

Commit

Permalink
*: optimize external iters for single-file, no-rangekey cases
Browse files Browse the repository at this point in the history
This change updates NewExternalIter to skip creation of a point-key
mergingIter if there is only one merging iter level and there are
no range dels. It also skips creation of a simpleLevelIter if there
is only one file that would be contained in it. And it skips
creation of a rangeKey interleaving iterator if there are no range
keys. These optimizations avoid overhead that's only necessary for
multi-file, range-key cases.

Together, these optimizations produce a ~45% performance gain
for forward iteration:

```
name                                                   old time/op  new time/op  delta
SSTIterator/keys=10000/variant=pebble/verify=false-10  1.22ms ± 0%  0.67ms ± 0%  -45.27%  (p=0.001 n=5+10)
```
  • Loading branch information
itsbilal committed Aug 25, 2022
1 parent e729e74 commit c751cef
Showing 1 changed file with 89 additions and 62 deletions.
151 changes: 89 additions & 62 deletions external_iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,79 +168,91 @@ func validateExternalIterOpts(iterOpts *IterOptions) error {
return nil
}

func finishInitializingExternal(it *Iterator) {
func createExternalPointIter(it *Iterator) (internalIterator, error) {
// TODO(jackson): In some instances we could generate fewer levels by using
// L0Sublevels code to organize nonoverlapping files into the same level.
// This would allow us to use levelIters and keep a smaller set of data and
// files in-memory. However, it would also require us to identify the bounds
// of all the files upfront.

mlevels := it.alloc.mlevels[:0]
if !it.opts.pointKeys() {
it.pointIter = emptyIter
} else if it.pointIter == nil {
if len(it.externalReaders) > cap(mlevels) {
mlevels = make([]mergingIterLevel, 0, len(it.externalReaders))
}
for _, readers := range it.externalReaders {
var combinedIters []internalIterator
for _, r := range readers {
var (
rangeDelIter keyspan.FragmentIterator
pointIter internalIterator
err error
)
pointIter, err = r.NewIter(it.opts.LowerBound, it.opts.UpperBound)
if err == nil {
rangeDelIter, err = r.NewRawRangeDelIter()
}
if err != nil {
pointIter = &errorIter{err: err}
rangeDelIter = &errorKeyspanIter{err: err}
}
if err == nil && rangeDelIter == nil && pointIter != nil && it.forwardOnly {
// TODO(bilal): Consider implementing range key pausing in
// simpleLevelIter so we can reduce mergingIterLevels even more by
// sending all sstable iterators to combinedIters, not just those
// corresponding to sstables without range deletes.
combinedIters = append(combinedIters, pointIter)
continue
}
mlevels = append(mlevels, mergingIterLevel{
iter: pointIter,
rangeDelIter: rangeDelIter,
})
return emptyIter, nil
} else if it.pointIter != nil {
return it.pointIter, nil
}
mlevels := it.alloc.mlevels[:0]

if len(it.externalReaders) > cap(mlevels) {
mlevels = make([]mergingIterLevel, 0, len(it.externalReaders))
}
for _, readers := range it.externalReaders {
var combinedIters []internalIterator
for _, r := range readers {
var (
rangeDelIter keyspan.FragmentIterator
pointIter internalIterator
err error
)
pointIter, err = r.NewIter(it.opts.LowerBound, it.opts.UpperBound)
if err != nil {
return nil, err
}
if len(combinedIters) > 0 {
sli := &simpleLevelIter{
cmp: it.cmp,
iters: combinedIters,
}
sli.init(it.opts)
mlevels = append(mlevels, mergingIterLevel{
iter: sli,
rangeDelIter: nil,
})
rangeDelIter, err = r.NewRawRangeDelIter()
if err != nil {
return nil, err
}
if rangeDelIter == nil && pointIter != nil && it.forwardOnly {
// TODO(bilal): Consider implementing range key pausing in
// simpleLevelIter so we can reduce mergingIterLevels even more by
// sending all sstable iterators to combinedIters, not just those
// corresponding to sstables without range deletes.
combinedIters = append(combinedIters, pointIter)
continue
}
mlevels = append(mlevels, mergingIterLevel{
iter: pointIter,
rangeDelIter: rangeDelIter,
})
}
it.alloc.merging.init(&it.opts, &it.stats.InternalStats, it.comparer.Compare, it.comparer.Split, mlevels...)
it.alloc.merging.snapshot = base.InternalKeySeqNumMax
it.alloc.merging.elideRangeTombstones = true
it.pointIter = &it.alloc.merging
if len(combinedIters) == 1 {
mlevels = append(mlevels, mergingIterLevel{
iter: combinedIters[0],
})
} else if len(combinedIters) > 1 {
sli := &simpleLevelIter{
cmp: it.cmp,
iters: combinedIters,
}
sli.init(it.opts)
mlevels = append(mlevels, mergingIterLevel{
iter: sli,
rangeDelIter: nil,
})
}
}
if len(mlevels) == 1 && mlevels[0].rangeDelIter == nil {
return mlevels[0].iter, nil
}

it.alloc.merging.init(&it.opts, &it.stats.InternalStats, it.comparer.Compare, it.comparer.Split, mlevels...)
it.alloc.merging.snapshot = base.InternalKeySeqNumMax
it.alloc.merging.elideRangeTombstones = true
return &it.alloc.merging, nil
}

func finishInitializingExternal(it *Iterator) {
pointIter, err := createExternalPointIter(it)
if err != nil {
it.pointIter = &errorIter{err: err}
} else {
it.pointIter = pointIter
}
it.iter = it.pointIter

if it.opts.rangeKeys() {
it.rangeKeyMasking.init(it, it.comparer.Compare, it.comparer.Split)
var rangeKeyIters []keyspan.FragmentIterator
if it.rangeKey == nil {
it.rangeKey = iterRangeKeyStateAllocPool.Get().(*iteratorRangeKeyState)
it.rangeKey.init(it.comparer.Compare, it.comparer.Split, &it.opts)
it.rangeKey.rangeKeyIter = it.rangeKey.iterConfig.Init(
&it.comparer,
base.InternalKeySeqNumMax,
it.opts.LowerBound, it.opts.UpperBound,
&it.hasPrefix, &it.prefixOrFullSeekKey,
)
// We could take advantage of the lack of overlaps in range keys within
// each slice in it.externalReaders, and generate keyspan.LevelIters
// out of those. However, since range keys are expected to be sparse to
Expand All @@ -253,16 +265,31 @@ func finishInitializingExternal(it *Iterator) {
for _, readers := range it.externalReaders {
for _, r := range readers {
if rki, err := r.NewRawRangeKeyIter(); err != nil {
it.rangeKey.iterConfig.AddLevel(&errorKeyspanIter{err: err})
rangeKeyIters = append(rangeKeyIters, &errorKeyspanIter{err: err})
} else if rki != nil {
it.rangeKey.iterConfig.AddLevel(rki)
rangeKeyIters = append(rangeKeyIters, rki)
}
}
}
if len(rangeKeyIters) > 0 {
it.rangeKey = iterRangeKeyStateAllocPool.Get().(*iteratorRangeKeyState)
it.rangeKey.init(it.comparer.Compare, it.comparer.Split, &it.opts)
it.rangeKey.rangeKeyIter = it.rangeKey.iterConfig.Init(
&it.comparer,
base.InternalKeySeqNumMax,
it.opts.LowerBound, it.opts.UpperBound,
&it.hasPrefix, &it.prefixOrFullSeekKey,
)
for i := range rangeKeyIters {
it.rangeKey.iterConfig.AddLevel(rangeKeyIters[i])
}
}
}
if it.rangeKey != nil {
it.rangeKey.iiter.Init(&it.comparer, it.iter, it.rangeKey.rangeKeyIter, &it.rangeKeyMasking,
it.opts.LowerBound, it.opts.UpperBound)
it.iter = &it.rangeKey.iiter
}
it.rangeKey.iiter.Init(&it.comparer, it.iter, it.rangeKey.rangeKeyIter, &it.rangeKeyMasking,
it.opts.LowerBound, it.opts.UpperBound)
it.iter = &it.rangeKey.iiter
}
}

Expand Down

0 comments on commit c751cef

Please sign in to comment.