Skip to content

Commit

Permalink
internal/rangekey: reduce range-key iteration allocations
Browse files Browse the repository at this point in the history
Remove the two remaining excess allocations incured when iterating with range
keys enabled, in the absence of any committed range keys. This brings the
number of allocations to parity with iteration with points only.

The two removed allocations stemmed from use of closures constructed at
iteration construction. These closures have been replaced with interfaces.

```
name                                                             old time/op    new time/op    delta
IteratorScan/keys=100,r-amp=1,key-types=points-only-10             5.76µs ± 0%    5.87µs ± 0%   +1.86%  (p=0.000 n=8+8)
IteratorScan/keys=100,r-amp=1,key-types=points-and-ranges-10       8.81µs ± 2%    8.81µs ± 3%     ~     (p=0.684 n=10+10)
IteratorScan/keys=100,r-amp=3,key-types=points-only-10             10.1µs ± 2%    10.4µs ± 3%     ~     (p=0.063 n=10+10)
IteratorScan/keys=100,r-amp=3,key-types=points-and-ranges-10       13.4µs ± 3%    13.8µs ± 1%   +2.83%  (p=0.022 n=10+9)
IteratorScan/keys=100,r-amp=7,key-types=points-only-10             15.5µs ± 0%    15.7µs ± 2%   +1.41%  (p=0.028 n=9+10)
IteratorScan/keys=100,r-amp=7,key-types=points-and-ranges-10       18.8µs ± 1%    19.0µs ± 2%     ~     (p=0.123 n=10+10)
IteratorScan/keys=100,r-amp=10,key-types=points-only-10            19.0µs ± 1%    19.2µs ± 1%     ~     (p=0.146 n=10+8)
IteratorScan/keys=100,r-amp=10,key-types=points-and-ranges-10      22.7µs ± 2%    22.9µs ± 2%   +0.99%  (p=0.011 n=10+10)
IteratorScan/keys=1000,r-amp=1,key-types=points-only-10            44.5µs ± 0%    44.8µs ± 1%   +0.60%  (p=0.002 n=10+9)
IteratorScan/keys=1000,r-amp=1,key-types=points-and-ranges-10      71.8µs ± 0%    71.5µs ± 3%     ~     (p=0.052 n=10+10)
IteratorScan/keys=1000,r-amp=3,key-types=points-only-10            77.4µs ± 1%    77.4µs ± 0%     ~     (p=0.633 n=10+8)
IteratorScan/keys=1000,r-amp=3,key-types=points-and-ranges-10       104µs ± 0%     104µs ± 1%   -0.43%  (p=0.023 n=10+10)
IteratorScan/keys=1000,r-amp=7,key-types=points-only-10             104µs ± 1%     104µs ± 1%     ~     (p=0.393 n=10+10)
IteratorScan/keys=1000,r-amp=7,key-types=points-and-ranges-10       132µs ± 1%     133µs ± 0%     ~     (p=0.063 n=10+10)
IteratorScan/keys=1000,r-amp=10,key-types=points-only-10            118µs ± 1%     118µs ± 1%     ~     (p=0.853 n=10+10)
IteratorScan/keys=1000,r-amp=10,key-types=points-and-ranges-10      145µs ± 0%     145µs ± 1%     ~     (p=0.579 n=10+10)
IteratorScan/keys=10000,r-amp=1,key-types=points-only-10            417µs ± 0%     418µs ± 0%     ~     (p=0.113 n=10+9)
IteratorScan/keys=10000,r-amp=1,key-types=points-and-ranges-10      671µs ± 0%     667µs ± 2%   -0.54%  (p=0.043 n=10+10)
IteratorScan/keys=10000,r-amp=3,key-types=points-only-10            708µs ± 2%     719µs ± 1%   +1.54%  (p=0.004 n=10+9)
IteratorScan/keys=10000,r-amp=3,key-types=points-and-ranges-10      971µs ± 1%     973µs ± 2%     ~     (p=0.739 n=10+10)
IteratorScan/keys=10000,r-amp=7,key-types=points-only-10            948µs ± 1%     965µs ± 3%   +1.82%  (p=0.004 n=10+10)
IteratorScan/keys=10000,r-amp=7,key-types=points-and-ranges-10     1.20ms ± 1%    1.22ms ± 1%   +1.22%  (p=0.004 n=10+10)
IteratorScan/keys=10000,r-amp=10,key-types=points-only-10          1.05ms ± 1%    1.07ms ± 1%   +1.86%  (p=0.000 n=10+10)
IteratorScan/keys=10000,r-amp=10,key-types=points-and-ranges-10    1.31ms ± 1%    1.32ms ± 1%   +1.17%  (p=0.000 n=10+9)

name                                                             old alloc/op   new alloc/op   delta
IteratorScan/keys=100,r-amp=1,key-types=points-only-10              16.0B ± 0%     16.0B ± 0%     ~     (all equal)
IteratorScan/keys=100,r-amp=1,key-types=points-and-ranges-10        48.0B ± 0%     16.0B ± 0%  -66.67%  (p=0.000 n=10+10)
IteratorScan/keys=100,r-amp=3,key-types=points-only-10              48.0B ± 0%     48.0B ± 0%     ~     (all equal)
IteratorScan/keys=100,r-amp=3,key-types=points-and-ranges-10        80.0B ± 0%     48.0B ± 0%  -40.00%  (p=0.000 n=10+10)
IteratorScan/keys=100,r-amp=7,key-types=points-only-10               112B ± 0%      112B ± 0%     ~     (all equal)
IteratorScan/keys=100,r-amp=7,key-types=points-and-ranges-10         144B ± 0%      112B ± 0%  -22.22%  (p=0.000 n=10+10)
IteratorScan/keys=100,r-amp=10,key-types=points-only-10              160B ± 0%      160B ± 0%     ~     (all equal)
IteratorScan/keys=100,r-amp=10,key-types=points-and-ranges-10        192B ± 0%      160B ± 0%  -16.67%  (p=0.000 n=10+10)
IteratorScan/keys=1000,r-amp=1,key-types=points-only-10             16.0B ± 0%     16.0B ± 0%     ~     (all equal)
IteratorScan/keys=1000,r-amp=1,key-types=points-and-ranges-10       48.0B ± 0%     16.0B ± 0%  -66.67%  (p=0.000 n=9+9)
IteratorScan/keys=1000,r-amp=3,key-types=points-only-10             48.0B ± 0%     48.0B ± 0%     ~     (all equal)
IteratorScan/keys=1000,r-amp=3,key-types=points-and-ranges-10       81.0B ± 0%     48.6B ± 1%  -40.00%  (p=0.000 n=8+10)
IteratorScan/keys=1000,r-amp=7,key-types=points-only-10              113B ± 0%      113B ± 0%     ~     (all equal)
IteratorScan/keys=1000,r-amp=7,key-types=points-and-ranges-10        145B ± 0%      113B ± 0%  -21.91%  (p=0.000 n=10+10)
IteratorScan/keys=1000,r-amp=10,key-types=points-only-10             161B ± 0%      161B ± 0%     ~     (all equal)
IteratorScan/keys=1000,r-amp=10,key-types=points-and-ranges-10       194B ± 0%      162B ± 0%  -16.49%  (p=0.000 n=9+8)
IteratorScan/keys=10000,r-amp=1,key-types=points-only-10            18.8B ±15%     18.8B ±15%     ~     (p=1.000 n=10+10)
IteratorScan/keys=10000,r-amp=1,key-types=points-and-ranges-10      54.3B ± 8%     24.0B ± 0%  -55.80%  (p=0.000 n=10+6)
IteratorScan/keys=10000,r-amp=3,key-types=points-only-10            53.0B ± 8%     53.8B ± 9%     ~     (p=0.577 n=10+10)
IteratorScan/keys=10000,r-amp=3,key-types=points-and-ranges-10      88.8B ± 7%     60.4B ± 1%  -31.95%  (p=0.000 n=10+7)
IteratorScan/keys=10000,r-amp=7,key-types=points-only-10             122B ± 0%      122B ± 0%     ~     (p=0.082 n=9+9)
IteratorScan/keys=10000,r-amp=7,key-types=points-and-ranges-10       160B ± 0%      123B ± 6%  -23.00%  (p=0.000 n=9+10)
IteratorScan/keys=10000,r-amp=10,key-types=points-only-10            169B ± 4%      172B ± 0%     ~     (p=0.294 n=10+8)
IteratorScan/keys=10000,r-amp=10,key-types=points-and-ranges-10      206B ± 5%      178B ± 0%  -13.51%  (p=0.000 n=10+8)

name                                                             old allocs/op  new allocs/op  delta
IteratorScan/keys=100,r-amp=1,key-types=points-only-10               1.00 ± 0%      1.00 ± 0%     ~     (all equal)
IteratorScan/keys=100,r-amp=1,key-types=points-and-ranges-10         3.00 ± 0%      1.00 ± 0%  -66.67%  (p=0.000 n=10+10)
IteratorScan/keys=100,r-amp=3,key-types=points-only-10               3.00 ± 0%      3.00 ± 0%     ~     (all equal)
IteratorScan/keys=100,r-amp=3,key-types=points-and-ranges-10         5.00 ± 0%      3.00 ± 0%  -40.00%  (p=0.000 n=10+10)
IteratorScan/keys=100,r-amp=7,key-types=points-only-10               7.00 ± 0%      7.00 ± 0%     ~     (all equal)
IteratorScan/keys=100,r-amp=7,key-types=points-and-ranges-10         9.00 ± 0%      7.00 ± 0%  -22.22%  (p=0.000 n=10+10)
IteratorScan/keys=100,r-amp=10,key-types=points-only-10              10.0 ± 0%      10.0 ± 0%     ~     (all equal)
IteratorScan/keys=100,r-amp=10,key-types=points-and-ranges-10        12.0 ± 0%      10.0 ± 0%  -16.67%  (p=0.000 n=10+10)
IteratorScan/keys=1000,r-amp=1,key-types=points-only-10              1.00 ± 0%      1.00 ± 0%     ~     (all equal)
IteratorScan/keys=1000,r-amp=1,key-types=points-and-ranges-10        3.00 ± 0%      1.00 ± 0%  -66.67%  (p=0.000 n=10+10)
IteratorScan/keys=1000,r-amp=3,key-types=points-only-10              3.00 ± 0%      3.00 ± 0%     ~     (all equal)
IteratorScan/keys=1000,r-amp=3,key-types=points-and-ranges-10        5.00 ± 0%      3.00 ± 0%  -40.00%  (p=0.000 n=10+10)
IteratorScan/keys=1000,r-amp=7,key-types=points-only-10              7.00 ± 0%      7.00 ± 0%     ~     (all equal)
IteratorScan/keys=1000,r-amp=7,key-types=points-and-ranges-10        9.00 ± 0%      7.00 ± 0%  -22.22%  (p=0.000 n=10+10)
IteratorScan/keys=1000,r-amp=10,key-types=points-only-10             10.0 ± 0%      10.0 ± 0%     ~     (all equal)
IteratorScan/keys=1000,r-amp=10,key-types=points-and-ranges-10       12.0 ± 0%      10.0 ± 0%  -16.67%  (p=0.000 n=10+10)
IteratorScan/keys=10000,r-amp=1,key-types=points-only-10             1.00 ± 0%      1.00 ± 0%     ~     (all equal)
IteratorScan/keys=10000,r-amp=1,key-types=points-and-ranges-10       3.00 ± 0%      1.00 ± 0%  -66.67%  (p=0.000 n=10+10)
IteratorScan/keys=10000,r-amp=3,key-types=points-only-10             3.00 ± 0%      3.00 ± 0%     ~     (all equal)
IteratorScan/keys=10000,r-amp=3,key-types=points-and-ranges-10       5.00 ± 0%      3.00 ± 0%  -40.00%  (p=0.000 n=10+10)
IteratorScan/keys=10000,r-amp=7,key-types=points-only-10             7.00 ± 0%      7.00 ± 0%     ~     (all equal)
IteratorScan/keys=10000,r-amp=7,key-types=points-and-ranges-10       9.00 ± 0%      7.00 ± 0%  -22.22%  (p=0.000 n=10+10)
IteratorScan/keys=10000,r-amp=10,key-types=points-only-10            10.0 ± 0%      10.0 ± 0%     ~     (all equal)
IteratorScan/keys=10000,r-amp=10,key-types=points-and-ranges-10      12.0 ± 0%      10.0 ± 0%  -16.67%  (p=0.000 n=10+10)
```
  • Loading branch information
jbowens committed May 19, 2022
1 parent 4e33626 commit 3355a02
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 51 deletions.
30 changes: 22 additions & 8 deletions internal/keyspan/defragment.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,29 @@ const bufferReuseMaxCapacity = 10 << 10 // 10 KB

// DefragmentMethod configures the defragmentation performed by the
// DefragmentingIter.
type DefragmentMethod func(base.Compare, Span, Span) bool
type DefragmentMethod interface {
// ShouldDefragment takes two abutting spans and returns whether the two
// spans should be combined into a single, defragmented Span.
ShouldDefragment(cmp base.Compare, left, right Span) bool
}

// The DefragmentMethodFunc type is an adapter to allow the use of ordinary
// functions as DefragmentMethods. If f is a function with the appropriate
// signature, DefragmentMethodFunc(f) is a DefragmentMethod that calls f.
type DefragmentMethodFunc func(cmp base.Compare, left, right Span) bool

// ShouldDefragment calls f(cmp, left, right).
func (f DefragmentMethodFunc) ShouldDefragment(cmp base.Compare, left, right Span) bool {
return f(cmp, left, right)
}

// DefragmentInternal configures a DefragmentingIter to defragment spans
// only if they have identical keys.
//
// This defragmenting method is intended for use in compactions that may see
// internal range keys fragments that may now be joined, because the state that
// required their fragmentation has been dropped.
var DefragmentInternal DefragmentMethod = func(cmp base.Compare, a, b Span) bool {
var DefragmentInternal DefragmentMethod = DefragmentMethodFunc(func(cmp base.Compare, a, b Span) bool {
if len(a.Keys) != len(b.Keys) {
return false
}
Expand All @@ -42,7 +56,7 @@ var DefragmentInternal DefragmentMethod = func(cmp base.Compare, a, b Span) bool
}
}
return true
}
})

// DefragmentReducer merges the current and next Key slices, returning a new Key
// slice.
Expand Down Expand Up @@ -119,10 +133,10 @@ type DefragmentingIter struct {
keysBuf []Key
keyBuf []byte

// equal is a comparison function for two spans. equal is called when two
// method is a comparison function for two spans. method is called when two
// spans are abutting to determine whether they may be defragmented.
// equal does not itself check for adjacency for the two spans.
equal DefragmentMethod
// method does not itself check for adjacency for the two spans.
method DefragmentMethod

// reduce is the reducer function used to collect Keys across all spans that
// constitute a defragmented span.
Expand All @@ -140,7 +154,7 @@ func (i *DefragmentingIter) Init(
*i = DefragmentingIter{
cmp: cmp,
iter: iter,
equal: equal,
method: equal,
reduce: reducer,
}
}
Expand Down Expand Up @@ -307,7 +321,7 @@ func (i *DefragmentingIter) Prev() Span {
// 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())
return (!left.Empty() && !right.Empty()) && i.method.ShouldDefragment(i.cmp, i.iterSpan, i.curr)
}

// defragmentForward defragments spans in the forward direction, starting from
Expand Down
2 changes: 1 addition & 1 deletion internal/keyspan/defragment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
func TestDefragmentingIter(t *testing.T) {
cmp := testkeys.Comparer.Compare
internalEqual := DefragmentInternal
alwaysEqual := func(_ base.Compare, _, _ Span) bool { return true }
alwaysEqual := DefragmentMethodFunc(func(_ base.Compare, _, _ Span) bool { return true })
staticReducer := StaticDefragmentReducer
collectReducer := func(cur, next []Key) []Key {
c := keysBySeqNumKind(append(cur, next...))
Expand Down
48 changes: 30 additions & 18 deletions internal/keyspan/merging_iter.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,39 @@ import (
// seeks would require introducing key comparisons to switchTo{Min,Max}Heap
// where there currently are none.

// Transform defines a transform function to be applied to a Span. A Transform
// takes a Span as input and writes the transformed Span to the provided output
// *Span pointer. The output Span's Keys slice may be reused by Transform to
// reduce allocations.
type Transform func(cmp base.Compare, in Span, out *Span) error
// Transformer defines a transformation to be applied to a Span.
type Transformer interface {
// Transform takes a Span as input and writes the transformed Span to the
// provided output *Span pointer. The output Span's Keys slice may be reused
// by Transform to reduce allocations.
Transform(cmp base.Compare, in Span, out *Span) error
}

// The TransformerFunc type is an adapter to allow the use of ordinary functions
// as Transformers. If f is a function with the appropriate signature,
// TransformerFunc(f) is a Transformer that calls f.
type TransformerFunc func(base.Compare, Span, *Span) error

// Transform calls f(cmp, in, out).
func (tf TransformerFunc) Transform(cmp base.Compare, in Span, out *Span) error {
return tf(cmp, in, out)
}

func noopTransform(_ base.Compare, s Span, dst *Span) error {
var noopTransform Transformer = TransformerFunc(func(_ base.Compare, s Span, dst *Span) error {
dst.Start, dst.End = s.Start, s.End
dst.Keys = append(dst.Keys[:0], s.Keys...)
return nil
}
})

// visibleTransform filters keys that are invisible at the provided snapshot
// sequence number.
func visibleTransform(snapshot uint64) Transform {
return func(_ base.Compare, s Span, dst *Span) error {
func visibleTransform(snapshot uint64) Transformer {
return TransformerFunc(func(_ base.Compare, s Span, dst *Span) error {
s = s.Visible(snapshot)
dst.Start, dst.End = s.Start, s.End
dst.Keys = append(dst.Keys[:0], s.Keys...)
return nil
}
})
}

// MergingIter merges spans across levels of the LSM, exposing an iterator over
Expand Down Expand Up @@ -200,10 +212,10 @@ type MergingIter struct {
// Each element points into a child iterator's memory, so the keys may not
// be directly modified.
keys keysBySeqNumKind
// transform defines a function to be applied to a span before it's yielded
// to the user. A transform may filter individual keys contained within the
// span.
transform Transform
// transformer defines a transformation to be applied to a span before it's
// yielded to the user. Transforming may filter individual keys contained
// within the span.
transformer Transformer
// span holds the iterator's current span. This span is used as the
// destination for transforms. Every tranformed span overwrites the
// previous.
Expand Down Expand Up @@ -240,12 +252,12 @@ func (l *mergingIterLevel) prev() {
}

// Init initializes the merging iterator with the provided fragment iterators.
func (m *MergingIter) Init(cmp base.Compare, transform Transform, iters ...FragmentIterator) {
func (m *MergingIter) Init(cmp base.Compare, transformer Transformer, iters ...FragmentIterator) {
levels, items := m.levels, m.heap.items

*m = MergingIter{
heap: mergingIterHeap{cmp: cmp},
transform: transform,
heap: mergingIterHeap{cmp: cmp},
transformer: transformer,
}
// Invariant: cap(levels) == cap(items)
if cap(levels) < len(iters) {
Expand Down Expand Up @@ -727,7 +739,7 @@ func (m *MergingIter) synthesizeKeys(dir int8) (bool, Span) {
End: m.end,
Keys: m.keys,
}
if err := m.transform(m.cmp, s, &m.span); err != nil {
if err := m.transformer.Transform(m.cmp, s, &m.span); err != nil {
m.err = err
return false, Span{}
}
Expand Down
41 changes: 20 additions & 21 deletions internal/rangekey/coalesce.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,19 @@ func (ui *UserIteratorConfig) Init(
ui.snapshot = snapshot
ui.defragBufA.keys = ui.defragBufAlloc[0][:0]
ui.defragBufB.keys = ui.defragBufAlloc[1][:0]
ui.miter.Init(cmp, ui.transform, levelIters...)
ui.diter.Init(cmp, &ui.miter, ui.defragmentMethod, keyspan.StaticDefragmentReducer)
ui.miter.Init(cmp, ui, levelIters...)
ui.diter.Init(cmp, &ui.miter, ui, keyspan.StaticDefragmentReducer)
return &ui.diter
}

// transform implements the keyspan.Transform function signature for use with a
// Transform implements the keyspan.Transformer interface for use with a
// keyspan.MergingIter. It transforms spans by resolving range keys at the
// provided snapshot sequence number. Shadowing of keys is resolved (eg, removal
// of unset keys, removal of keys overwritten by a set at the same suffix, etc)
// and then non-RangeKeySet keys are removed. The resulting transformed spans
// only contain RangeKeySets describing the state visible at the provided
// sequence number.
func (ui *UserIteratorConfig) transform(cmp base.Compare, s keyspan.Span, dst *keyspan.Span) error {
func (ui *UserIteratorConfig) Transform(cmp base.Compare, s keyspan.Span, dst *keyspan.Span) error {
// Apply shadowing of keys.
if err := Coalesce(cmp, s.Visible(ui.snapshot), dst); err != nil {
return err
Expand Down Expand Up @@ -80,24 +80,23 @@ func (ui *UserIteratorConfig) transform(cmp base.Compare, s keyspan.Span, dst *k
return nil
}

// defragmentMethod implements the DefragmentMethod function signature and
// configures a DefragmentingIter to defragment spans of range keys if their
// user-visible state is identical. This defragmenting method assumes the
// provided spans have already been transformed through
// (UserIterationConfig).transform, so all RangeKeySets are user-visible sets.
// This defragmenter checks for equality between set suffixes and values
// (ignoring sequence numbers). It's intended for use during user iteration,
// when the wrapped keyspan iterator is merging spans across all levels of the
// LSM.
// ShouldDefragment implements the DefragmentMethod interface and configures a
// DefragmentingIter to defragment spans of range keys if their user-visible
// state is identical. This defragmenting method assumes the provided spans have
// already been transformed through (UserIterationConfig).Transform, so all
// RangeKeySets are user-visible sets. This defragmenter checks for equality
// between set suffixes and values (ignoring sequence numbers). It's intended
// for use during user iteration, when the wrapped keyspan iterator is merging
// spans across all levels of the LSM.
//
// The returned defragmenting method is stateful, and must not be used on
// multiple DefragmentingIters concurrently.
func (ui *UserIteratorConfig) defragmentMethod(cmp base.Compare, a, b keyspan.Span) bool {
// UserIterationDefragmenter must only be used on spans that have
// transformed by ui.transform. The transform applies shadowing and removes
// all keys besides the resulting Sets. Since shadowing has been applied,
// each Set must set a unique suffix. If the two spans are equivalent, they
// must have the same number of range key sets.
// This implementation is stateful, and must not be used on multiple
// DefragmentingIters concurrently.
func (ui *UserIteratorConfig) ShouldDefragment(cmp base.Compare, a, b keyspan.Span) bool {
// This implementation must only be used on spans that have transformed by
// ui.Transform. The transform applies shadowing and removes all keys
// besides the resulting Sets. Since shadowing has been applied, each Set
// must set a unique suffix. If the two spans are equivalent, they must have
// the same number of range key sets.
if len(a.Keys) != len(b.Keys) || len(a.Keys) == 0 {
return false
}
Expand Down
4 changes: 2 additions & 2 deletions internal/rangekey/coalesce_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,10 @@ func TestIter(t *testing.T) {
for _, line := range lines {
spans = append(spans, keyspan.ParseSpan(line))
}
transform := func(cmp base.Compare, s keyspan.Span, dst *keyspan.Span) error {
transform := keyspan.TransformerFunc(func(cmp base.Compare, s keyspan.Span, dst *keyspan.Span) error {
s = s.Visible(visibleSeqNum)
return Coalesce(cmp, s, dst)
}
})
iter.Init(cmp, transform, keyspan.NewIter(cmp, spans))
return "OK"
case "iter":
Expand Down
4 changes: 3 additions & 1 deletion table_stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,9 @@ func foreachDefragmentedTombstone(
fn func([]byte, []byte, uint64, uint64) error,
) error {
// Use an equals func that will always merge abutting spans.
equal := func(_ base.Compare, _, _ keyspan.Span) bool { return true }
equal := keyspan.DefragmentMethodFunc(func(_ base.Compare, _, _ keyspan.Span) bool {
return true
})
// Reduce keys by maintaining a slice of length two, corresponding to the
// largest and smallest keys in the defragmented span. This maintains the
// contract that the emitted slice is sorted by (SeqNum, Kind) descending.
Expand Down

0 comments on commit 3355a02

Please sign in to comment.