Skip to content

Commit 69f59c5

Browse files
committed
leveldb: implement deleteObsoleteFiles.
Also implement tableCache eviction, parseDBFilename, and delete the logFileNum function. R=bradfitz CC=golang-dev https://codereview.appspot.com/19710043
1 parent 477ea9b commit 69f59c5

File tree

8 files changed

+263
-32
lines changed

8 files changed

+263
-32
lines changed

leveldb/compaction.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,8 @@ func (d *DB) compact1() error {
214214
if err := d.versions.logAndApply(d.dirname, ve); err != nil {
215215
return err
216216
}
217-
return d.deleteObsoleteFiles()
217+
d.deleteObsoleteFiles()
218+
return nil
218219
}
219220

220221
// compactMemTable runs a compaction that copies d.imm from memory to disk.
@@ -236,7 +237,8 @@ func (d *DB) compactMemTable() error {
236237
return err
237238
}
238239
d.imm = nil
239-
return d.deleteObsoleteFiles()
240+
d.deleteObsoleteFiles()
241+
return nil
240242
}
241243

242244
// compactDiskTables runs a compaction that produces new on-disk tables from

leveldb/filenames.go

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,24 @@ package leveldb
77
import (
88
"fmt"
99
"os"
10+
"path/filepath"
1011
"strconv"
1112
"strings"
1213

1314
"code.google.com/p/leveldb-go/leveldb/db"
1415
)
1516

17+
type fileType int
18+
1619
const (
17-
fileTypeLog = iota
20+
fileTypeLog fileType = iota
1821
fileTypeLock
1922
fileTypeTable
2023
fileTypeManifest
2124
fileTypeCurrent
2225
)
2326

24-
func dbFilename(dirname string, fileType int, fileNum uint64) string {
27+
func dbFilename(dirname string, fileType fileType, fileNum uint64) string {
2528
for len(dirname) > 0 && dirname[len(dirname)-1] == os.PathSeparator {
2629
dirname = dirname[:len(dirname)-1]
2730
}
@@ -40,18 +43,36 @@ func dbFilename(dirname string, fileType int, fileNum uint64) string {
4043
panic("unreachable")
4144
}
4245

43-
// logFileNum returns the fileNum of the given log file, or 0 if that file is
44-
// not a log file.
45-
func logFileNum(filename string) uint64 {
46-
if !strings.HasSuffix(filename, ".log") {
47-
return 0
48-
}
49-
filename = filename[:len(filename)-4]
50-
u, err := strconv.ParseUint(filename, 10, 64)
51-
if err != nil {
52-
return 0
46+
func parseDBFilename(filename string) (fileType fileType, fileNum uint64, ok bool) {
47+
filename = filepath.Base(filename)
48+
switch {
49+
case filename == "CURRENT":
50+
return fileTypeCurrent, 0, true
51+
case filename == "LOCK":
52+
return fileTypeLock, 0, true
53+
case strings.HasPrefix(filename, "MANIFEST-"):
54+
u, err := strconv.ParseUint(filename[len("MANIFEST-"):], 10, 64)
55+
if err != nil {
56+
break
57+
}
58+
return fileTypeManifest, u, true
59+
default:
60+
i := strings.IndexByte(filename, '.')
61+
if i < 0 {
62+
break
63+
}
64+
u, err := strconv.ParseUint(filename[:i], 10, 64)
65+
if err != nil {
66+
break
67+
}
68+
switch filename[i+1:] {
69+
case "log":
70+
return fileTypeLog, u, true
71+
case "sst":
72+
return fileTypeTable, u, true
73+
}
5374
}
54-
return u
75+
return 0, 0, false
5576
}
5677

5778
func setCurrentFile(dirname string, fs db.FileSystem, fileNum uint64) error {

leveldb/filenames_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright 2013 The LevelDB-Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package leveldb
6+
7+
import (
8+
"path/filepath"
9+
"testing"
10+
)
11+
12+
func TestParseDBFilename(t *testing.T) {
13+
testCases := map[string]bool{
14+
"000000.log": true,
15+
"000000.log.zip": false,
16+
"000000..log": false,
17+
"a000000.log": false,
18+
"abcdef.log": false,
19+
"000001.sst": true,
20+
"CURRENT": true,
21+
"CURRaNT": false,
22+
"LOCK": true,
23+
"xLOCK": false,
24+
"x.LOCK": false,
25+
"MANIFEST": false,
26+
"MANIFEST123456": false,
27+
"MANIFEST-": false,
28+
"MANIFEST-123456": true,
29+
"MANIFEST-123456.doc": false,
30+
}
31+
for tc, want := range testCases {
32+
_, _, got := parseDBFilename(filepath.Join("foo", tc))
33+
if got != want {
34+
t.Errorf("%q: got %v, want %v", tc, got, want)
35+
}
36+
}
37+
}
38+
39+
func TestFilenameRoundTrip(t *testing.T) {
40+
testCases := map[fileType]bool{
41+
// CURRENT and LOCK files aren't numbered.
42+
fileTypeCurrent: false,
43+
fileTypeLock: false,
44+
// The remaining file types are numbered.
45+
fileTypeLog: true,
46+
fileTypeManifest: true,
47+
fileTypeTable: true,
48+
}
49+
for fileType, numbered := range testCases {
50+
fileNums := []uint64{0}
51+
if numbered {
52+
fileNums = []uint64{0, 1, 2, 3, 10, 42, 99, 1001}
53+
}
54+
for _, fileNum := range fileNums {
55+
filename := dbFilename("foo", fileType, fileNum)
56+
gotFT, gotFN, gotOK := parseDBFilename(filename)
57+
if !gotOK {
58+
t.Errorf("could not parse %q", filename)
59+
continue
60+
}
61+
if gotFT != fileType || gotFN != fileNum {
62+
t.Errorf("filename=%q: got %v, %v, want %v, %v", filename, gotFT, gotFN, fileType, fileNum)
63+
continue
64+
}
65+
}
66+
}
67+
}

leveldb/leveldb.go

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -297,9 +297,9 @@ func Open(dirname string, opts *db.Options) (*DB, error) {
297297
}
298298
var logFiles fileNumAndNameSlice
299299
for _, filename := range ls {
300-
n := logFileNum(filename)
301-
if n != 0 && (n >= d.versions.logNumber || n == d.versions.prevLogNumber) {
302-
logFiles = append(logFiles, fileNumAndName{n, filename})
300+
ft, fn, ok := parseDBFilename(filename)
301+
if ok && ft == fileTypeLog && (fn >= d.versions.logNumber || fn == d.versions.prevLogNumber) {
302+
logFiles = append(logFiles, fileNumAndName{fn, filename})
303303
}
304304
}
305305
sort.Sort(logFiles)
@@ -333,9 +333,7 @@ func Open(dirname string, opts *db.Options) (*DB, error) {
333333
return nil, err
334334
}
335335

336-
if err := d.deleteObsoleteFiles(); err != nil {
337-
return nil, err
338-
}
336+
d.deleteObsoleteFiles()
339337
d.maybeScheduleCompaction()
340338

341339
d.logFile, logFile = logFile, nil
@@ -608,7 +606,47 @@ func (d *DB) makeRoomForWrite(force bool) error {
608606
//
609607
// d.mu must be held when calling this, but the mutex may be dropped and
610608
// re-acquired during the course of this method.
611-
func (d *DB) deleteObsoleteFiles() error {
612-
// TODO: implement.
613-
return nil
609+
func (d *DB) deleteObsoleteFiles() {
610+
// TODO: (elsewhere) track pending outputs, and refer to them here.
611+
liveFileNums := map[uint64]struct{}{}
612+
613+
d.versions.addLiveFileNums(liveFileNums)
614+
logNumber := d.versions.logNumber
615+
manifestFileNumber := d.versions.manifestFileNumber
616+
617+
// Release the d.mu lock while doing I/O.
618+
// Note the unusual order: Unlock and then Lock.
619+
d.mu.Unlock()
620+
defer d.mu.Lock()
621+
622+
fs := d.opts.GetFileSystem()
623+
list, err := fs.List(d.dirname)
624+
if err != nil {
625+
// Ignore any filesystem errors.
626+
return
627+
}
628+
for _, filename := range list {
629+
fileType, fileNum, ok := parseDBFilename(filename)
630+
if !ok {
631+
return
632+
}
633+
keep := true
634+
switch fileType {
635+
case fileTypeLog:
636+
// TODO: also look at prevLogNumber?
637+
keep = fileNum >= logNumber
638+
case fileTypeManifest:
639+
keep = fileNum >= manifestFileNumber
640+
case fileTypeTable:
641+
_, keep = liveFileNums[fileNum]
642+
}
643+
if keep {
644+
continue
645+
}
646+
if fileType == fileTypeTable {
647+
d.tableCache.evict(fileNum)
648+
}
649+
// Ignore any file system errors.
650+
fs.Remove(filepath.Join(d.dirname, filename))
651+
}
614652
}

leveldb/leveldb_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import (
99
"io"
1010
"math/rand"
1111
"os"
12+
"path/filepath"
13+
"reflect"
14+
"sort"
1215
"strconv"
1316
"strings"
1417
"testing"
@@ -46,6 +49,34 @@ func TestErrorIfDBExists(t *testing.T) {
4649
}
4750
}
4851

52+
func TestNewDBFilenames(t *testing.T) {
53+
fooBar := filepath.Join("foo", "bar")
54+
fs := memfs.New()
55+
d, err := Open(fooBar, &db.Options{
56+
FileSystem: fs,
57+
})
58+
if err != nil {
59+
t.Fatalf("Open: %v", err)
60+
}
61+
if err := d.Close(); err != nil {
62+
t.Fatalf("Close: %v", err)
63+
}
64+
got, err := fs.List(fooBar)
65+
if err != nil {
66+
t.Fatalf("List: %v", err)
67+
}
68+
sort.Strings(got)
69+
// TODO: should there be a LOCK file here?
70+
want := []string{
71+
"000003.log",
72+
"CURRENT",
73+
"MANIFEST-000002",
74+
}
75+
if !reflect.DeepEqual(got, want) {
76+
t.Errorf("\ngot %v\nwant %v", got, want)
77+
}
78+
}
79+
4980
// cloneFileSystem returns a new memory-backed file system whose root contains
5081
// a copy of the directory dirname in the source file system srcFS. The copy
5182
// is not recursive; directories under dirname are not copied.

leveldb/table_cache.go

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,19 @@ func (c *tableCache) find(fileNum uint64, ikey internalKey) (db.Iterator, error)
5959
}, nil
6060
}
6161

62+
// releaseNode releases a node from the tableCache.
63+
//
64+
// c.mu must be held when calling this.
65+
func (c *tableCache) releaseNode(n *tableCacheNode) {
66+
delete(c.nodes, n.fileNum)
67+
n.next.prev = n.prev
68+
n.prev.next = n.next
69+
n.refCount--
70+
if n.refCount == 0 {
71+
go n.release()
72+
}
73+
}
74+
6275
// findNode returns the node for the table with the given file number, creating
6376
// that node if it didn't already exist. The caller is responsible for
6477
// decrementing the returned node's refCount.
@@ -76,14 +89,7 @@ func (c *tableCache) findNode(fileNum uint64) *tableCacheNode {
7689
c.nodes[fileNum] = n
7790
if len(c.nodes) > c.size {
7891
// Release the tail node.
79-
tail := c.dummy.prev
80-
delete(c.nodes, tail.fileNum)
81-
tail.next.prev = tail.prev
82-
tail.prev.next = tail.next
83-
tail.refCount--
84-
if tail.refCount == 0 {
85-
go tail.release()
86-
}
92+
c.releaseNode(c.dummy.prev)
8793
}
8894
go n.load(c)
8995
} else {
@@ -101,6 +107,15 @@ func (c *tableCache) findNode(fileNum uint64) *tableCacheNode {
101107
return n
102108
}
103109

110+
func (c *tableCache) evict(fileNum uint64) {
111+
c.mu.Lock()
112+
defer c.mu.Unlock()
113+
114+
if n := c.nodes[fileNum]; n != nil {
115+
c.releaseNode(n)
116+
}
117+
}
118+
104119
func (c *tableCache) Close() error {
105120
c.mu.Lock()
106121
defer c.mu.Unlock()

leveldb/table_cache_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,50 @@ func TestTableCacheFrequentlyUsed(t *testing.T) {
222222
}
223223
})
224224
}
225+
226+
func TestTableCacheEvictions(t *testing.T) {
227+
const (
228+
N = 1000
229+
lo, hi = 10, 20
230+
)
231+
c, fs, err := newTableCache()
232+
if err != nil {
233+
t.Fatal(err)
234+
}
235+
236+
rng := rand.New(rand.NewSource(2))
237+
for i := 0; i < N; i++ {
238+
j := rng.Intn(tableCacheTestNumTables)
239+
iter, err := c.find(uint64(j), nil)
240+
if err != nil {
241+
t.Fatalf("i=%d, j=%d: find: %v", i, j, err)
242+
}
243+
if err := iter.Close(); err != nil {
244+
t.Fatalf("i=%d, j=%d: close: %v", i, j, err)
245+
}
246+
247+
c.evict(uint64(lo + rng.Intn(hi-lo)))
248+
}
249+
250+
sumEvicted, nEvicted := 0, 0
251+
sumSafe, nSafe := 0, 0
252+
fs.validate(t, c, func(i, gotO, gotC int) {
253+
if lo <= i && i < hi {
254+
sumEvicted += gotO
255+
nEvicted++
256+
} else {
257+
sumSafe += gotO
258+
nSafe++
259+
}
260+
})
261+
fEvicted := float64(sumEvicted) / float64(nEvicted)
262+
fSafe := float64(sumSafe) / float64(nSafe)
263+
// The magic 1.25 number isn't derived from formal modeling. It's just a guess. For
264+
// (lo, hi, tableCacheTestCacheSize, tableCacheTestNumTables) = (10, 20, 100, 300),
265+
// the ratio seems to converge on roughly 1.5 for large N, compared to 1.0 if we do
266+
// not evict any cache entries.
267+
if ratio := fEvicted / fSafe; ratio < 1.25 {
268+
t.Errorf("evicted tables were opened %.3f times on average, safe tables %.3f, ratio %.3f < 1.250",
269+
fEvicted, fSafe, ratio)
270+
}
271+
}

leveldb/version_set.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,3 +265,13 @@ func (vs *versionSet) append(v *version) {
265265
func (vs *versionSet) currentVersion() *version {
266266
return vs.dummyVersion.prev
267267
}
268+
269+
func (vs *versionSet) addLiveFileNums(m map[uint64]struct{}) {
270+
for v := vs.dummyVersion.next; v != &vs.dummyVersion; v = v.next {
271+
for _, ff := range v.files {
272+
for _, f := range ff {
273+
m[f.fileNum] = struct{}{}
274+
}
275+
}
276+
}
277+
}

0 commit comments

Comments
 (0)