mirror of
https://github.com/pgsty/minio.git
synced 2026-09-22 10:48:25 +03:00
feat(ilm): relocate hot objects across server pools by GET frequency
Keep NVMe/HDD pool pairs useful without remote tiering: promote objects that are read often, demote previously moved objects once they go idle, and leave the feature off until operators set a two-pool topology. Signed-off-by: mr javad seydi <seydi.birjand@gmail.com>
This commit is contained in:
+95
-22
@@ -615,7 +615,17 @@ func (z *erasureServerPools) getPoolIdxExistingNoLock(ctx context.Context, bucke
|
||||
})
|
||||
}
|
||||
|
||||
func (z *erasureServerPools) getPoolIdxNoLock(ctx context.Context, bucket, object string, size int64) (idx int, err error) {
|
||||
func (z *erasureServerPools) getPoolIdxNoLock(ctx context.Context, bucket, object string, size int64, dstPoolIdx *int) (idx int, err error) {
|
||||
if dstPoolIdx != nil {
|
||||
if *dstPoolIdx < 0 || *dstPoolIdx >= len(z.serverPools) {
|
||||
return -1, errInvalidArgument
|
||||
}
|
||||
if z.IsSuspended(*dstPoolIdx) || z.IsPoolRebalancing(*dstPoolIdx) {
|
||||
return -1, toObjectErr(errDiskFull)
|
||||
}
|
||||
return *dstPoolIdx, nil
|
||||
}
|
||||
|
||||
idx, err = z.getPoolIdxExistingNoLock(ctx, bucket, object)
|
||||
if err != nil && !isErrObjectNotFound(err) {
|
||||
return idx, err
|
||||
@@ -634,7 +644,17 @@ func (z *erasureServerPools) getPoolIdxNoLock(ctx context.Context, bucket, objec
|
||||
// getPoolIdx returns the found previous object and its corresponding pool idx,
|
||||
// if none are found falls back to most available space pool, this function is
|
||||
// designed to be only used by PutObject, CopyObject (newObject creation) and NewMultipartUpload.
|
||||
func (z *erasureServerPools) getPoolIdx(ctx context.Context, bucket, object string, size int64) (idx int, err error) {
|
||||
func (z *erasureServerPools) getPoolIdx(ctx context.Context, bucket, object string, size int64, dstPoolIdx *int) (idx int, err error) {
|
||||
if dstPoolIdx != nil {
|
||||
if *dstPoolIdx < 0 || *dstPoolIdx >= len(z.serverPools) {
|
||||
return -1, errInvalidArgument
|
||||
}
|
||||
if z.IsSuspended(*dstPoolIdx) || z.IsPoolRebalancing(*dstPoolIdx) {
|
||||
return -1, toObjectErr(errDiskFull)
|
||||
}
|
||||
return *dstPoolIdx, nil
|
||||
}
|
||||
|
||||
pinfo, _, err := z.getPoolInfoExistingWithOpts(ctx, bucket, object, ObjectOptions{
|
||||
SkipDecommissioned: true,
|
||||
SkipRebalancing: true,
|
||||
@@ -656,6 +676,13 @@ func (z *erasureServerPools) getPoolIdx(ctx context.Context, bucket, object stri
|
||||
return idx, nil
|
||||
}
|
||||
|
||||
func dataMovementDstPool(opts ObjectOptions) *int {
|
||||
if !opts.DataMovement {
|
||||
return nil
|
||||
}
|
||||
return opts.DstPoolIdx
|
||||
}
|
||||
|
||||
func (z *erasureServerPools) Shutdown(ctx context.Context) error {
|
||||
g := errgroup.WithNErrs(len(z.serverPools))
|
||||
|
||||
@@ -1090,14 +1117,20 @@ func (z *erasureServerPools) PutObject(ctx context.Context, bucket string, objec
|
||||
|
||||
object = encodeDirObject(object)
|
||||
if z.SinglePool() {
|
||||
_, err := z.getPoolIdx(ctx, bucket, object, data.Size())
|
||||
idx, err := z.getPoolIdx(ctx, bucket, object, data.Size(), dataMovementDstPool(opts))
|
||||
if err != nil {
|
||||
return ObjectInfo{}, err
|
||||
}
|
||||
if dataMovementDstPool(opts) != nil && idx == opts.SrcPoolIdx {
|
||||
return ObjectInfo{}, DataMovementOverwriteErr{
|
||||
Bucket: bucket, Object: object, VersionID: opts.VersionID,
|
||||
Err: errDataMovementSrcDstPoolSame,
|
||||
}
|
||||
}
|
||||
return z.serverPools[0].PutObject(ctx, bucket, object, data, opts)
|
||||
}
|
||||
|
||||
idx, err := z.getPoolIdx(ctx, bucket, object, data.Size())
|
||||
idx, err := z.getPoolIdx(ctx, bucket, object, data.Size(), dataMovementDstPool(opts))
|
||||
if err != nil {
|
||||
return ObjectInfo{}, err
|
||||
}
|
||||
@@ -1145,6 +1178,28 @@ func (z *erasureServerPools) DeleteObject(ctx context.Context, bucket string, ob
|
||||
return ObjectInfo{}, z.deletePrefix(ctx, bucket, object)
|
||||
}
|
||||
|
||||
// Access-tier moves must recreate delete markers on the explicitly
|
||||
// selected destination. The regular data-movement path discovers a pool
|
||||
// from existing object state, which is ambiguous while both source and
|
||||
// destination temporarily contain the version stack.
|
||||
if dstPoolIdx := dataMovementDstPool(opts); dstPoolIdx != nil {
|
||||
if *dstPoolIdx < 0 || *dstPoolIdx >= len(z.serverPools) {
|
||||
return ObjectInfo{}, errInvalidArgument
|
||||
}
|
||||
if *dstPoolIdx == opts.SrcPoolIdx {
|
||||
return ObjectInfo{}, DataMovementOverwriteErr{
|
||||
Bucket: bucket, Object: decodeDirObject(object), VersionID: opts.VersionID,
|
||||
Err: errDataMovementSrcDstPoolSame,
|
||||
}
|
||||
}
|
||||
if z.IsSuspended(*dstPoolIdx) || z.IsPoolRebalancing(*dstPoolIdx) {
|
||||
return ObjectInfo{}, toObjectErr(errDiskFull)
|
||||
}
|
||||
objInfo, err = z.serverPools[*dstPoolIdx].DeleteObject(ctx, bucket, object, opts)
|
||||
objInfo.Name = decodeDirObject(object)
|
||||
return objInfo, err
|
||||
}
|
||||
|
||||
gopts := opts
|
||||
gopts.NoLock = true
|
||||
|
||||
@@ -1323,10 +1378,16 @@ func (z *erasureServerPools) CopyObject(ctx context.Context, srcBucket, srcObjec
|
||||
dstOpts.NoLock = true
|
||||
}
|
||||
|
||||
poolIdx, err := z.getPoolIdxNoLock(ctx, dstBucket, dstObject, srcInfo.Size)
|
||||
poolIdx, err := z.getPoolIdxNoLock(ctx, dstBucket, dstObject, srcInfo.Size, dataMovementDstPool(dstOpts))
|
||||
if err != nil {
|
||||
return objInfo, err
|
||||
}
|
||||
if dataMovementDstPool(dstOpts) != nil && poolIdx == dstOpts.SrcPoolIdx {
|
||||
return ObjectInfo{}, DataMovementOverwriteErr{
|
||||
Bucket: dstBucket, Object: dstObject, VersionID: dstOpts.VersionID,
|
||||
Err: errDataMovementSrcDstPoolSame,
|
||||
}
|
||||
}
|
||||
|
||||
if cpSrcDstSame && srcInfo.metadataOnly {
|
||||
// Version ID is set for the destination and source == destination version ID.
|
||||
@@ -1771,29 +1832,41 @@ func (z *erasureServerPools) NewMultipartUpload(ctx context.Context, bucket, obj
|
||||
}()
|
||||
|
||||
if z.SinglePool() {
|
||||
return z.serverPools[0].NewMultipartUpload(ctx, bucket, object, opts)
|
||||
}
|
||||
|
||||
for idx, pool := range z.serverPools {
|
||||
if z.IsSuspended(idx) || z.IsPoolRebalancing(idx) {
|
||||
continue
|
||||
}
|
||||
|
||||
result, err := pool.ListMultipartUploads(ctx, bucket, object, "", "", "", maxUploadsList)
|
||||
idx, err := z.getPoolIdx(ctx, bucket, object, -1, dataMovementDstPool(opts))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// If there is a multipart upload with the same bucket/object name,
|
||||
// create the new multipart in the same pool, this will avoid
|
||||
// creating two multiparts uploads in two different pools
|
||||
if len(result.Uploads) != 0 {
|
||||
return z.serverPools[idx].NewMultipartUpload(ctx, bucket, object, opts)
|
||||
if dataMovementDstPool(opts) != nil && idx == opts.SrcPoolIdx {
|
||||
return nil, DataMovementOverwriteErr{
|
||||
Bucket: bucket, Object: object, VersionID: opts.VersionID,
|
||||
Err: errDataMovementSrcDstPoolSame,
|
||||
}
|
||||
}
|
||||
return z.serverPools[0].NewMultipartUpload(ctx, bucket, object, opts)
|
||||
}
|
||||
|
||||
if dataMovementDstPool(opts) == nil {
|
||||
for idx, pool := range z.serverPools {
|
||||
if z.IsSuspended(idx) || z.IsPoolRebalancing(idx) {
|
||||
continue
|
||||
}
|
||||
|
||||
result, err := pool.ListMultipartUploads(ctx, bucket, object, "", "", "", maxUploadsList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// If there is a multipart upload with the same bucket/object name,
|
||||
// create the new multipart in the same pool, this will avoid
|
||||
// creating two multiparts uploads in two different pools.
|
||||
if len(result.Uploads) != 0 {
|
||||
return z.serverPools[idx].NewMultipartUpload(ctx, bucket, object, opts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// any parallel writes on the object will block for this poolIdx
|
||||
// to return since this holds a read lock on the namespace.
|
||||
idx, err := z.getPoolIdx(ctx, bucket, object, -1)
|
||||
idx, err := z.getPoolIdx(ctx, bucket, object, -1, dataMovementDstPool(opts))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1827,7 +1900,7 @@ func (z *erasureServerPools) PutObjectPart(ctx context.Context, bucket, object,
|
||||
}
|
||||
|
||||
if z.SinglePool() {
|
||||
_, err := z.getPoolIdx(ctx, bucket, object, data.Size())
|
||||
_, err := z.getPoolIdx(ctx, bucket, object, data.Size(), dataMovementDstPool(opts))
|
||||
if err != nil {
|
||||
return PartInfo{}, err
|
||||
}
|
||||
@@ -2987,7 +3060,7 @@ func (z *erasureServerPools) DecomTieredObject(ctx context.Context, bucket, obje
|
||||
defer ns.Unlock(lkctx)
|
||||
opts.NoLock = true
|
||||
}
|
||||
idx, err := z.getPoolIdxNoLock(ctx, bucket, object, fi.Size)
|
||||
idx, err := z.getPoolIdxNoLock(ctx, bucket, object, fi.Size, dataMovementDstPool(opts))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user