github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/helper/boltdd/boltdd.go (about) 1 // Package boltdd contains a wrapper around BBoltDB to deduplicate writes and encode 2 // values using mgspack. (dd stands for de-duplicate) 3 package boltdd 4 5 import ( 6 "bytes" 7 "fmt" 8 "os" 9 "sync" 10 11 "github.com/hashicorp/go-msgpack/codec" 12 "github.com/hashicorp/nomad/nomad/structs" 13 "go.etcd.io/bbolt" 14 "golang.org/x/crypto/blake2b" 15 ) 16 17 // ErrNotFound is returned when a key is not found. 18 type ErrNotFound struct { 19 name string 20 } 21 22 func (e *ErrNotFound) Error() string { 23 return fmt.Sprintf("key not found: %s", e.name) 24 } 25 26 // NotFound returns a new error for a key that was not found. 27 func NotFound(name string) error { 28 return &ErrNotFound{name} 29 } 30 31 // IsErrNotFound returns true if the error is an ErrNotFound error. 32 func IsErrNotFound(e error) bool { 33 if e == nil { 34 return false 35 } 36 _, ok := e.(*ErrNotFound) 37 return ok 38 } 39 40 // DB wraps an underlying bolt.DB to create write de-duplicating buckets and 41 // msgpack encoded values. 42 type DB struct { 43 rootBuckets map[string]*bucketMeta 44 rootBucketsLock sync.Mutex 45 46 boltDB *bbolt.DB 47 } 48 49 // Open a bolt.DB and wrap it in a write-de-duplicating msgpack-encoding 50 // implementation. 51 func Open(path string, mode os.FileMode, options *bbolt.Options) (*DB, error) { 52 bdb, err := bbolt.Open(path, mode, options) 53 if err != nil { 54 return nil, err 55 } 56 57 return New(bdb), nil 58 } 59 60 // New de-duplicating wrapper for the given bboltdb. 61 func New(bdb *bbolt.DB) *DB { 62 return &DB{ 63 rootBuckets: make(map[string]*bucketMeta), 64 boltDB: bdb, 65 } 66 } 67 68 func (db *DB) bucket(btx *bbolt.Tx, name []byte) *Bucket { 69 bb := btx.Bucket(name) 70 if bb == nil { 71 return nil 72 } 73 74 db.rootBucketsLock.Lock() 75 defer db.rootBucketsLock.Unlock() 76 77 if db.isClosed() { 78 return nil 79 } 80 81 b, ok := db.rootBuckets[string(name)] 82 if !ok { 83 b = newBucketMeta() 84 db.rootBuckets[string(name)] = b 85 } 86 87 return newBucket(b, bb) 88 } 89 90 func (db *DB) createBucket(btx *bbolt.Tx, name []byte) (*Bucket, error) { 91 bb, err := btx.CreateBucket(name) 92 if err != nil { 93 return nil, err 94 } 95 96 db.rootBucketsLock.Lock() 97 defer db.rootBucketsLock.Unlock() 98 99 // While creating a bucket on a closed db would error, we must recheck 100 // after acquiring the lock to avoid races. 101 if db.isClosed() { 102 return nil, bbolt.ErrDatabaseNotOpen 103 } 104 105 // Always create a new Bucket since CreateBucket above fails if the 106 // bucket already exists. 107 b := newBucketMeta() 108 db.rootBuckets[string(name)] = b 109 110 return newBucket(b, bb), nil 111 } 112 113 func (db *DB) createBucketIfNotExists(btx *bbolt.Tx, name []byte) (*Bucket, error) { 114 bb, err := btx.CreateBucketIfNotExists(name) 115 if err != nil { 116 return nil, err 117 } 118 119 db.rootBucketsLock.Lock() 120 defer db.rootBucketsLock.Unlock() 121 122 // While creating a bucket on a closed db would error, we must recheck 123 // after acquiring the lock to avoid races. 124 if db.isClosed() { 125 return nil, bbolt.ErrDatabaseNotOpen 126 } 127 128 b, ok := db.rootBuckets[string(name)] 129 if !ok { 130 b = newBucketMeta() 131 db.rootBuckets[string(name)] = b 132 } 133 134 return newBucket(b, bb), nil 135 } 136 137 func (db *DB) Update(fn func(*Tx) error) error { 138 return db.boltDB.Update(func(btx *bbolt.Tx) error { 139 tx := newTx(db, btx) 140 return fn(tx) 141 }) 142 } 143 144 func (db *DB) Batch(fn func(*Tx) error) error { 145 return db.boltDB.Batch(func(btx *bbolt.Tx) error { 146 tx := newTx(db, btx) 147 return fn(tx) 148 }) 149 } 150 151 func (db *DB) View(fn func(*Tx) error) error { 152 return db.boltDB.View(func(btx *bbolt.Tx) error { 153 tx := newTx(db, btx) 154 return fn(tx) 155 }) 156 } 157 158 // isClosed returns true if the database is closed and must be called while 159 // db.rootBucketsLock is acquired. 160 func (db *DB) isClosed() bool { 161 return db.rootBuckets == nil 162 } 163 164 // Close closes the underlying bolt.DB and clears all bucket hashes. DB is 165 // unusable after closing. 166 func (db *DB) Close() error { 167 db.rootBucketsLock.Lock() 168 db.rootBuckets = nil 169 db.rootBucketsLock.Unlock() 170 return db.boltDB.Close() 171 } 172 173 // BoltDB returns the underlying bolt.DB. 174 func (db *DB) BoltDB() *bbolt.DB { 175 return db.boltDB 176 } 177 178 type Tx struct { 179 db *DB 180 btx *bbolt.Tx 181 } 182 183 func newTx(db *DB, btx *bbolt.Tx) *Tx { 184 return &Tx{ 185 db: db, 186 btx: btx, 187 } 188 } 189 190 // Bucket returns a root bucket or nil if it doesn't exist. 191 func (tx *Tx) Bucket(name []byte) *Bucket { 192 return tx.db.bucket(tx.btx, name) 193 } 194 195 func (tx *Tx) CreateBucket(name []byte) (*Bucket, error) { 196 return tx.db.createBucket(tx.btx, name) 197 } 198 199 // CreateBucketIfNotExists returns a root bucket or creates a new one if it 200 // doesn't already exist. 201 func (tx *Tx) CreateBucketIfNotExists(name []byte) (*Bucket, error) { 202 return tx.db.createBucketIfNotExists(tx.btx, name) 203 } 204 205 // Writable wraps boltdb Tx.Writable. 206 func (tx *Tx) Writable() bool { 207 return tx.btx.Writable() 208 } 209 210 // BoltTx returns the underlying bolt.Tx. 211 func (tx *Tx) BoltTx() *bbolt.Tx { 212 return tx.btx 213 } 214 215 // bucketMeta persists metadata -- such as key hashes and child buckets -- 216 // about boltdb Buckets across transactions. 217 type bucketMeta struct { 218 // hashes holds all of the value hashes for keys in this bucket 219 hashes map[string][]byte 220 hashesLock sync.Mutex 221 222 // buckets holds all of the child buckets 223 buckets map[string]*bucketMeta 224 bucketsLock sync.Mutex 225 } 226 227 func newBucketMeta() *bucketMeta { 228 return &bucketMeta{ 229 hashes: make(map[string][]byte), 230 buckets: make(map[string]*bucketMeta), 231 } 232 } 233 234 // getHash of last value written to a key or nil if no hash exists. 235 func (bm *bucketMeta) getHash(hashKey string) []byte { 236 bm.hashesLock.Lock() 237 lastHash := bm.hashes[hashKey] 238 bm.hashesLock.Unlock() 239 return lastHash 240 } 241 242 // setHash of last value written to key. 243 func (bm *bucketMeta) setHash(hashKey string, hashVal []byte) { 244 bm.hashesLock.Lock() 245 bm.hashes[hashKey] = hashVal 246 bm.hashesLock.Unlock() 247 } 248 249 // delHash deletes a hash value or does nothing if the hash key does not exist. 250 func (bm *bucketMeta) delHash(hashKey string) { 251 bm.hashesLock.Lock() 252 delete(bm.hashes, hashKey) 253 bm.hashesLock.Unlock() 254 } 255 256 // createBucket metadata entry for the given nested bucket. Overwrites any 257 // existing entry so caller should ensure bucket does not already exist. 258 func (bm *bucketMeta) createBucket(name []byte) *bucketMeta { 259 bm.bucketsLock.Lock() 260 defer bm.bucketsLock.Unlock() 261 262 // Always create a new Bucket since CreateBucket above fails if the 263 // bucket already exists. 264 b := newBucketMeta() 265 bm.buckets[string(name)] = b 266 return b 267 } 268 269 // deleteBucket metadata entry for the given nested bucket. Does nothing if 270 // nested bucket metadata does not exist. 271 func (bm *bucketMeta) deleteBucket(name []byte) { 272 bm.bucketsLock.Lock() 273 delete(bm.buckets, string(name)) 274 bm.bucketsLock.Unlock() 275 276 } 277 278 // getOrCreateBucket metadata entry for the given nested bucket. 279 func (bm *bucketMeta) getOrCreateBucket(name []byte) *bucketMeta { 280 bm.bucketsLock.Lock() 281 defer bm.bucketsLock.Unlock() 282 283 b, ok := bm.buckets[string(name)] 284 if !ok { 285 b = newBucketMeta() 286 bm.buckets[string(name)] = b 287 } 288 return b 289 } 290 291 type Bucket struct { 292 bm *bucketMeta 293 boltBucket *bbolt.Bucket 294 } 295 296 // newBucket creates a new view into a bucket backed by a boltdb 297 // transaction. 298 func newBucket(b *bucketMeta, bb *bbolt.Bucket) *Bucket { 299 return &Bucket{ 300 bm: b, 301 boltBucket: bb, 302 } 303 } 304 305 // Put into boltdb iff it has changed since the last write. 306 func (b *Bucket) Put(key []byte, val interface{}) error { 307 // buffer for writing serialized state to 308 var buf bytes.Buffer 309 310 // Serialize the object 311 if err := codec.NewEncoder(&buf, structs.MsgpackHandle).Encode(val); err != nil { 312 return fmt.Errorf("failed to encode passed object: %v", err) 313 } 314 315 // Hash for skipping unnecessary writes 316 hashKey := string(key) 317 hashVal := blake2b.Sum256(buf.Bytes()) 318 319 // lastHash value or nil if it hasn't been hashed yet 320 lastHash := b.bm.getHash(hashKey) 321 322 // If the hashes are equal, skip the write 323 if bytes.Equal(hashVal[:], lastHash) { 324 return nil 325 } 326 327 // New value: write it to the underlying boltdb 328 if err := b.boltBucket.Put(key, buf.Bytes()); err != nil { 329 return fmt.Errorf("failed to write data at key %s: %v", key, err) 330 } 331 332 // New value written, store hash (bucket path map was created above) 333 b.bm.setHash(hashKey, hashVal[:]) 334 335 return nil 336 337 } 338 339 // Get value by key from boltdb or return an ErrNotFound error if key not 340 // found. 341 func (b *Bucket) Get(key []byte, obj interface{}) error { 342 // Get the raw data from the underlying boltdb 343 data := b.boltBucket.Get(key) 344 if data == nil { 345 return NotFound(string(key)) 346 } 347 348 // Deserialize the object 349 if err := codec.NewDecoderBytes(data, structs.MsgpackHandle).Decode(obj); err != nil { 350 return fmt.Errorf("failed to decode data into passed object: %v", err) 351 } 352 353 return nil 354 } 355 356 // Iterate iterates each key in Bucket b that starts with prefix. fn is called on 357 // the key and msg-pack decoded value. If prefix is empty or nil, all keys in the 358 // bucket are iterated. 359 // 360 // b must already exist. 361 func Iterate[T any](b *Bucket, prefix []byte, fn func([]byte, T)) error { 362 c := b.boltBucket.Cursor() 363 for k, data := c.Seek(prefix); k != nil && bytes.HasPrefix(k, prefix); k, data = c.Next() { 364 var obj T 365 if err := codec.NewDecoderBytes(data, structs.MsgpackHandle).Decode(&obj); err != nil { 366 return fmt.Errorf("failed to decode data into passed object: %v", err) 367 } 368 fn(k, obj) 369 } 370 return nil 371 } 372 373 // DeletePrefix removes all keys starting with prefix from the bucket. If no keys 374 // with prefix exist then nothing is done and a nil error is returned. Returns an 375 // error if the bucket was created from a read-only transaction. 376 // 377 // b must already exist. 378 func (b *Bucket) DeletePrefix(prefix []byte) error { 379 c := b.boltBucket.Cursor() 380 for k, _ := c.Seek(prefix); k != nil && bytes.HasPrefix(k, prefix); k, _ = c.Next() { 381 if err := c.Delete(); err != nil { 382 return err 383 } 384 b.bm.delHash(string(k)) 385 } 386 return nil 387 } 388 389 // Delete removes a key from the bucket. If the key does not exist then nothing 390 // is done and a nil error is returned. Returns an error if the bucket was 391 // created from a read-only transaction. 392 func (b *Bucket) Delete(key []byte) error { 393 err := b.boltBucket.Delete(key) 394 b.bm.delHash(string(key)) 395 return err 396 } 397 398 // Bucket represents a boltdb Bucket and its associated metadata necessary for 399 // write deduplication. Like bolt.Buckets it is only valid for the duration of 400 // the transaction that created it. 401 func (b *Bucket) Bucket(name []byte) *Bucket { 402 bb := b.boltBucket.Bucket(name) 403 if bb == nil { 404 return nil 405 } 406 407 bmeta := b.bm.getOrCreateBucket(name) 408 return newBucket(bmeta, bb) 409 } 410 411 // CreateBucket creates a new bucket at the given key and returns the new 412 // bucket. Returns an error if the key already exists, if the bucket name is 413 // blank, or if the bucket name is too long. The bucket instance is only valid 414 // for the lifetime of the transaction. 415 func (b *Bucket) CreateBucket(name []byte) (*Bucket, error) { 416 bb, err := b.boltBucket.CreateBucket(name) 417 if err != nil { 418 return nil, err 419 } 420 421 bmeta := b.bm.createBucket(name) 422 return newBucket(bmeta, bb), nil 423 } 424 425 // CreateBucketIfNotExists creates a new bucket if it doesn't already exist and 426 // returns a reference to it. The bucket instance is only valid for the 427 // lifetime of the transaction. 428 func (b *Bucket) CreateBucketIfNotExists(name []byte) (*Bucket, error) { 429 bb, err := b.boltBucket.CreateBucketIfNotExists(name) 430 if err != nil { 431 return nil, err 432 } 433 434 bmeta := b.bm.getOrCreateBucket(name) 435 return newBucket(bmeta, bb), nil 436 } 437 438 // DeleteBucket deletes a child bucket. Returns an error if the bucket 439 // corresponds to a non-bucket key or another error is encountered. No error is 440 // returned if the bucket does not exist. 441 func (b *Bucket) DeleteBucket(name []byte) error { 442 // Delete the bucket from the underlying boltdb 443 err := b.boltBucket.DeleteBucket(name) 444 if err == bbolt.ErrBucketNotFound { 445 err = nil 446 } 447 448 // Remove reference to child bucket 449 b.bm.deleteBucket(name) 450 return err 451 } 452 453 // BoltBucket returns the internal bolt.Bucket for this Bucket. Only valid 454 // for the duration of the current transaction. 455 func (b *Bucket) BoltBucket() *bbolt.Bucket { 456 return b.boltBucket 457 }