github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/db/kv/kv.go (about) 1 // Package kv defines a bolt-db, key-value store implementation 2 // of the Database interface defined by a Prysm beacon node. 3 package kv 4 5 import ( 6 "context" 7 "os" 8 "path" 9 "time" 10 11 "github.com/dgraph-io/ristretto" 12 "github.com/pkg/errors" 13 "github.com/prometheus/client_golang/prometheus" 14 prombolt "github.com/prysmaticlabs/prombbolt" 15 "github.com/prysmaticlabs/prysm/beacon-chain/db/iface" 16 "github.com/prysmaticlabs/prysm/shared/fileutil" 17 "github.com/prysmaticlabs/prysm/shared/params" 18 bolt "go.etcd.io/bbolt" 19 ) 20 21 var _ iface.Database = (*Store)(nil) 22 23 const ( 24 // VotesCacheSize with 1M validators will be 8MB. 25 VotesCacheSize = 1 << 23 26 // NumOfVotes specifies the vote cache size. 27 NumOfVotes = 1 << 20 28 // BeaconNodeDbDirName is the name of the directory containing the beacon node database. 29 BeaconNodeDbDirName = "beaconchaindata" 30 // DatabaseFileName is the name of the beacon node database. 31 DatabaseFileName = "beaconchain.db" 32 33 boltAllocSize = 8 * 1024 * 1024 34 ) 35 36 // BlockCacheSize specifies 1000 slots worth of blocks cached, which 37 // would be approximately 2MB 38 var BlockCacheSize = int64(1 << 21) 39 40 // blockedBuckets represents the buckets that we want to restrict 41 // from our metrics fetching for performance reasons. For a detailed 42 // summary, it can be read in https://github.com/prysmaticlabs/prysm/issues/8274. 43 var blockedBuckets = [][]byte{ 44 blocksBucket, 45 stateSummaryBucket, 46 blockParentRootIndicesBucket, 47 blockSlotIndicesBucket, 48 finalizedBlockRootsIndexBucket, 49 } 50 51 // Config for the bolt db kv store. 52 type Config struct { 53 InitialMMapSize int 54 } 55 56 // Store defines an implementation of the Prysm Database interface 57 // using BoltDB as the underlying persistent kv-store for Ethereum Beacon Nodes. 58 type Store struct { 59 db *bolt.DB 60 databasePath string 61 blockCache *ristretto.Cache 62 validatorIndexCache *ristretto.Cache 63 stateSummaryCache *stateSummaryCache 64 ctx context.Context 65 } 66 67 // KVStoreDatafilePath is the canonical construction of a full 68 // database file path from the directory path, so that code outside 69 // this package can find the full path in a consistent way. 70 func KVStoreDatafilePath(dirPath string) string { 71 return path.Join(dirPath, DatabaseFileName) 72 } 73 74 // NewKVStore initializes a new boltDB key-value store at the directory 75 // path specified, creates the kv-buckets based on the schema, and stores 76 // an open connection db object as a property of the Store struct. 77 func NewKVStore(ctx context.Context, dirPath string, config *Config) (*Store, error) { 78 hasDir, err := fileutil.HasDir(dirPath) 79 if err != nil { 80 return nil, err 81 } 82 if !hasDir { 83 if err := fileutil.MkdirAll(dirPath); err != nil { 84 return nil, err 85 } 86 } 87 datafile := KVStoreDatafilePath(dirPath) 88 boltDB, err := bolt.Open( 89 datafile, 90 params.BeaconIoConfig().ReadWritePermissions, 91 &bolt.Options{ 92 Timeout: 1 * time.Second, 93 InitialMmapSize: config.InitialMMapSize, 94 }, 95 ) 96 if err != nil { 97 if errors.Is(err, bolt.ErrTimeout) { 98 return nil, errors.New("cannot obtain database lock, database may be in use by another process") 99 } 100 return nil, err 101 } 102 boltDB.AllocSize = boltAllocSize 103 blockCache, err := ristretto.NewCache(&ristretto.Config{ 104 NumCounters: 1000, // number of keys to track frequency of (1000). 105 MaxCost: BlockCacheSize, // maximum cost of cache (1000 Blocks). 106 BufferItems: 64, // number of keys per Get buffer. 107 }) 108 if err != nil { 109 return nil, err 110 } 111 112 validatorCache, err := ristretto.NewCache(&ristretto.Config{ 113 NumCounters: NumOfVotes, // number of keys to track frequency of (1M). 114 MaxCost: VotesCacheSize, // maximum cost of cache (8MB). 115 BufferItems: 64, // number of keys per Get buffer. 116 }) 117 if err != nil { 118 return nil, err 119 } 120 121 kv := &Store{ 122 db: boltDB, 123 databasePath: dirPath, 124 blockCache: blockCache, 125 validatorIndexCache: validatorCache, 126 stateSummaryCache: newStateSummaryCache(), 127 ctx: ctx, 128 } 129 130 if err := kv.db.Update(func(tx *bolt.Tx) error { 131 return createBuckets( 132 tx, 133 attestationsBucket, 134 blocksBucket, 135 stateBucket, 136 proposerSlashingsBucket, 137 attesterSlashingsBucket, 138 voluntaryExitsBucket, 139 chainMetadataBucket, 140 checkpointBucket, 141 powchainBucket, 142 stateSummaryBucket, 143 // Indices buckets. 144 attestationHeadBlockRootBucket, 145 attestationSourceRootIndicesBucket, 146 attestationSourceEpochIndicesBucket, 147 attestationTargetRootIndicesBucket, 148 attestationTargetEpochIndicesBucket, 149 blockSlotIndicesBucket, 150 stateSlotIndicesBucket, 151 blockParentRootIndicesBucket, 152 finalizedBlockRootsIndexBucket, 153 // State management service bucket. 154 newStateServiceCompatibleBucket, 155 // Migrations 156 migrationsBucket, 157 ) 158 }); err != nil { 159 return nil, err 160 } 161 162 err = prometheus.Register(createBoltCollector(kv.db)) 163 164 return kv, err 165 } 166 167 // ClearDB removes the previously stored database in the data directory. 168 func (s *Store) ClearDB() error { 169 if _, err := os.Stat(s.databasePath); os.IsNotExist(err) { 170 return nil 171 } 172 prometheus.Unregister(createBoltCollector(s.db)) 173 if err := os.Remove(path.Join(s.databasePath, DatabaseFileName)); err != nil { 174 return errors.Wrap(err, "could not remove database file") 175 } 176 return nil 177 } 178 179 // Close closes the underlying BoltDB database. 180 func (s *Store) Close() error { 181 prometheus.Unregister(createBoltCollector(s.db)) 182 183 // Before DB closes, we should dump the cached state summary objects to DB. 184 if err := s.saveCachedStateSummariesDB(s.ctx); err != nil { 185 return err 186 } 187 188 return s.db.Close() 189 } 190 191 // DatabasePath at which this database writes files. 192 func (s *Store) DatabasePath() string { 193 return s.databasePath 194 } 195 196 func createBuckets(tx *bolt.Tx, buckets ...[]byte) error { 197 for _, bucket := range buckets { 198 if _, err := tx.CreateBucketIfNotExists(bucket); err != nil { 199 return err 200 } 201 } 202 return nil 203 } 204 205 // createBoltCollector returns a prometheus collector specifically configured for boltdb. 206 func createBoltCollector(db *bolt.DB) prometheus.Collector { 207 return prombolt.New("boltDB", db, blockedBuckets...) 208 }