feat(boltdb): attempt to compact using a read-only database BE-12287 (#1268)

release/2.33
andres-portainer 2025-09-30 19:10:16 -03:00 committed by GitHub
parent 74b1dd04d1
commit fd6d74602c
2 changed files with 29 additions and 12 deletions

View File

@ -136,10 +136,8 @@ func (connection *DbConnection) NeedsEncryptionMigration() (bool, error) {
func (connection *DbConnection) Open() error { func (connection *DbConnection) Open() error {
log.Info().Str("filename", connection.GetDatabaseFileName()).Msg("loading PortainerDB") log.Info().Str("filename", connection.GetDatabaseFileName()).Msg("loading PortainerDB")
// Now we open the db
databasePath := connection.GetDatabaseFilePath() databasePath := connection.GetDatabaseFilePath()
db, err := bolt.Open(databasePath, 0600, connection.boltOptions(connection.Compact))
db, err := bolt.Open(databasePath, 0600, connection.boltOptions())
if err != nil { if err != nil {
return err return err
} }
@ -152,6 +150,15 @@ func (connection *DbConnection) Open() error {
log.Info().Msg("compacting database") log.Info().Msg("compacting database")
if err := connection.compact(); err != nil { if err := connection.compact(); err != nil {
log.Error().Err(err).Msg("failed to compact database") log.Error().Err(err).Msg("failed to compact database")
// Close the read-only database and re-open in read-write mode
if err := connection.Close(); err != nil {
log.Warn().Err(err).Msg("failure to close the database after failed compaction")
}
connection.Compact = false
return connection.Open()
} else { } else {
log.Info().Msg("database compaction completed") log.Info().Msg("database compaction completed")
} }
@ -424,9 +431,14 @@ func (connection *DbConnection) RestoreMetadata(s map[string]any) error {
} }
// compact attempts to compact the database and replace it iff it succeeds // compact attempts to compact the database and replace it iff it succeeds
func (connection *DbConnection) compact() error { func (connection *DbConnection) compact() (err error) {
compactedPath := connection.GetDatabaseFilePath() + compactedSuffix compactedPath := connection.GetDatabaseFilePath() + compactedSuffix
compactedDB, err := bolt.Open(compactedPath, 0o600, connection.boltOptions())
if err := os.Remove(compactedPath); err != nil && !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("failure to remove an existing compacted database: %w", err)
}
compactedDB, err := bolt.Open(compactedPath, 0o600, connection.boltOptions(false))
if err != nil { if err != nil {
return fmt.Errorf("failure to create the compacted database: %w", err) return fmt.Errorf("failure to create the compacted database: %w", err)
} }
@ -453,11 +465,12 @@ func (connection *DbConnection) compact() error {
return nil return nil
} }
func (connection *DbConnection) boltOptions() *bolt.Options { func (connection *DbConnection) boltOptions(readOnly bool) *bolt.Options {
return &bolt.Options{ return &bolt.Options{
Timeout: 1 * time.Second, Timeout: 1 * time.Second,
InitialMmapSize: connection.InitialMmapSize, InitialMmapSize: connection.InitialMmapSize,
FreelistType: bolt.FreelistMapType, FreelistType: bolt.FreelistMapType,
NoFreelistSync: true, NoFreelistSync: true,
ReadOnly: readOnly,
} }
} }

View File

@ -5,6 +5,8 @@ import (
"path" "path"
"testing" "testing"
"github.com/portainer/portainer/api/filesystem"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.etcd.io/bbolt" "go.etcd.io/bbolt"
@ -123,10 +125,7 @@ func Test_NeedsEncryptionMigration(t *testing.T) {
} }
func TestDBCompaction(t *testing.T) { func TestDBCompaction(t *testing.T) {
db := &DbConnection{ db := &DbConnection{Path: t.TempDir()}
Path: t.TempDir(),
Compact: true,
}
err := db.Open() err := db.Open()
require.NoError(t, err) require.NoError(t, err)
@ -147,6 +146,7 @@ func TestDBCompaction(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
// Reopen the DB to trigger compaction // Reopen the DB to trigger compaction
db.Compact = true
err = db.Open() err = db.Open()
require.NoError(t, err) require.NoError(t, err)
@ -168,10 +168,14 @@ func TestDBCompaction(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
// Failures // Failures
compactedPath := db.GetDatabaseFilePath() + compactedSuffix
err = os.Mkdir(db.GetDatabaseFilePath()+compactedSuffix, 0o755) err = os.Mkdir(compactedPath, 0o755)
require.NoError(t, err) require.NoError(t, err)
f, err := os.Create(filesystem.JoinPaths(compactedPath, "somefile"))
require.NoError(t, err)
require.NoError(t, f.Close())
err = db.Open() err = db.Open()
require.NoError(t, err) require.NoError(t, err)
} }