package writecache import ( "bytes" "context" "errors" "fmt" "io/fs" "os" "path/filepath" "time" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" "go.etcd.io/bbolt" ) const dbName = "small.bolt" var defaultBucket = []byte{0} func (c *cache) flushAndDropBBoltDB(ctx context.Context) error { _, err := os.Stat(filepath.Join(c.path, dbName)) if err != nil && os.IsNotExist(err) { return nil } if err != nil { return fmt.Errorf("could not check write-cache database existence: %w", err) } db, err := OpenDB(c.path, true, os.OpenFile) if err != nil { return fmt.Errorf("could not open write-cache database: %w", err) } defer func() { _ = db.Close() }() var last string for { batch, err := c.readNextDBBatch(db, last) if err != nil { return err } if len(batch) == 0 { break } for _, item := range batch { var obj objectSDK.Object if err := obj.Unmarshal(item.data); err != nil { return fmt.Errorf("unmarshal object from database: %w", err) } if err := c.flushObject(ctx, &obj, item.data, StorageTypeDB); err != nil { return fmt.Errorf("flush object from database: %w", err) } } last = batch[len(batch)-1].address } if err := db.Close(); err != nil { return fmt.Errorf("close write-cache database: %w", err) } if err := os.Remove(filepath.Join(c.path, dbName)); err != nil { return fmt.Errorf("remove write-cache database: %w", err) } return nil } type batchItem struct { data []byte address string } func (c *cache) readNextDBBatch(db *bbolt.DB, last string) ([]batchItem, error) { const batchSize = 100 var batch []batchItem err := db.View(func(tx *bbolt.Tx) error { var addr oid.Address b := tx.Bucket(defaultBucket) cs := b.Cursor() for k, data := cs.Seek([]byte(last)); k != nil; k, data = cs.Next() { sa := string(k) if sa == last { continue } if err := addr.DecodeString(sa); err != nil { return fmt.Errorf("decode address from database: %w", err) } batch = append(batch, batchItem{data: bytes.Clone(data), address: sa}) if len(batch) == batchSize { return errIterationCompleted } } return nil }) if err == nil || errors.Is(err, errIterationCompleted) { return batch, nil } return nil, err } // OpenDB opens BoltDB instance for write-cache. Opens in read-only mode if ro is true. func OpenDB(p string, ro bool, openFile func(string, int, fs.FileMode) (*os.File, error)) (*bbolt.DB, error) { return bbolt.Open(filepath.Join(p, dbName), os.ModePerm, &bbolt.Options{ NoFreelistSync: true, ReadOnly: ro, Timeout: 100 * time.Millisecond, OpenFile: openFile, }) }