frostfs-node/pkg/local_object_storage/engine/get.go
Aleksey Savchuk 2dc934839a
Some checks failed
DCO action / DCO (pull_request) Successful in 1m31s
Vulncheck / Vulncheck (pull_request) Successful in 2m14s
Pre-commit hooks / Pre-commit (pull_request) Successful in 2m24s
Build / Build Components (pull_request) Successful in 2m28s
Tests and linters / Lint (pull_request) Successful in 2m59s
Tests and linters / Run gofumpt (pull_request) Successful in 2m55s
Tests and linters / gopls check (pull_request) Successful in 3m13s
Tests and linters / Tests (pull_request) Failing after 3m30s
Tests and linters / Staticcheck (pull_request) Successful in 3m35s
Tests and linters / Tests with -race (pull_request) Failing after 5m5s
[#1403] engine: Iterate all shards while seaching for an object
Before, when searching for an object, we iterated over shards and stopped
right after we found the object. Currently, we need to iterate over all
shards, because, when the object and its GC mark are stored separately, we
could find the object earlier than its GC mark.

Signed-off-by: Aleksey Savchuk <a.savchuk@yadro.com>
2024-10-08 12:29:09 +03:00

229 lines
6.3 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-node/pkg/local_object_storage/util"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/util/logicerr"
tracingPkg "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/tracing"
"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"
)
// GetPrm groups the parameters of Get operation.
type GetPrm struct {
addr oid.Address
}
// GetRes groups the resulting values of Get operation.
type GetRes struct {
obj *objectSDK.Object
}
// WithAddress is a Get option to set the address of the requested object.
//
// Option is required.
func (p *GetPrm) WithAddress(addr oid.Address) {
p.addr = addr
}
// Object returns the requested object.
func (r GetRes) Object() *objectSDK.Object {
return r.obj
}
// Get reads an object from local storage.
//
// Returns any error encountered that
// did not allow to completely read the object part.
//
// Returns an error of type apistatus.ObjectNotFound if the requested object is missing in local storage.
// Returns an error of type apistatus.ObjectAlreadyRemoved if the object has been marked as removed.
//
// Returns an error if executions are blocked (see BlockExecution).
func (e *StorageEngine) Get(ctx context.Context, prm GetPrm) (res GetRes, err error) {
ctx, span := tracing.StartSpanFromContext(ctx, "StorageEngine.Get",
trace.WithAttributes(
attribute.String("address", prm.addr.EncodeToString()),
))
defer span.End()
err = e.execIfNotBlocked(func() error {
res, err = e.get(ctx, prm)
return err
})
return
}
func (e *StorageEngine) get(ctx context.Context, prm GetPrm) (GetRes, error) {
defer elapsed("Get", e.metrics.AddMethodDuration)()
errNotFound := new(apistatus.ObjectNotFound)
var shPrm shard.GetPrm
shPrm.SetAddress(prm.addr)
it := &getShardIterator{
OutError: errNotFound,
ShardPrm: shPrm,
Address: prm.addr,
Engine: e,
}
it.tryGetWithMeta(ctx)
if it.SplitInfo != nil {
return GetRes{}, logicerr.Wrap(objectSDK.NewSplitInfoError(it.SplitInfo))
}
if it.ECInfo != nil {
return GetRes{}, logicerr.Wrap(objectSDK.NewECInfoError(it.ECInfo))
}
if it.ObjectExpired {
return GetRes{}, errNotFound
}
if client.IsErrObjectAlreadyRemoved(it.OutError) {
return GetRes{}, it.OutError
}
if it.Object == nil {
if !it.HasDegraded && it.ShardWithMeta.Shard == nil || !client.IsErrObjectNotFound(it.OutError) {
return GetRes{}, it.OutError
}
it.tryGetFromBlobstore(ctx)
if it.Object == nil {
return GetRes{}, it.OutError
}
if it.ShardWithMeta.Shard != nil && it.MetaError != nil {
e.log.Warn(logs.ShardMetaInfoPresentButObjectNotFound,
zap.Stringer("shard_id", it.ShardWithMeta.ID()),
zap.String("error", it.MetaError.Error()),
zap.Stringer("address", prm.addr),
zap.String("trace_id", tracingPkg.GetTraceID(ctx)))
}
}
return GetRes{
obj: it.Object,
}, nil
}
type getShardIterator struct {
Object *objectSDK.Object
SplitInfo *objectSDK.SplitInfo
ECInfo *objectSDK.ECInfo
OutError error
ShardWithMeta hashedShard
MetaError error
HasDegraded bool
ObjectExpired bool
ShardPrm shard.GetPrm
Address oid.Address
Engine *StorageEngine
splitInfoErr *objectSDK.SplitInfoError
ecInfoErr *objectSDK.ECInfoError
}
func (i *getShardIterator) tryGetWithMeta(ctx context.Context) {
i.Engine.iterateOverSortedShards(i.Address, func(_ int, sh hashedShard) (stop bool) {
noMeta := sh.GetMode().NoMetabase()
i.ShardPrm.SetIgnoreMeta(noMeta)
i.HasDegraded = i.HasDegraded || noMeta
res, err := sh.Get(ctx, i.ShardPrm)
if err == nil {
i.Object = res.Object()
// Keep iterating over shards because the object's GC mark can be
// stored on another shard. For more information, please refer to
// https://git.frostfs.info/TrueCloudLab/frostfs-node/pulls/1403.
return false
}
if res.HasMeta() {
i.ShardWithMeta = sh
i.MetaError = err
}
switch {
case client.IsErrObjectNotFound(err):
return false // ignore, go to next shard
case errors.As(err, &i.splitInfoErr):
if i.SplitInfo == nil {
i.SplitInfo = objectSDK.NewSplitInfo()
}
util.MergeSplitInfo(i.splitInfoErr.SplitInfo(), i.SplitInfo)
_, withLink := i.SplitInfo.Link()
_, withLast := i.SplitInfo.LastPart()
// stop iterating over shards if SplitInfo structure is complete
return withLink && withLast
case errors.As(err, &i.ecInfoErr):
if i.ECInfo == nil {
i.ECInfo = objectSDK.NewECInfo()
}
util.MergeECInfo(i.ecInfoErr.ECInfo(), i.ECInfo)
// stop iterating over shards if ECInfo structure is complete
return len(i.ECInfo.Chunks) == int(i.ECInfo.Chunks[0].Total)
case client.IsErrObjectAlreadyRemoved(err):
i.OutError = err
return true // stop, return it back
case shard.IsErrObjectExpired(err):
// object is found but should not be returned
i.ObjectExpired = true
return true
default:
i.Engine.reportShardError(sh, "could not get object from shard", err, zap.Stringer("address", i.Address))
return false
}
})
}
func (i *getShardIterator) tryGetFromBlobstore(ctx context.Context) {
// If the object is not found but is present in metabase,
// try to fetch it from blobstor directly. If it is found in any
// blobstor, increase the error counter for the shard which contains the meta.
i.ShardPrm.SetIgnoreMeta(true)
i.Engine.iterateOverSortedShards(i.Address, func(_ int, sh hashedShard) (stop bool) {
if sh.GetMode().NoMetabase() {
// Already visited.
return false
}
res, err := sh.Get(ctx, i.ShardPrm)
i.Object = res.Object()
return err == nil
})
}
// Get reads object from local storage by provided address.
func Get(ctx context.Context, storage *StorageEngine, addr oid.Address) (*objectSDK.Object, error) {
var getPrm GetPrm
getPrm.WithAddress(addr)
res, err := storage.Get(ctx, getPrm)
if err != nil {
return nil, err
}
return res.Object(), nil
}