Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

db: add range key table stats #1616

Merged
merged 2 commits into from
Apr 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,7 @@ func runTableStatsCmd(td *datadriven.TestData, d *DB) string {
var b bytes.Buffer
fmt.Fprintf(&b, "num-entries: %d\n", f.Stats.NumEntries)
fmt.Fprintf(&b, "num-deletions: %d\n", f.Stats.NumDeletions)
fmt.Fprintf(&b, "num-range-keys: %d\n", f.Stats.NumRangeKeys)
fmt.Fprintf(&b, "point-deletions-bytes-estimate: %d\n", f.Stats.PointDeletionsBytesEstimate)
fmt.Fprintf(&b, "range-deletions-bytes-estimate: %d\n", f.Stats.RangeDeletionsBytesEstimate)
return b.String()
Expand Down
2 changes: 2 additions & 0 deletions internal/manifest/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ type TableStats struct {
NumEntries uint64
// The number of point and range deletion entries in the table.
NumDeletions uint64
// NumRangeKeys is the total number of range keys in the table.
NumRangeKeys uint64
// Estimate of the total disk space that may be dropped by this table's
// point deletions by compacting them.
PointDeletionsBytesEstimate uint64
Expand Down
167 changes: 97 additions & 70 deletions table_stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,79 +253,20 @@ func (d *DB) loadTableStats(
stats.NumEntries = r.Properties.NumEntries
stats.NumDeletions = r.Properties.NumDeletions
if r.Properties.NumPointDeletions() > 0 {
// TODO(jackson): If the file has a wide keyspace, the average
// value size beneath the entire file might not be representative
// of the size of the keys beneath the point tombstones.
// We could write the ranges of 'clusters' of point tombstones to
// a sstable property and call averageValueSizeBeneath for each of
// these narrower ranges to improve the estimate.
avgKeySize, avgValSize, err := d.averageEntrySizeBeneath(v, level, meta)
if err != nil {
return err
if err = d.loadTablePointKeyStats(r, v, level, meta, &stats); err != nil {
return
}
stats.PointDeletionsBytesEstimate = pointDeletionsBytesEstimate(&r.Properties, avgKeySize, avgValSize)
}

if r.Properties.NumRangeDeletions == 0 {
return nil
}
// We iterate over the defragmented range tombstones, which ensures
// we don't double count ranges deleted at different sequence numbers.
// Also, merging abutting tombstones reduces the number of calls to
// estimateSizeBeneath which is costly, and improves the accuracy of
// our overall estimate.
rangeDelIter, err := r.NewRawRangeDelIter()
if err != nil {
return err
if r.Properties.NumRangeDeletions > 0 {
if compactionHints, err = d.loadTableRangeDelStats(r, v, level, meta, &stats); err != nil {
return
}
}
defer rangeDelIter.Close()
// Truncate tombstones to the containing file's bounds if necessary.
// See docs/range_deletions.md for why this is necessary.
rangeDelIter = keyspan.Truncate(
d.cmp, rangeDelIter, meta.Smallest.UserKey, meta.Largest.UserKey, nil, nil)
err = foreachDefragmentedTombstone(rangeDelIter, d.cmp,
func(startUserKey, endUserKey []byte, smallestSeqNum, largestSeqNum uint64) error {
// If the file is in the last level of the LSM, there is no
// data beneath it. The fact that there is still a range
// tombstone in a bottommost file suggests that an open
// snapshot kept the tombstone around. Estimate disk usage
// within the file itself.
if level == numLevels-1 {
size, err := r.EstimateDiskUsage(startUserKey, endUserKey)
if err != nil {
return err
}
stats.RangeDeletionsBytesEstimate += size
return nil
}

estimate, hintSeqNum, err := d.estimateSizeBeneath(v, level, meta, startUserKey, endUserKey)
if err != nil {
return err
}
stats.RangeDeletionsBytesEstimate += estimate

// If any files were completely contained with the range,
// hintSeqNum is the smallest sequence number contained in any
// such file.
if hintSeqNum == math.MaxUint64 {
return nil
}
hint := deleteCompactionHint{
start: make([]byte, len(startUserKey)),
end: make([]byte, len(endUserKey)),
tombstoneFile: meta,
tombstoneLevel: level,
tombstoneLargestSeqNum: largestSeqNum,
tombstoneSmallestSeqNum: smallestSeqNum,
fileSmallestSeqNum: hintSeqNum,
}
copy(hint.start, startUserKey)
copy(hint.end, endUserKey)
compactionHints = append(compactionHints, hint)
return nil
})
return err
// TODO(travers): Once we have real-world data, consider collecting
// additional stats that may provide improved heuristics for compaction
// picking.
stats.NumRangeKeys = r.Properties.NumRangeKeys()
return
})
if err != nil {
return stats, nil, err
Expand All @@ -334,6 +275,91 @@ func (d *DB) loadTableStats(
return stats, compactionHints, nil
}

// loadTablePointKeyStats calculates the point key statistics for the given
// table. The provided manifest.TableStats are updated.
func (d *DB) loadTablePointKeyStats(
r *sstable.Reader, v *version, level int, meta *fileMetadata, stats *manifest.TableStats,
) error {
// TODO(jackson): If the file has a wide keyspace, the average
// value size beneath the entire file might not be representative
// of the size of the keys beneath the point tombstones.
// We could write the ranges of 'clusters' of point tombstones to
// a sstable property and call averageValueSizeBeneath for each of
// these narrower ranges to improve the estimate.
avgKeySize, avgValSize, err := d.averageEntrySizeBeneath(v, level, meta)
if err != nil {
return err
}
stats.PointDeletionsBytesEstimate =
pointDeletionsBytesEstimate(&r.Properties, avgKeySize, avgValSize)
return nil
}

// loadTableRangeDelStats calculates the range deletion statistics for the given
// table.
func (d *DB) loadTableRangeDelStats(
r *sstable.Reader, v *version, level int, meta *fileMetadata, stats *manifest.TableStats,
) ([]deleteCompactionHint, error) {
var compactionHints []deleteCompactionHint
// We iterate over the defragmented range tombstones, which ensures
// we don't double count ranges deleted at different sequence numbers.
// Also, merging abutting tombstones reduces the number of calls to
// estimateSizeBeneath which is costly, and improves the accuracy of
// our overall estimate.
rangeDelIter, err := r.NewRawRangeDelIter()
if err != nil {
return nil, err
}
defer rangeDelIter.Close()
// Truncate tombstones to the containing file's bounds if necessary.
// See docs/range_deletions.md for why this is necessary.
rangeDelIter = keyspan.Truncate(
d.cmp, rangeDelIter, meta.Smallest.UserKey, meta.Largest.UserKey, nil, nil)
err = foreachDefragmentedTombstone(rangeDelIter, d.cmp,
func(startUserKey, endUserKey []byte, smallestSeqNum, largestSeqNum uint64) error {
// If the file is in the last level of the LSM, there is no
// data beneath it. The fact that there is still a range
// tombstone in a bottommost file suggests that an open
// snapshot kept the tombstone around. Estimate disk usage
// within the file itself.
if level == numLevels-1 {
size, err := r.EstimateDiskUsage(startUserKey, endUserKey)
if err != nil {
return err
}
stats.RangeDeletionsBytesEstimate += size
return nil
}

estimate, hintSeqNum, err := d.estimateSizeBeneath(v, level, meta, startUserKey, endUserKey)
if err != nil {
return err
}
stats.RangeDeletionsBytesEstimate += estimate

// If any files were completely contained with the range,
// hintSeqNum is the smallest sequence number contained in any
// such file.
if hintSeqNum == math.MaxUint64 {
return nil
}
hint := deleteCompactionHint{
start: make([]byte, len(startUserKey)),
end: make([]byte, len(endUserKey)),
tombstoneFile: meta,
tombstoneLevel: level,
tombstoneLargestSeqNum: largestSeqNum,
tombstoneSmallestSeqNum: smallestSeqNum,
fileSmallestSeqNum: hintSeqNum,
}
copy(hint.start, startUserKey)
copy(hint.end, endUserKey)
compactionHints = append(compactionHints, hint)
return nil
})
return compactionHints, err
}

func (d *DB) averageEntrySizeBeneath(
v *version, level int, meta *fileMetadata,
) (avgKeySize, avgValueSize uint64, err error) {
Expand Down Expand Up @@ -510,6 +536,7 @@ func maybeSetStatsFromProperties(meta *fileMetadata, props *sstable.Properties)
Valid: true,
NumEntries: props.NumEntries,
NumDeletions: props.NumDeletions,
NumRangeKeys: props.NumRangeKeys(),
PointDeletionsBytesEstimate: pointEstimate,
RangeDeletionsBytesEstimate: 0,
}
Expand Down
16 changes: 16 additions & 0 deletions table_stats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/cockroachdb/pebble/internal/base"
"github.com/cockroachdb/pebble/internal/datadriven"
"github.com/cockroachdb/pebble/internal/keyspan"
"github.com/cockroachdb/pebble/internal/testkeys"
"github.com/cockroachdb/pebble/vfs"
"github.com/stretchr/testify/require"
)
Expand All @@ -28,6 +29,9 @@ func TestTableStats(t *testing.T) {
},
}
opts.DisableAutomaticCompactions = true
opts.Comparer = testkeys.Comparer
opts.Experimental.RangeKeys = new(RangeKeysArena)
opts.FormatMajorVersion = FormatRangeKeys

d, err := Open("", opts)
require.NoError(t, err)
Expand Down Expand Up @@ -92,6 +96,18 @@ func TestTableStats(t *testing.T) {
d.mu.Unlock()
return s

case "ingest":
if err = runBuildCmd(td, d, d.opts.FS); err != nil {
return err.Error()
}
if err = runIngestCmd(td, d, d.opts.FS); err != nil {
return err.Error()
}
d.mu.Lock()
s := d.mu.versions.currentVersion().String()
d.mu.Unlock()
return s

case "wait-pending-table-stats":
return runTableStatsCmd(td, d)

Expand Down
1 change: 1 addition & 0 deletions testdata/compaction_delete_only_hints
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ wait-pending-table-stats
----
num-entries: 2
num-deletions: 1
num-range-keys: 0
point-deletions-bytes-estimate: 0
range-deletions-bytes-estimate: 26

Expand Down
8 changes: 8 additions & 0 deletions testdata/compaction_tombstones
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ wait-pending-table-stats
----
num-entries: 2
num-deletions: 2
num-range-keys: 0
point-deletions-bytes-estimate: 0
range-deletions-bytes-estimate: 0

Expand All @@ -34,6 +35,7 @@ wait-pending-table-stats
----
num-entries: 2
num-deletions: 2
num-range-keys: 0
point-deletions-bytes-estimate: 0
range-deletions-bytes-estimate: 0

Expand All @@ -54,6 +56,7 @@ wait-pending-table-stats
----
num-entries: 2
num-deletions: 1
num-range-keys: 0
point-deletions-bytes-estimate: 0
range-deletions-bytes-estimate: 26

Expand All @@ -76,6 +79,7 @@ wait-pending-table-stats
----
num-entries: 2
num-deletions: 1
num-range-keys: 0
point-deletions-bytes-estimate: 0
range-deletions-bytes-estimate: 0

Expand Down Expand Up @@ -114,6 +118,7 @@ wait-pending-table-stats
----
num-entries: 6
num-deletions: 2
num-range-keys: 0
point-deletions-bytes-estimate: 0
range-deletions-bytes-estimate: 76

Expand Down Expand Up @@ -146,6 +151,7 @@ wait-pending-table-stats
----
num-entries: 11
num-deletions: 1
num-range-keys: 0
point-deletions-bytes-estimate: 149
range-deletions-bytes-estimate: 0

Expand Down Expand Up @@ -189,6 +195,7 @@ wait-pending-table-stats
----
num-entries: 5
num-deletions: 1
num-range-keys: 0
point-deletions-bytes-estimate: 0
range-deletions-bytes-estimate: 16488

Expand Down Expand Up @@ -225,6 +232,7 @@ wait-pending-table-stats
----
num-entries: 3
num-deletions: 3
num-range-keys: 0
point-deletions-bytes-estimate: 13167
range-deletions-bytes-estimate: 0

Expand Down
2 changes: 2 additions & 0 deletions testdata/ingest
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ wait-pending-table-stats
----
num-entries: 2
num-deletions: 0
num-range-keys: 0
point-deletions-bytes-estimate: 0
range-deletions-bytes-estimate: 0

Expand Down Expand Up @@ -346,6 +347,7 @@ wait-pending-table-stats
----
num-entries: 2
num-deletions: 2
num-range-keys: 0
point-deletions-bytes-estimate: 0
range-deletions-bytes-estimate: 1666

Expand Down
2 changes: 2 additions & 0 deletions testdata/manual_compaction
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ wait-pending-table-stats
----
num-entries: 1
num-deletions: 1
num-range-keys: 0
point-deletions-bytes-estimate: 0
range-deletions-bytes-estimate: 1552

Expand All @@ -103,6 +104,7 @@ wait-pending-table-stats
----
num-entries: 2
num-deletions: 1
num-range-keys: 0
point-deletions-bytes-estimate: 0
range-deletions-bytes-estimate: 776

Expand Down
2 changes: 2 additions & 0 deletions testdata/manual_compaction_set_with_del
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ wait-pending-table-stats
----
num-entries: 1
num-deletions: 1
num-range-keys: 0
point-deletions-bytes-estimate: 0
range-deletions-bytes-estimate: 1552

Expand All @@ -103,6 +104,7 @@ wait-pending-table-stats
----
num-entries: 2
num-deletions: 1
num-range-keys: 0
point-deletions-bytes-estimate: 0
range-deletions-bytes-estimate: 776

Expand Down
Loading