frostfs-node/pkg/local_object_storage/engine/delete.go
Dmitrii Stepanov 3a441f072f
[#1709] shard: Check if context canceled for shard iteration
If context has already been canceled, then there is no need to check other shards.
At the same time, it is necessary to avoid handling context cancellation
in each handler. Therefore, the context check has been moved to the shard
iteration method, which now returns an error.

Change-Id: I70030ace36593ce7d2b8376bee39fe82e9dbf88f
Signed-off-by: Dmitrii Stepanov <d.stepanov@yadro.com>
2025-04-21 15:20:50 +03:00

196 lines
5.5 KiB
Go

package engine
import (
"context"
"errors"
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
)
// DeletePrm groups the parameters of Delete operation.
type DeletePrm struct {
addr oid.Address
forceRemoval bool
}
// WithAddress is a Delete option to set the addresses of the objects to delete.
//
// Option is required.
func (p *DeletePrm) WithAddress(addr oid.Address) {
p.addr = addr
}
// WithForceRemoval is a Delete option to remove an object despite any
// restrictions imposed on deleting that object. Expected to be used
// only in control service.
func (p *DeletePrm) WithForceRemoval() {
p.forceRemoval = true
}
// Delete marks the objects to be removed.
//
// Returns an error if executions are blocked (see BlockExecution).
//
// Returns apistatus.ObjectLocked if at least one object is locked.
// In this case no object from the list is marked to be deleted.
//
// NOTE: Marks any object to be deleted (despite any prohibitions
// on operations with that object) if WithForceRemoval option has
// been provided.
func (e *StorageEngine) Delete(ctx context.Context, prm DeletePrm) error {
ctx, span := tracing.StartSpanFromContext(ctx, "StorageEngine.Delete",
trace.WithAttributes(
attribute.String("address", prm.addr.EncodeToString()),
attribute.Bool("force_removal", prm.forceRemoval),
))
defer span.End()
defer elapsed("Delete", e.metrics.AddMethodDuration)()
return e.execIfNotBlocked(func() error {
return e.delete(ctx, prm)
})
}
func (e *StorageEngine) delete(ctx context.Context, prm DeletePrm) error {
var locked struct {
is bool
}
var splitInfo *objectSDK.SplitInfo
var ecInfo *objectSDK.ECInfo
// Removal of a big object is done in multiple stages:
// 1. Remove the parent object. If it is locked or already removed, return immediately.
// 2. Otherwise, search for all objects with a particular SplitID and delete them too.
if err := e.iterateOverSortedShards(ctx, prm.addr, func(_ int, sh hashedShard) (stop bool) {
var existsPrm shard.ExistsPrm
existsPrm.Address = prm.addr
resExists, err := sh.Exists(ctx, existsPrm)
if err != nil {
if client.IsErrObjectAlreadyRemoved(err) || shard.IsErrObjectExpired(err) {
return true
}
var splitErr *objectSDK.SplitInfoError
var ecErr *objectSDK.ECInfoError
if errors.As(err, &splitErr) {
splitInfo = splitErr.SplitInfo()
} else if errors.As(err, &ecErr) {
e.deleteChunks(ctx, sh, ecInfo, prm)
return false
} else {
if !client.IsErrObjectNotFound(err) {
e.reportShardError(ctx, sh, "could not check object existence", err, zap.Stringer("address", prm.addr))
}
return false
}
} else if !resExists.Exists() {
return false
}
var shPrm shard.InhumePrm
shPrm.MarkAsGarbage(prm.addr)
if prm.forceRemoval {
shPrm.ForceRemoval()
}
_, err = sh.Inhume(ctx, shPrm)
if err != nil {
e.reportShardError(ctx, sh, "could not inhume object in shard", err, zap.Stringer("address", prm.addr))
var target *apistatus.ObjectLocked
locked.is = errors.As(err, &target)
return locked.is
}
// If a parent object is removed we should set GC mark on each shard.
return splitInfo == nil
}); err != nil {
return err
}
if locked.is {
return new(apistatus.ObjectLocked)
}
if splitInfo != nil {
return e.deleteChildren(ctx, prm.addr, prm.forceRemoval, splitInfo.SplitID())
}
return nil
}
func (e *StorageEngine) deleteChildren(ctx context.Context, addr oid.Address, force bool, splitID *objectSDK.SplitID) error {
var fs objectSDK.SearchFilters
fs.AddSplitIDFilter(objectSDK.MatchStringEqual, splitID)
var selectPrm shard.SelectPrm
selectPrm.SetFilters(fs)
selectPrm.SetContainerID(addr.Container(), false) // doesn't matter for search by splitID
var inhumePrm shard.InhumePrm
if force {
inhumePrm.ForceRemoval()
}
return e.iterateOverSortedShards(ctx, addr, func(_ int, sh hashedShard) (stop bool) {
res, err := sh.Select(ctx, selectPrm)
if err != nil {
e.log.Warn(ctx, logs.EngineErrorDuringSearchingForObjectChildren,
zap.Stringer("addr", addr),
zap.Error(err))
return false
}
for _, addr := range res.AddressList() {
inhumePrm.MarkAsGarbage(addr)
_, err = sh.Inhume(ctx, inhumePrm)
if err != nil {
e.log.Debug(ctx, logs.EngineCouldNotInhumeObjectInShard,
zap.Stringer("addr", addr),
zap.Error(err))
continue
}
}
return false
})
}
func (e *StorageEngine) deleteChunks(
ctx context.Context, sh hashedShard, ecInfo *objectSDK.ECInfo, prm DeletePrm,
) {
var inhumePrm shard.InhumePrm
if prm.forceRemoval {
inhumePrm.ForceRemoval()
}
for _, chunk := range ecInfo.Chunks {
var addr oid.Address
addr.SetContainer(prm.addr.Container())
var objID oid.ID
err := objID.ReadFromV2(chunk.ID)
if err != nil {
e.reportShardError(ctx, sh, "could not delete EC chunk", err, zap.Stringer("address", prm.addr))
}
addr.SetObject(objID)
inhumePrm.MarkAsGarbage(addr)
_, err = sh.Inhume(ctx, inhumePrm)
if err != nil {
e.log.Debug(ctx, logs.EngineCouldNotInhumeObjectInShard,
zap.Stringer("addr", addr),
zap.Error(err))
continue
}
}
}