|
| 1 | +package storage |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + "io/fs" |
| 6 | + "path/filepath" |
| 7 | + "strings" |
| 8 | + |
| 9 | + "github.com/spf13/cobra" |
| 10 | + |
| 11 | + "github.com/oasisprotocol/oasis-core/go/common" |
| 12 | + cmtDBProvider "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/db/badger" |
| 13 | + cmdCommon "github.com/oasisprotocol/oasis-core/go/oasis-node/cmd/common" |
| 14 | + "github.com/oasisprotocol/oasis-core/go/runtime/registry" |
| 15 | +) |
| 16 | + |
| 17 | +func newCompactCmd() *cobra.Command { |
| 18 | + cmd := &cobra.Command{ |
| 19 | + Use: "compact", |
| 20 | + Args: cobra.NoArgs, |
| 21 | + Short: "trigger compaction for all databases", |
| 22 | + Long: `Optimize the storage for all databases by manually compacting the underlying storage engines. |
| 23 | +
|
| 24 | +WARNING: Ensure you have at least as much of a free disk as your largest database. |
| 25 | +`, |
| 26 | + PreRunE: func(_ *cobra.Command, args []string) error { |
| 27 | + if err := cmdCommon.Init(); err != nil { |
| 28 | + cmdCommon.EarlyLogAndExit(err) |
| 29 | + } |
| 30 | + |
| 31 | + running, err := cmdCommon.IsNodeRunning() |
| 32 | + if err != nil { |
| 33 | + return fmt.Errorf("failed to ensure the node is not running: %w", err) |
| 34 | + } |
| 35 | + |
| 36 | + if running { |
| 37 | + return fmt.Errorf("compaction can only be done when the node is not running") |
| 38 | + } |
| 39 | + return nil |
| 40 | + }, |
| 41 | + RunE: func(cmd *cobra.Command, args []string) error { |
| 42 | + dataDir := cmdCommon.DataDir() |
| 43 | + |
| 44 | + logger.Info("Starting database compactions. This may take a while...") |
| 45 | + |
| 46 | + if err := compactConsensusDBs(dataDir); err != nil { |
| 47 | + return fmt.Errorf("failed to compact consensus databases: %w", err) |
| 48 | + } |
| 49 | + |
| 50 | + runtimes, err := registry.GetConfiguredRuntimeIDs() |
| 51 | + if err != nil { |
| 52 | + return fmt.Errorf("failed to get configured runtimes: %w", err) |
| 53 | + } |
| 54 | + for _, rt := range runtimes { |
| 55 | + if err := compactRuntimeDBs(dataDir, rt); err != nil { |
| 56 | + return fmt.Errorf("failed to compact runtime dbs (runtime ID: %s): %w", rt, err) |
| 57 | + } |
| 58 | + } |
| 59 | + |
| 60 | + return nil |
| 61 | + }, |
| 62 | + } |
| 63 | + |
| 64 | + return cmd |
| 65 | +} |
| 66 | + |
| 67 | +func compactConsensusDBs(dataDir string) error { |
| 68 | + // Compact CometBFT managed databases: block store, evidence and state (NOT application state). |
| 69 | + if err := compactCometDBs(dataDir); err != nil { |
| 70 | + return fmt.Errorf("failed to compact CometBFT managed databases: %w", err) |
| 71 | + } |
| 72 | + |
| 73 | + // Compact consensus NodeDB (application state). |
| 74 | + ndb, close, err := openConsensusNodeDB(dataDir) |
| 75 | + if err != nil { |
| 76 | + return fmt.Errorf("failed to open consensus NodeDB: %w", err) |
| 77 | + } |
| 78 | + defer close() |
| 79 | + |
| 80 | + if err := ndb.Compact(); err != nil { |
| 81 | + return fmt.Errorf("failed to compact consensus node DB: %w", err) |
| 82 | + } |
| 83 | + |
| 84 | + return nil |
| 85 | +} |
| 86 | + |
| 87 | +func compactCometDBs(dataDir string) error { |
| 88 | + dir := fmt.Sprintf("%s/consensus/data", dataDir) |
| 89 | + |
| 90 | + var dbDirs []string |
| 91 | + err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { |
| 92 | + if err != nil { |
| 93 | + return err |
| 94 | + } |
| 95 | + if d.IsDir() && strings.HasSuffix(d.Name(), ".db") { |
| 96 | + dbDirs = append(dbDirs, path) |
| 97 | + } |
| 98 | + return nil |
| 99 | + }) |
| 100 | + if err != nil { |
| 101 | + return fmt.Errorf("failed to walk dir %s: %w", dir, err) |
| 102 | + } |
| 103 | + |
| 104 | + if len(dbDirs) == 0 { |
| 105 | + return fmt.Errorf("zero database instances found") |
| 106 | + } |
| 107 | + |
| 108 | + for _, dbDir := range dbDirs { |
| 109 | + if err := compactCometDB(dbDir); err != nil { |
| 110 | + return fmt.Errorf("failed to compact %s: %w", dbDir, err) |
| 111 | + } |
| 112 | + } |
| 113 | + return nil |
| 114 | +} |
| 115 | + |
| 116 | +func compactCometDB(path string) error { |
| 117 | + logger := logger.With("path", path) |
| 118 | + db, err := cmtDBProvider.OpenBadger(path, logger) |
| 119 | + if err != nil { |
| 120 | + return fmt.Errorf("failed to open BadgerDB: %w", err) |
| 121 | + } |
| 122 | + defer db.Close() |
| 123 | + |
| 124 | + logger.Info("compacting") |
| 125 | + |
| 126 | + if err := db.Flatten(1); err != nil { |
| 127 | + return fmt.Errorf("failed to flatten db: %w", err) |
| 128 | + } |
| 129 | + |
| 130 | + logger.Info("compaction completed") |
| 131 | + |
| 132 | + return nil |
| 133 | +} |
| 134 | + |
| 135 | +func compactRuntimeDBs(dataDir string, rt common.Namespace) error { |
| 136 | + history, err := openRuntimeLightHistory(dataDir, rt) |
| 137 | + if err != nil { |
| 138 | + return fmt.Errorf("failed to open runtime history: %w", err) |
| 139 | + } |
| 140 | + defer history.Close() |
| 141 | + if err := history.Compact(); err != nil { |
| 142 | + return fmt.Errorf("failed to compact runtime history: %w", err) |
| 143 | + } |
| 144 | + ndb, err := openRuntimeStateDB(dataDir, rt) |
| 145 | + if err != nil { |
| 146 | + return fmt.Errorf("failed to open runtime state DB: %w", err) |
| 147 | + } |
| 148 | + defer ndb.Close() |
| 149 | + if err := ndb.Compact(); err != nil { |
| 150 | + return fmt.Errorf("failed to compact runtime state DB: %w", err) |
| 151 | + } |
| 152 | + return nil |
| 153 | +} |
0 commit comments