Skip to content

Commit

Permalink
internal/keyspan: Emit empty spans where there are no range keys
Browse files Browse the repository at this point in the history
This change updates keyspan.LevelIter to emit empty Spans (i.e.
spans with valid start/end but no values/Keys) between files.
This is used as an optimization to avoid loading the next file
after a file has been exhausted.

To preserve this optimization up the iterator stack, the
merging iter and defragmenting iter were also updated to
special-case empty spans returned by child iterators and
preserve them as-is instead of defragmenting them
or iterating beyond them.

The top-level Iterator was also updated to elide
rangekey boundary keys that correspond to empty spans that do
not cover any point keys.

Fixes cockroachdb#1605
  • Loading branch information
itsbilal committed Apr 19, 2022
1 parent 785bb36 commit ab7c881
Show file tree
Hide file tree
Showing 11 changed files with 667 additions and 92 deletions.
43 changes: 34 additions & 9 deletions internal/keyspan/defragment.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ func (i *DefragmentingIter) SeekGE(key []byte) Span {
// Save the current span and peek backwards.
i.saveCurrent(i.iterSpan)
i.iterSpan = i.iter.Prev()
if i.cmp(i.curr.Start, i.iterSpan.End) == 0 && i.equal(i.cmp, i.iterSpan, i.curr) {
if i.cmp(i.curr.Start, i.iterSpan.End) == 0 && i.checkEqual(i.iterSpan, i.curr) {
// A continuation. The span we originally landed on and defragmented
// backwards has a true Start key < key. To obey the FragmentIterator
// contract, we must not return this defragmented span. Defragment
Expand All @@ -202,8 +202,10 @@ func (i *DefragmentingIter) SeekLT(key []byte) Span {
i.iterSpan = i.iter.SeekLT(key)
// Defragment forward to find the end of the defragmented span.
i.defragmentForward()
// Prev once back onto the span.
i.iterSpan = i.iter.Prev()
if i.iterPos == iterPosNext {
// Prev once back onto the span.
i.iterSpan = i.iter.Prev()
}
// Defragment the full span from its end.
return i.defragmentBackward()
}
Expand Down Expand Up @@ -250,8 +252,9 @@ func (i *DefragmentingIter) Next() Span {
// fragments, defragment forward.
return i.defragmentForward()
case iterPosCurr:
// iterPosCurr is only used when the iter is exhausted.
if invariants.Enabled && i.iterSpan.Valid() {
// iterPosCurr is only used when the iter is exhausted or when the iterator
// is at an empty span.
if invariants.Enabled && i.iterSpan.Valid() && !i.iterSpan.Empty() {
panic("pebble: invariant violation: iterPosCurr with valid iterSpan")
}

Expand All @@ -272,8 +275,9 @@ func (i *DefragmentingIter) Prev() Span {
// Already at the previous span.
return i.defragmentBackward()
case iterPosCurr:
// iterPosCurr is only used when the iter is exhausted.
if invariants.Enabled && i.iterSpan.Valid() {
// iterPosCurr is only used when the iter is exhausted or when the iterator
// is at an empty span.
if invariants.Enabled && i.iterSpan.Valid() && !i.iterSpan.Empty() {
panic("pebble: invariant violation: iterPosCurr with valid iterSpan")
}

Expand Down Expand Up @@ -315,13 +319,27 @@ func (i *DefragmentingIter) SetBounds(lower, upper []byte) {
i.iter.SetBounds(lower, upper)
}

// checkEqual checks the two spans for logical equivalence. It uses the passed-in
// DefragmentMethod and ensures both spans are NOT empty; not defragmenting empty
// spans is an optimization that lets us load fewer sstable blocks.
func (i *DefragmentingIter) checkEqual(left, right Span) bool {
return i.equal(i.cmp, i.iterSpan, i.curr) && !(left.Empty() && right.Empty())
}

// defragmentForward defragments spans in the forward direction, starting from
// i.iter's current position.
func (i *DefragmentingIter) defragmentForward() Span {
if !i.iterSpan.Valid() {
i.iterPos = iterPosCurr
return i.iterSpan
}
if i.iterSpan.Empty() {
// An empty span will never be equal to another span; see checkEqual for
// why. To avoid loading non-empty range keys further ahead by calling Next,
// return early.
i.iterPos = iterPosCurr
return i.iterSpan
}
i.saveCurrent(i.iterSpan)

i.iterPos = iterPosNext
Expand All @@ -331,7 +349,7 @@ func (i *DefragmentingIter) defragmentForward() Span {
// Not a continuation.
break
}
if !i.equal(i.cmp, i.iterSpan, i.curr) {
if !i.checkEqual(i.iterSpan, i.curr) {
// Not a continuation.
break
}
Expand All @@ -351,6 +369,13 @@ func (i *DefragmentingIter) defragmentBackward() Span {
i.iterPos = iterPosCurr
return i.iterSpan
}
if i.iterSpan.Empty() {
// An empty span will never be equal to another span; see checkEqual for
// why. To avoid loading non-empty range keys further ahead by calling Next,
// return early.
i.iterPos = iterPosCurr
return i.iterSpan
}
i.saveCurrent(i.iterSpan)

i.iterPos = iterPosPrev
Expand All @@ -360,7 +385,7 @@ func (i *DefragmentingIter) defragmentBackward() Span {
// Not a continuation.
break
}
if !i.equal(i.cmp, i.iterSpan, i.curr) {
if !i.checkEqual(i.iterSpan, i.curr) {
// Not a continuation.
break
}
Expand Down
13 changes: 10 additions & 3 deletions internal/keyspan/interleaving_iter.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,14 @@ func (i *InterleavingIter) interleaveForward(lowerBound []byte) (*base.InternalK
// interleaved it, we should.
if !i.keyspanInterleaved {
if i.span.Empty() {
i.keyspanInterleaved = true
if i.pointKey != nil && i.cmp(i.pointKey.UserKey, i.span.End) >= 0 {
// Advance the keyspan iterator, as just flipping
// keyspanInterleaved would likely trip up the invariant check
// above.
i.checkForwardBound(i.keyspanIter.Next())
} else {
i.keyspanInterleaved = true
}
continue
}
return i.yieldSyntheticSpanMarker(lowerBound)
Expand Down Expand Up @@ -501,7 +508,7 @@ func (i *InterleavingIter) interleaveBackward() (*base.InternalKey, []byte) {
case i.pointKey == nil:
// If we're out of point keys, we need to return a span marker.
if i.span.Empty() {
i.keyspanInterleaved = true
i.checkBackwardBound(i.keyspanIter.Prev())
continue
}
return i.yieldSyntheticSpanMarker(i.lower)
Expand All @@ -510,7 +517,7 @@ func (i *InterleavingIter) interleaveBackward() (*base.InternalKey, []byte) {
// marker for the span.
if i.cmp(i.span.Start, i.pointKey.UserKey) > 0 {
if i.span.Empty() {
i.keyspanInterleaved = true
i.checkBackwardBound(i.keyspanIter.Prev())
continue
}
return i.yieldSyntheticSpanMarker(i.lower)
Expand Down
10 changes: 8 additions & 2 deletions internal/keyspan/internal_iter_shim.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ func (i *InternalIteratorShim) SeekLT(key []byte) (*base.InternalKey, []byte) {
// First implements (base.InternalIterator).First.
func (i *InternalIteratorShim) First() (*base.InternalKey, []byte) {
i.span = i.miter.First()
if i.span.Empty() {
for i.span.Valid() && i.span.Empty() {
i.span = i.miter.Next()
}
if !i.span.Valid() {
return nil, nil
}
i.iterKey = base.InternalKey{UserKey: i.span.Start, Trailer: i.span.Keys[0].Trailer}
Expand All @@ -72,7 +75,10 @@ func (i *InternalIteratorShim) Last() (*base.InternalKey, []byte) {
// Next implements (base.InternalIterator).Next.
func (i *InternalIteratorShim) Next() (*base.InternalKey, []byte) {
i.span = i.miter.Next()
if i.span.Empty() {
for i.span.Valid() && i.span.Empty() {
i.span = i.miter.Next()
}
if !i.span.Valid() {
return nil, nil
}
i.iterKey = base.InternalKey{UserKey: i.span.Start, Trailer: i.span.Keys[0].Trailer}
Expand Down
Loading

0 comments on commit ab7c881

Please sign in to comment.