Skip to content

Commit

Permalink
manifest: unref files with range keys when version refcnt is zero
Browse files Browse the repository at this point in the history
A manifest version uses reference counting to ensure that obsolete
SSTables are only removed once it is safe to do so (i.e. no iterators or
snapshots are open referencing tables in a older manifest version).

In cockroachdb#1771, a new slice of `LevelMetadata` was added to support range
keys. A slice already existed for point keys (plus range dels).
Currently, when a version's ref count reaches zero, only the files
present in the point key `LevelMetadata` slice have their ref counts
decremented. If a file also contains range keys, the reference count
never becomes zero and the file becomes "zombied", and can never be
deleted from disk.

Decrement the ref counts on files present in the range keys
`LevelMetadata` slice, allowing the ref counts to be correctly zeroed.

Add regression test to exercise the bug observed in
cockroachdb/cockroach#90149.

Fix cockroachdb/cockroach#90149.
  • Loading branch information
nicktrav committed Oct 20, 2022
1 parent 653147d commit cc0dd37
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 0 deletions.
7 changes: 7 additions & 0 deletions compaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2200,6 +2200,13 @@ func TestCompactionTombstones(t *testing.T) {
d.mu.Unlock()
return str

case "close":
if err := d.Close(); err != nil {
return err.Error()
}
d = nil
return ""

case "version":
d.mu.Lock()
s := d.mu.versions.currentVersion().String()
Expand Down
6 changes: 6 additions & 0 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -1316,6 +1316,12 @@ func (d *DB) Close() error {
d.compactionSchedulers.Wait()
d.mu.Lock()

// As a sanity check, ensure that there are no zombie tables. A non-zero count
// hints at a reference count leak.
if ztbls := len(d.mu.versions.zombieTables); ztbls > 0 {
err = firstError(err, errors.Errorf("non-zero zombie file count: %d", ztbls))
}

// If the options include a closer to 'close' the filesystem, close it.
if d.opts.private.fsCloser != nil {
d.opts.private.fsCloser.Close()
Expand Down
3 changes: 3 additions & 0 deletions internal/manifest/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,9 @@ func (v *Version) unrefFiles() []*FileMetadata {
for _, lm := range v.Levels {
obsolete = append(obsolete, lm.release()...)
}
for _, lm := range v.RangeKeyLevels {
obsolete = append(obsolete, lm.release()...)
}
return obsolete
}

Expand Down
37 changes: 37 additions & 0 deletions testdata/compaction_tombstones
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,40 @@ range-deletions-bytes-estimate: 0
maybe-compact
----
(none)

# Regression test for cockroachdb/cockroach#90149, exercising reference counting
# on tables that contain a mixture of point, range dels and range keys.
#
# Place a table in L6 that contains a RANGEKEYDEL. Because this table is in the
# bottom of the LSM, and there are no range keys below it, the RANGEKEYDEL is
# eligible for elision (the RANGEDEL too). After the elision, the input table
# should be deleted. In #90149, the table still had a reference count, and
# therefore could not be deleted from the filesystem.

define
L6
rangekey:a-b:{(#1,RANGEKEYDEL)}
a.SET.2:a
b.SET.3:b
c.SET.4:c
c.RANGEDEL.5:z
----
6:
000004:[a#2,SET-z#72057594037927935,RANGEDEL]

wait-pending-table-stats
000004
----
num-entries: 3
num-deletions: 1
num-range-key-sets: 0
point-deletions-bytes-estimate: 0
range-deletions-bytes-estimate: 39

maybe-compact
----
[JOB 100] compacted(elision-only) L6 [000004] (1001 B) + L6 [] (0 B) -> L6 [000005] (778 B), in 1.0s (2.0s total), output rate 778 B/s

# Close the DB, asserting that the reference counts balance.
close
----

0 comments on commit cc0dd37

Please sign in to comment.