Skip to content

Commit

Permalink
Merge pull request #5876 from oasisprotocol/kostko/feature/storage-de…
Browse files Browse the repository at this point in the history
…fault-pathbadger

go/storage: Add automatic storage backend detection
  • Loading branch information
kostko authored Oct 7, 2024
2 parents cc88fcd + b6d3503 commit 7978b58
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 4 deletions.
12 changes: 12 additions & 0 deletions .changelog/5876.cfg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
The pathbadger storage backend is now the default for new nodes

When a node is provisioned into an empty data directory it will default to
using the `pathbadger` storage backend.

For existing nodes, the storage backend is automatically detected based on
the data directory. When multiple storage directories exist, the one most
recently modified is used.

In case one does not want this new behavior, it is still possible to set
the `storage.backend` to `badger`/`pathbadger` to explicitly configure the
desired storage backend and disable autodetection.
8 changes: 8 additions & 0 deletions .changelog/5876.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
go/storage: Add automatic storage backend detection

The new default storage backend is "auto" which attempts to detect the
storage backend that should be used based on existing data directories.
When none exist, "pathbadger" is used. When multiple exist, the most
recently modified one is used.

This should make newly deployed nodes default to pathbadger.
2 changes: 1 addition & 1 deletion go/oasis-test-runner/oasis/oasis.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const (
defaultVRFInterval = 20
defaultVRFSubmissionDelay = 5

defaultStorageBackend = database.BackendNamePathBadger
defaultStorageBackend = database.BackendNameAuto

logNodeFile = "node.log"
logConsoleFile = "console.log"
Expand Down
69 changes: 69 additions & 0 deletions go/storage/database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"slices"
"time"

"github.com/oasisprotocol/oasis-core/go/storage/api"
"github.com/oasisprotocol/oasis-core/go/storage/mkvs/checkpoint"
Expand All @@ -14,11 +17,17 @@ import (
)

const (
// BackendNameAuto is the name of the automatic backend detection "backend".
BackendNameAuto = "auto"
// BackendNameBadgerDB is the name of the BadgerDB backed database backend.
BackendNameBadgerDB = "badger"
// BackendNamePathBadger is the name of the PathBadger database backend.
BackendNamePathBadger = "pathbadger"

// defaultBackendName is the default backend in case automatic backend detection is enabled and
// no previous backend exists.
defaultBackendName = BackendNamePathBadger

checkpointDir = "checkpoints"
)

Expand All @@ -39,6 +48,10 @@ type databaseBackend struct {

// New constructs a new database backed storage Backend instance.
func New(cfg *api.Config) (api.LocalBackend, error) {
if err := autoDetectBackend(cfg); err != nil {
return nil, err
}

ndb, err := db.New(cfg.Backend, cfg.ToNodeDB())
if err != nil {
return nil, fmt.Errorf("storage/database: failed to create node database: %w", err)
Expand Down Expand Up @@ -164,3 +177,59 @@ func (ba *databaseBackend) Checkpointer() checkpoint.CreateRestorer {
func (ba *databaseBackend) NodeDB() dbApi.NodeDB {
return ba.ndb
}

// autoDetectBackend attempts automatic backend detection, modifying the configuration in place.
func autoDetectBackend(cfg *api.Config) error {
if cfg.Backend != BackendNameAuto {
return nil
}

// Make sure that the DefaultFileName was used to derive the subdirectory. Otherwise automatic
// detection cannot be performed.
if filepath.Base(cfg.DB) != DefaultFileName(cfg.Backend) {
return fmt.Errorf("storage/database: 'auto' backend selected using a non-default path")
}

// Perform automatic database backend detection if selected. Detection will be based on existing
// database directories. If multiple directories are available, the most recently modified is
// selected.
type foundBackend struct {
path string
timestamp time.Time
name string
}
var backends []foundBackend

for _, b := range db.Backends {
// Generate expected filename for the given backend.
fn := DefaultFileName(b.Name())
maybeDb := filepath.Join(filepath.Dir(cfg.DB), fn)
fi, err := os.Stat(maybeDb)
if err != nil {
continue
}

backends = append(backends, foundBackend{
path: maybeDb,
timestamp: fi.ModTime(),
name: b.Name(),
})
}
slices.SortFunc(backends, func(a, b foundBackend) int {
return a.timestamp.Compare(b.timestamp)
})

// If no existing backends are available, use default.
if len(backends) == 0 {
cfg.Backend = defaultBackendName
cfg.DB = filepath.Join(filepath.Dir(cfg.DB), DefaultFileName(cfg.Backend))
return nil
}

// Otherwise, use the backend that has been updated most recently.
b := backends[len(backends)-1]
cfg.Backend = b.name
cfg.DB = b.path

return nil
}
89 changes: 89 additions & 0 deletions go/storage/database/database_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,92 @@ func doTestImpl(t *testing.T, backend string) {
genesisTestHelpers.SetTestChainContext()
tests.StorageImplementationTests(t, impl, impl, testNs, 0)
}

func TestAutoBackend(t *testing.T) {
t.Run("NoExistingDir", func(t *testing.T) {
require := require.New(t)

tmpDir, err := os.MkdirTemp("", "oasis-storage-database-test")
require.NoError(err, "TempDir()")
defer os.RemoveAll(tmpDir)

// When there is no existing database directory, the default should be used.
cfg := api.Config{
Backend: "auto",
DB: filepath.Join(tmpDir, DefaultFileName("auto")),
}
impl, err := New(&cfg)
require.NoError(err)
impl.Cleanup()

require.Equal(defaultBackendName, cfg.Backend)
require.Equal(filepath.Join(tmpDir, DefaultFileName(defaultBackendName)), cfg.DB)
})

t.Run("OneExistingDir", func(t *testing.T) {
require := require.New(t)

tmpDir, err := os.MkdirTemp("", "oasis-storage-database-test")
require.NoError(err, "TempDir()")
defer os.RemoveAll(tmpDir)

// Create a badger database first.
cfg := api.Config{
Backend: "badger",
DB: filepath.Join(tmpDir, DefaultFileName("badger")),
}
impl, err := New(&cfg)
require.NoError(err)
impl.Cleanup()

// When there is an existing backend, it should be used.
cfg = api.Config{
Backend: "auto",
DB: filepath.Join(tmpDir, DefaultFileName("auto")),
}
impl, err = New(&cfg)
require.NoError(err)
impl.Cleanup()

require.Equal("badger", cfg.Backend)
require.Equal(filepath.Join(tmpDir, DefaultFileName("badger")), cfg.DB)
})

t.Run("MultiExistingDirs", func(t *testing.T) {
require := require.New(t)

tmpDir, err := os.MkdirTemp("", "oasis-storage-database-test")
require.NoError(err, "TempDir()")
defer os.RemoveAll(tmpDir)

// Create a badger database first.
cfg := api.Config{
Backend: "badger",
DB: filepath.Join(tmpDir, DefaultFileName("badger")),
}
impl, err := New(&cfg)
require.NoError(err)
impl.Cleanup()

// Then create a pathbadger database.
cfg = api.Config{
Backend: "pathbadger",
DB: filepath.Join(tmpDir, DefaultFileName("pathbadger")),
}
impl, err = New(&cfg)
require.NoError(err)
impl.Cleanup()

// When there are multiple existing backends, the most recent one should be used.
cfg = api.Config{
Backend: "auto",
DB: filepath.Join(tmpDir, DefaultFileName("auto")),
}
impl, err = New(&cfg)
require.NoError(err)
impl.Cleanup()

require.Equal("pathbadger", cfg.Backend)
require.Equal(filepath.Join(tmpDir, DefaultFileName("pathbadger")), cfg.DB)
})
}
9 changes: 6 additions & 3 deletions go/worker/storage/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,17 @@ type CheckpointerConfig struct {

// Validate validates the configuration settings.
func (c *Config) Validate() error {
_, err := db.GetBackendByName(c.Backend)
return err
if c.Backend != "auto" {
_, err := db.GetBackendByName(c.Backend)
return err
}
return nil
}

// DefaultConfig returns the default configuration settings.
func DefaultConfig() Config {
return Config{
Backend: "badger",
Backend: "auto",
MaxCacheSize: "64mb",
FetcherCount: 4,
PublicRPCEnabled: false,
Expand Down

0 comments on commit 7978b58

Please sign in to comment.