package blobovniczatree import ( "context" "errors" "os" "strconv" "strings" "sync" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/common" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/compression" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" "git.frostfs.info/TrueCloudLab/hrw" ) // Blobovniczas represents the storage of the "small" objects. // // Each object is stored in Blobovnicza's (B-s). // B-s are structured in a multilevel directory hierarchy // with fixed depth and width (configured by BlobStor). // // Example (width = 4, depth = 3): // // x===============================x // |[0] [1] [2] [3]| // | \ / | // | \ / | // | \ / | // | \ / | // |[0] [1] [2] [3]| // | | / | // | | / | // | | / | // | | / | // |[0](F) [1](A) [X] [X]| // x===============================x // // Elements of the deepest level are B-s. // B-s are allocated dynamically. At each moment of the time there is // an active B (ex. A), set of already filled B-s (ex. F) and // a list of not yet initialized B-s (ex. X). After filling the active B // it becomes full, and next B becomes initialized and active. // // Active B and some of the full B-s are cached (LRU). All cached // B-s are intitialized and opened. // // Object is saved as follows: // 1. at each level, according to HRW, the next one is selected and // dives into it until we reach the deepest; // 2. at the B-s level object is saved to the active B. If active B // is full, next B is opened, initialized and cached. If there // is no more X candidates, goto 1 and process next level. // // After the object is saved in B, path concatenation is returned // in system path format as B identifier (ex. "0/1/1" or "3/2/1"). type Blobovniczas struct { cfg commondbManager *dbManager activeDBManager *activeDBManager dbCache *dbCache deleteProtectedObjects *addressMap dbFilesGuard *sync.RWMutex rebuildGuard *sync.RWMutex } var _ common.Storage = (*Blobovniczas)(nil) var errPutFailed = errors.New("could not save the object in any blobovnicza") const ( dbExtension = ".db" ) // NewBlobovniczaTree returns new instance of blobovniczas tree. func NewBlobovniczaTree(ctx context.Context, opts ...Option) (blz *Blobovniczas) { blz = new(Blobovniczas) initConfig(&blz.cfg) for i := range opts { opts[i](&blz.cfg) } blz.commondbManager = newDBManager(blz.rootPath, blz.blzOpts, blz.readOnly, blz.metrics.Blobovnicza(), blz.log) blz.activeDBManager = newActiveDBManager(blz.commondbManager, blz.rootPath) blz.dbCache = newDBCache(ctx, blz.openedCacheSize, blz.openedCacheTTL, blz.openedCacheExpInterval, blz.commondbManager) blz.deleteProtectedObjects = newAddressMap() blz.dbFilesGuard = &sync.RWMutex{} blz.rebuildGuard = &sync.RWMutex{} return blz } // returns hash of the object address. func addressHash(addr *oid.Address, path string) uint64 { var a string if addr != nil { a = addr.EncodeToString() } return hrw.StringHash(a + path) } func u64ToHexString(ind uint64) string { return strconv.FormatUint(ind, 16) } func u64ToHexStringExt(ind uint64) string { return strconv.FormatUint(ind, 16) + dbExtension } func u64FromHexString(str string) uint64 { v, err := strconv.ParseUint(strings.TrimSuffix(str, dbExtension), 16, 64) if err != nil { panic("blobovnicza name is not an index " + str) } return v } func getBlobovniczaMaxIndex(directory string) (bool, uint64, error) { entries, err := os.ReadDir(directory) if os.IsNotExist(err) { // non initialized tree return false, 0, nil } if err != nil { return false, 0, err } if len(entries) == 0 { return false, 0, nil } var hasDBs bool var maxIdx uint64 for _, e := range entries { if e.IsDir() || strings.HasSuffix(e.Name(), rebuildSuffix) { continue } hasDBs = true maxIdx = max(u64FromHexString(e.Name()), maxIdx) } return hasDBs, maxIdx, nil } // Type is blobovniczatree storage type used in logs and configuration. const Type = "blobovnicza" // Type implements common.Storage. func (b *Blobovniczas) Type() string { return Type } // Path implements common.Storage. func (b *Blobovniczas) Path() string { return b.rootPath } // SetCompressor implements common.Storage. func (b *Blobovniczas) SetCompressor(cc *compression.Config) { b.compression = cc } func (b *Blobovniczas) Compressor() *compression.Config { return b.compression } // SetReportErrorFunc implements common.Storage. func (b *Blobovniczas) SetReportErrorFunc(f func(context.Context, string, error)) { b.reportError = f } func (b *Blobovniczas) SetParentID(parentID string) { b.metrics.SetParentID(parentID) }