@@ -5,16 +5,22 @@ import (
55 "context"
66 "errors"
77 "fmt"
8+ "io/fs"
89 "os"
910 "path/filepath"
11+ "strings"
1012 "time"
1113
14+ badgerDB "github.com/dgraph-io/badger/v4"
1215 "github.com/spf13/cobra"
1316
1417 "github.com/oasisprotocol/oasis-core/go/common"
1518 "github.com/oasisprotocol/oasis-core/go/common/crypto/hash"
1619 "github.com/oasisprotocol/oasis-core/go/common/logging"
1720 "github.com/oasisprotocol/oasis-core/go/config"
21+ "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/abci"
22+ cmtCommon "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/common"
23+ cmtDBProvider "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/db/badger"
1824 cmdCommon "github.com/oasisprotocol/oasis-core/go/oasis-node/cmd/common"
1925 roothash "github.com/oasisprotocol/oasis-core/go/roothash/api"
2026 "github.com/oasisprotocol/oasis-core/go/runtime/bundle"
5359 RunE : doRenameNs ,
5460 }
5561
62+ storageCompactCmd = & cobra.Command {
63+ Use : "compact-experimental" ,
64+ Args : cobra .NoArgs ,
65+ Short : "EXPERIMENTAL: trigger compaction for all consensus databases" ,
66+ Long : `EXPERIMENTAL: Optimize the storage for all consensus databases by manually compacting the underlying storage engines.
67+
68+ WARNING: Ensure you have at least as much of a free disk as your largest database.
69+ ` ,
70+ RunE : doDBCompactions ,
71+ }
72+
5673 logger = logging .GetLogger ("cmd/storage" )
5774
5875 pretty = cmdCommon .Isatty (1 )
@@ -283,12 +300,119 @@ func doRenameNs(_ *cobra.Command, args []string) error {
283300 return nil
284301}
285302
303+ func doDBCompactions (_ * cobra.Command , args []string ) error {
304+ if err := cmdCommon .Init (); err != nil {
305+ cmdCommon .EarlyLogAndExit (err )
306+ }
307+
308+ dataDir := cmdCommon .DataDir ()
309+
310+ logger .Info ("Starting database compactions. This may take a while..." )
311+
312+ // Compact CometBFT managed databases: block store, evidence and state (NOT application state).
313+ if err := compactCometDBs (dataDir ); err != nil {
314+ return fmt .Errorf ("failed to compact CometBFT managed databases: %w" , err )
315+ }
316+
317+ if err := compactConsensusNodeDB (dataDir ); err != nil {
318+ return fmt .Errorf ("failed to compact consensus NodeDB: %w" , err )
319+ }
320+
321+ return nil
322+ }
323+
324+ func compactCometDBs (dataDir string ) error {
325+ paths , err := findCometDBs (dataDir )
326+ if err != nil {
327+ return fmt .Errorf ("failed to find database instances: %w" , err )
328+ }
329+ for _ , path := range paths {
330+ if err := compactCometDB (path ); err != nil {
331+ return fmt .Errorf ("failed to compact %s: %w" , path , err )
332+ }
333+ }
334+ return nil
335+ }
336+
337+ func compactCometDB (path string ) error {
338+ logger := logger .With ("path" , path )
339+ db , err := cmtDBProvider .OpenBadger (path , logger )
340+ if err != nil {
341+ return fmt .Errorf ("failed to open BadgerDB: %w" , err )
342+ }
343+
344+ if err := flattenBadgerDB (db , logger ); err != nil {
345+ return fmt .Errorf ("failed to compact %s: %w" , path , err )
346+ }
347+
348+ return nil
349+ }
350+
351+ func findCometDBs (dataDir string ) ([]string , error ) {
352+ dir := fmt .Sprintf ("%s/consensus/data" , dataDir )
353+
354+ var dbDirs []string
355+ err := filepath .WalkDir (dir , func (path string , d fs.DirEntry , err error ) error {
356+ if err != nil {
357+ return err
358+ }
359+ if d .IsDir () && strings .HasSuffix (d .Name (), ".db" ) {
360+ dbDirs = append (dbDirs , path )
361+ }
362+ return nil
363+ })
364+ if err != nil {
365+ return nil , fmt .Errorf ("failed to walk dir %s: %w" , dataDir , err )
366+ }
367+
368+ if len (dbDirs ) == 0 {
369+ return nil , fmt .Errorf ("zero database instances found" )
370+ }
371+
372+ return dbDirs , nil
373+ }
374+
375+ func flattenBadgerDB (db * badgerDB.DB , logger * logging.Logger ) error {
376+ logger .Info ("compacting" )
377+
378+ if err := db .Flatten (1 ); err != nil {
379+ return fmt .Errorf ("failed to flatten db: %w" , err )
380+ }
381+
382+ logger .Info ("compaction completed" )
383+
384+ return nil
385+ }
386+
387+ func compactConsensusNodeDB (dataDir string ) error {
388+ ldb , ndb , _ , err := abci .InitStateStorage (
389+ & abci.ApplicationConfig {
390+ DataDir : filepath .Join (dataDir , cmtCommon .StateDir ),
391+ StorageBackend : config .GlobalConfig .Storage .Backend ,
392+ MemoryOnlyStorage : false ,
393+ ReadOnlyStorage : false ,
394+ DisableCheckpointer : true ,
395+ },
396+ )
397+ if err != nil {
398+ return fmt .Errorf ("failed to initialize ABCI storage backend: %w" , err )
399+ }
400+
401+ // Close the resources. Both Close and Cleanup only close NodeDB.
402+ // Closing both here, to prevent resource leaks if things change in the future.
403+ defer ndb .Close ()
404+ defer ldb .Cleanup ()
405+
406+ return ndb .Compact ()
407+ }
408+
286409// Register registers the client sub-command and all of its children.
287410func Register (parentCmd * cobra.Command ) {
288411 storageMigrateCmd .Flags ().AddFlagSet (bundle .Flags )
289412 storageCheckCmd .Flags ().AddFlagSet (bundle .Flags )
290413 storageCmd .AddCommand (storageMigrateCmd )
291414 storageCmd .AddCommand (storageCheckCmd )
292415 storageCmd .AddCommand (storageRenameNsCmd )
416+ storageCmd .AddCommand (storageCompactCmd )
293417 parentCmd .AddCommand (storageCmd )
294418}
0 commit comments