Skip to content

Commit

Permalink
db: surface OPTIONS filename in pebble.Peek
Browse files Browse the repository at this point in the history
The pebble.Peek function allows external users to retrieve metadata about an
Engine without opening it. This commit adds the filename of the latest OPTIONS
file to the output, so the caller may parse the options used by the most recent
process. Additionally, this commit adds new methods for locating
atomicfs.Marker that use an existing directory listing to avoid repeated
listing of the data directory during Open and Peek.
  • Loading branch information
jbowens committed Mar 19, 2024
1 parent 85cc117 commit 13874c1
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 22 deletions.
4 changes: 2 additions & 2 deletions format_major_version.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,9 +282,9 @@ const formatVersionMarkerName = `format-version`
// only acceptable if we are creating a new store (we no longer support
// FormatMostCompatible which is the only one with no version marker file).
func lookupFormatMajorVersion(
fs vfs.FS, dirname string,
fs vfs.FS, dirname string, ls []string,
) (FormatMajorVersion, *atomicfs.Marker, error) {
m, versString, err := atomicfs.LocateMarker(fs, dirname, formatVersionMarkerName)
m, versString, err := atomicfs.LocateMarkerInListing(fs, dirname, formatVersionMarkerName, ls)
if err != nil {
return 0, nil, err
}
Expand Down
1 change: 1 addition & 0 deletions ingest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3038,6 +3038,7 @@ func TestIngestMemtableOverlapRace(t *testing.T) {
dbDesc, err := Peek("", mem)
require.NoError(t, err)
require.True(t, dbDesc.Exists)
require.Greater(t, len(dbDesc.OptionsFilename), 0)
f, err = mem.Open(dbDesc.ManifestFilename)
require.NoError(t, err)
defer f.Close()
Expand Down
56 changes: 45 additions & 11 deletions open.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,16 @@ func Open(dirname string, opts *Options) (db *DB, err error) {
}
}()

// List the directory contents. This also happens to include WAL log files, if
// they are in the same dir, but we will ignore those below. The provider is
// also given this list, but it ignores non sstable files.
ls, err := opts.FS.List(dirname)
if err != nil {
return nil, err
}

// Establish the format major version.
formatVersion, formatVersionMarker, err := lookupFormatMajorVersion(opts.FS, dirname)
formatVersion, formatVersionMarker, err := lookupFormatMajorVersion(opts.FS, dirname, ls)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -169,7 +177,7 @@ func Open(dirname string, opts *Options) (db *DB, err error) {
}

// Find the currently active manifest, if there is one.
manifestMarker, manifestFileNum, manifestExists, err := findCurrentManifest(opts.FS, dirname)
manifestMarker, manifestFileNum, manifestExists, err := findCurrentManifest(opts.FS, dirname, ls)
if err != nil {
return nil, errors.Wrapf(err, "pebble: database %q", dirname)
}
Expand Down Expand Up @@ -281,13 +289,6 @@ func Open(dirname string, opts *Options) (db *DB, err error) {
jobID := d.mu.nextJobID
d.mu.nextJobID++

// List the objects. This also happens to include WAL log files, if they are
// in the same dir, but we will ignore those below. The provider is also
// given this list, but it ignores non sstable files.
ls, err := opts.FS.List(d.dirname)
if err != nil {
return nil, err
}
providerSettings := objstorageprovider.Settings{
Logger: opts.Logger,
FS: opts.FS,
Expand Down Expand Up @@ -1076,13 +1077,33 @@ type DBDesc struct {
// ManifestFilename is the filename of the current active manifest,
// if the database exists.
ManifestFilename string
// OptionsFilename is the filename of the most recent OPTIONS file, if it
// exists.
OptionsFilename string
}

// String implements fmt.Stringer.
func (d *DBDesc) String() string {
if !d.Exists {
return "uninitialized"
}
var buf bytes.Buffer
fmt.Fprintf(&buf, "initialized at format major version %s\n", d.FormatMajorVersion)
fmt.Fprintf(&buf, "manifest: %s\n", d.ManifestFilename)
fmt.Fprintf(&buf, "options: %s", d.OptionsFilename)
return buf.String()
}

// Peek looks for an existing database in dirname on the provided FS. It
// returns a brief description of the database. Peek is read-only and
// does not open the database
func Peek(dirname string, fs vfs.FS) (*DBDesc, error) {
vers, versMarker, err := lookupFormatMajorVersion(fs, dirname)
ls, err := fs.List(dirname)
if err != nil {
return nil, err
}

vers, versMarker, err := lookupFormatMajorVersion(fs, dirname, ls)
if err != nil {
return nil, err
}
Expand All @@ -1093,7 +1114,7 @@ func Peek(dirname string, fs vfs.FS) (*DBDesc, error) {
}

// Find the currently active manifest, if there is one.
manifestMarker, manifestFileNum, exists, err := findCurrentManifest(fs, dirname)
manifestMarker, manifestFileNum, exists, err := findCurrentManifest(fs, dirname, ls)
if err != nil {
return nil, err
}
Expand All @@ -1107,6 +1128,19 @@ func Peek(dirname string, fs vfs.FS) (*DBDesc, error) {
Exists: exists,
FormatMajorVersion: vers,
}

// Find the OPTIONS file with the highest file number within the list of
// directory entries.
var previousOptionsFileNum base.DiskFileNum
for _, filename := range ls {
ft, fn, ok := base.ParseFilename(fs, filename)
if !ok || ft != fileTypeOptions || fn < previousOptionsFileNum {
continue
}
previousOptionsFileNum = fn
desc.OptionsFilename = fs.PathJoin(dirname, filename)
}

if exists {
desc.ManifestFilename = base.MakeFilepath(fs, dirname, fileTypeManifest, manifestFileNum)
}
Expand Down
22 changes: 22 additions & 0 deletions open_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"os"
"path/filepath"
"reflect"
"runtime"
"runtime/debug"
"slices"
"sort"
Expand Down Expand Up @@ -1189,6 +1190,27 @@ func TestOpenWALReplayMemtableGrowth(t *testing.T) {
db.Close()
}

func TestPeek(t *testing.T) {
// The file paths are UNIX-oriented. To avoid duplicating the test fixtures
// just for Windows, just skip the tests on Windows.
if runtime.GOOS == "windows" {
t.Skip()
}

datadriven.RunTest(t, "testdata/peek", func(t *testing.T, td *datadriven.TestData) string {
switch td.Cmd {
case "peek":
desc, err := Peek(td.CmdArgs[0].String(), vfs.Default)
if err != nil {
return fmt.Sprintf("err=%v", err)
}
return desc.String()
default:
return fmt.Sprintf("unrecognized command %q\n", td.Cmd)
}
})
}

func TestGetVersion(t *testing.T) {
mem := vfs.NewMem()
opts := &Options{
Expand Down
31 changes: 31 additions & 0 deletions testdata/peek
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
peek testdata/db-stage-1
----
initialized at format major version 013
manifest: testdata/db-stage-1/MANIFEST-000001
options: testdata/db-stage-1/OPTIONS-000003

peek testdata/db-stage-2
----
initialized at format major version 013
manifest: testdata/db-stage-2/MANIFEST-000001
options: testdata/db-stage-2/OPTIONS-000003

peek testdata/db-stage-3
----
initialized at format major version 013
manifest: testdata/db-stage-3/MANIFEST-000006
options: testdata/db-stage-3/OPTIONS-000007

peek testdata/db-stage-4
----
initialized at format major version 013
manifest: testdata/db-stage-4/MANIFEST-000006
options: testdata/db-stage-4/OPTIONS-000007

peek testdata/db-stage-5
----
err=open testdata/db-stage-5: no such file or directory

peek testdata
----
uninitialized
4 changes: 2 additions & 2 deletions version_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -1070,11 +1070,11 @@ func (vs *versionSet) updateObsoleteTableMetricsLocked() {
}

func findCurrentManifest(
fs vfs.FS, dirname string,
fs vfs.FS, dirname string, ls []string,
) (marker *atomicfs.Marker, manifestNum base.DiskFileNum, exists bool, err error) {
// Locating a marker should succeed even if the marker has never been placed.
var filename string
marker, filename, err = atomicfs.LocateMarker(fs, dirname, manifestMarkerName)
marker, filename, err = atomicfs.LocateMarkerInListing(fs, dirname, manifestMarkerName, ls)
if err != nil {
return nil, 0, false, err
}
Expand Down
27 changes: 20 additions & 7 deletions vfs/atomicfs/marker.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ import (
// current value of the marker. Callers that may need to move the marker
// to a new value should use LocateMarker.
func ReadMarker(fs vfs.FS, dir, markerName string) (string, error) {
state, err := scanForMarker(fs, dir, markerName)
ls, err := fs.List(dir)
if err != nil {
return "", err
}
state, err := scanForMarker(fs, ls, markerName)
if err != nil {
return "", err
}
Expand All @@ -29,7 +33,20 @@ func ReadMarker(fs vfs.FS, dir, markerName string) (string, error) {
// to the Marker that may be used to move the marker and the
// current value of the marker.
func LocateMarker(fs vfs.FS, dir, markerName string) (*Marker, string, error) {
state, err := scanForMarker(fs, dir, markerName)
ls, err := fs.List(dir)
if err != nil {
return nil, "", err
}
return LocateMarkerInListing(fs, dir, markerName, ls)
}

// LocateMarkerInListing is like LocateMarker but takes a listing of the files
// contained within dir. It's useful when the caller has already listed the
// directory entries of dir for its own purposes.
func LocateMarkerInListing(
fs vfs.FS, dir, markerName string, ls []string,
) (*Marker, string, error) {
state, err := scanForMarker(fs, ls, markerName)
if err != nil {
return nil, "", err
}
Expand Down Expand Up @@ -57,11 +74,7 @@ type scannedState struct {
obsolete []string
}

func scanForMarker(fs vfs.FS, dir, markerName string) (scannedState, error) {
ls, err := fs.List(dir)
if err != nil {
return scannedState{}, err
}
func scanForMarker(fs vfs.FS, ls []string, markerName string) (scannedState, error) {
var state scannedState
for _, filename := range ls {
if !strings.HasPrefix(filename, `marker.`) {
Expand Down

0 comments on commit 13874c1

Please sign in to comment.