diff --git a/go.mod b/go.mod index 5d0a5270..7a310aee 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/fluxcd/pkg/oci v0.45.0 github.com/fluxcd/pkg/runtime v0.53.1 github.com/fluxcd/pkg/version v0.6.0 + github.com/go-logr/logr v1.4.2 github.com/google/go-containerregistry v0.20.3 github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20250115185438-c4dd792fa06c github.com/onsi/ginkgo v1.16.5 @@ -83,7 +84,6 @@ require ( github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-errors/errors v1.5.1 // indirect - github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect diff --git a/internal/database/badger_gc.go b/internal/database/badger_gc.go new file mode 100644 index 00000000..26143aad --- /dev/null +++ b/internal/database/badger_gc.go @@ -0,0 +1,93 @@ +/* +Copyright 2025 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package database + +import ( + "errors" + "sync" + "time" + + "github.com/dgraph-io/badger/v3" + "github.com/go-logr/logr" +) + +type BadgerGarbageCollector struct { + // settings + DiscardRatio float64 + Interval time.Duration + // external deps + db *badger.DB + log *logr.Logger + // flow control + timer *time.Timer + running sync.Mutex +} + +// NewBadgerGarbageCollector creates and returns a new +func NewBadgerGarbageCollector(db *badger.DB, interval time.Duration, log *logr.Logger) *BadgerGarbageCollector { + return &BadgerGarbageCollector{ + DiscardRatio: 0.5, // must be a float between 0.0 and 1.0, inclusive + Interval: interval, + + db: db, + log: log, + } +} + +// Start repeatedly runs the BadgerDB garbage collector with a delay inbetween +// runs. +// +// This is a non-blocking operation. +// To stop the garbage collector, call Stop(). +func (gc *BadgerGarbageCollector) Start() { + gc.log.Info("Starting Badger GC") + gc.timer = time.AfterFunc(gc.Interval, func() { + gc.running.Lock() + gc.discardValueLogFiles() + gc.running.Unlock() + gc.timer.Reset(gc.Interval) + }) +} + +// Stop blocks until the garbage collector has been stopped. +// +// To avoid GC Errors, call Stop() before closing the database. +func (gc *BadgerGarbageCollector) Stop() { + gc.log.Info("Sending stop to Badger GC") + gc.timer.Stop() + gc.running.Lock() + gc.running.Unlock() + gc.log.Info("Stopped Badger GC") +} + +// upper bound for loop +const maxDiscards = 1000 + +func (gc *BadgerGarbageCollector) discardValueLogFiles() { + for c := 0; c < maxDiscards; c++ { + err := gc.db.RunValueLogGC(gc.DiscardRatio) + if errors.Is(err, badger.ErrNoRewrite) { + // there is no more garbage to discard + gc.log.Info("Ran Badger GC", "discarded_vlogs", c) + return + } + if err != nil { + gc.log.Error(err, "Badger GC Error", "discarded_vlogs", c) + return + } + } + gc.log.Info("Ran Badger GC for maximum discards", "discarded_vlogs", maxDiscards) +} diff --git a/main.go b/main.go index da5a1044..04bf0bd8 100644 --- a/main.go +++ b/main.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "os" + "time" "github.com/dgraph-io/badger/v3" flag "github.com/spf13/pflag" @@ -60,6 +61,7 @@ const controllerName = "image-reflector-controller" var ( scheme = runtime.NewScheme() setupLog = ctrl.Log.WithName("setup") + gcLog = ctrl.Log.WithName("badger-gc") ) func init() { @@ -132,6 +134,11 @@ func main() { os.Exit(1) } defer badgerDB.Close() + + badgerGC := database.NewBadgerGarbageCollector(badgerDB, 1*time.Minute, &gcLog) + badgerGC.Start() + defer badgerGC.Stop() + db := database.NewBadgerDatabase(badgerDB) watchNamespace := ""