Skip to content

Commit

Permalink
Fix checkOverlap in compaction (#1166)
Browse files Browse the repository at this point in the history
The overlap check in compaction would keep additional keys in case
the levels under compaction had overlap amongst themselves.
This commit fixes it.

This commit also fixes the `numVersionsToKeep` check. Without this
commit, we would end up keeping `2` versions of a key even when the
number of versions to keep was set to `1`. See
`level 0 to level 1 with lower overlap` test.

Fixes #1053

The following test fails on master but works with this commit
```go
func TestCompaction(t *testing.T) {
	t.Run("level 0 to level 1", func(t *testing.T) {
		dir, err := ioutil.TempDir("", "badger-test")
		require.NoError(t, err)
		defer removeDir(dir)

		// Disable compactions and keep single version of each key.
		opt := DefaultOptions(dir).WithNumCompactors(0).WithNumVersionsToKeep(1)
		db, err := OpenManaged(opt)
		require.NoError(t, err)

		l0 := []keyValVersion{{"foo", "bar", 3}, {"fooz", "baz", 1}}
		l01 := []keyValVersion{{"foo", "bar", 2}}
		l1 := []keyValVersion{{"foo", "bar", 1}}
		// Level 0 has table l0 and l01.
		createAndOpen(db, l0, 0)
		createAndOpen(db, l01, 0)
		// Level 1 has table l1.
		createAndOpen(db, l1, 1)

		// Set a high discard timestamp so that all the keys are below the discard timestamp.
		db.SetDiscardTs(10)

		getAllAndCheck(t, db, []keyValVersion{
			{"foo", "bar", 3}, {"foo", "bar", 2}, {"foo", "bar", 1}, {"fooz", "baz", 1},
		})
		cdef := compactDef{
			thisLevel: db.lc.levels[0],
			nextLevel: db.lc.levels[1],
			top:       db.lc.levels[0].tables,
			bot:       db.lc.levels[1].tables,
		}
		require.NoError(t, db.lc.runCompactDef(0, cdef))
		// foo version 2 should be dropped after compaction.
		getAllAndCheck(t, db, []keyValVersion{{"foo", "bar", 3}, {"fooz", "baz", 1}})
	})
}
```

(cherry picked from commit 0a06173)
  • Loading branch information
Ibrahim Jarif committed Mar 5, 2020
1 parent c66c7a4 commit 6ee3f53
Show file tree
Hide file tree
Showing 2 changed files with 474 additions and 21 deletions.
54 changes: 33 additions & 21 deletions levels.go
Original file line number Diff line number Diff line change
Expand Up @@ -430,28 +430,35 @@ func (s *levelsController) pickCompactLevels() (prios []compactionPriority) {
return prios
}

// compactBuildTables merge topTables and botTables to form a list of new tables.
// checkOverlap checks if the given tables overlap with any level from the given "lev" onwards.
func (s *levelsController) checkOverlap(tables []*table.Table, lev int) bool {
kr := getKeyRange(tables...)
for i, lh := range s.levels {
if i < lev { // Skip upper levels.
continue
}
lh.RLock()
left, right := lh.overlappingTables(levelHandlerRLocked{}, kr)
lh.RUnlock()
if right-left > 0 {
return true
}
}
return false
}

// compactBuildTables merges topTables and botTables to form a list of new tables.
func (s *levelsController) compactBuildTables(
lev int, cd compactDef) ([]*table.Table, func() error, error) {
topTables := cd.top
botTables := cd.bot

var hasOverlap bool
{
kr := getKeyRange(cd.top...)
for i, lh := range s.levels {
if i <= lev { // Skip upper levels.
continue
}
lh.RLock()
left, right := lh.overlappingTables(levelHandlerRLocked{}, kr)
lh.RUnlock()
if right-left > 0 {
hasOverlap = true
break
}
}
}
// Check overlap of the top level with the levels which are not being
// compacted in this compaction. We don't need to check overlap of the bottom
// tables with other levels because if the top tables overlap with any of the lower
// levels, it implies bottom level also overlaps because top and bottom tables
// overlap with each other.
hasOverlap := s.checkOverlap(cd.top, cd.nextLevel.level+1)

// Try to collect stats so that we can inform value log about GC. That would help us find which
// value log file should be GCed.
Expand Down Expand Up @@ -561,10 +568,15 @@ func (s *levelsController) compactBuildTables(
// versions which are below the minReadTs, otherwise, we might end up discarding the
// only valid version for a running transaction.
numVersions++
lastValidVersion := vs.Meta&bitDiscardEarlierVersions > 0
if isDeletedOrExpired(vs.Meta, vs.ExpiresAt) ||
numVersions > s.kv.opt.NumVersionsToKeep ||
lastValidVersion {

// Keep the current version and discard all the next versions if
// - The `discardEarlierVersions` bit is set OR
// - We've already processed `NumVersionsToKeep` number of versions
// (including the current item being processed)
lastValidVersion := vs.Meta&bitDiscardEarlierVersions > 0 ||
numVersions == s.kv.opt.NumVersionsToKeep

if isDeletedOrExpired(vs.Meta, vs.ExpiresAt) || lastValidVersion {
// If this version of the key is deleted or expired, skip all the rest of the
// versions. Ensure that we're only removing versions below readTs.
skipKey = y.SafeCopy(skipKey, it.Key())
Expand Down
Loading

0 comments on commit 6ee3f53

Please sign in to comment.