From 50b9dea0fbced5aa9dc63b301be971862a7cc529 Mon Sep 17 00:00:00 2001 From: Dmitrii Stepanov Date: Fri, 12 Jul 2024 09:20:45 +0300 Subject: [PATCH] [#1237] objectSvc: Collect EC parts right after HEAD Signed-off-by: Dmitrii Stepanov --- internal/logs/logs.go | 5 +- pkg/services/object/get/assemble.go | 3 +- pkg/services/object/get/assembleec.go | 4 +- pkg/services/object/get/assemblerec.go | 91 ++++++++--------------- pkg/services/object/get/container.go | 99 +++++++++++++++++++++----- pkg/services/object/get/get.go | 3 +- pkg/services/object/get/local.go | 63 +++++++++++++++- pkg/services/object/get/remote.go | 47 ++++++++++++ pkg/services/object/get/request.go | 7 +- 9 files changed, 232 insertions(+), 90 deletions(-) diff --git a/internal/logs/logs.go b/internal/logs/logs.go index 06d8668b2..8ab2a9ed6 100644 --- a/internal/logs/logs.go +++ b/internal/logs/logs.go @@ -104,9 +104,8 @@ const ( GetTryingToAssembleTheECObject = "trying to assemble the ec object..." GetAssemblingSplittedObject = "assembling splitted object..." GetAssemblingECObject = "assembling erasure-coded object..." - GetUnableToGetAllPartsECObject = "unable to get all parts, continue to reconstruct with existed" - GetUnableToGetPartECObject = "unable to get part of the erasure-encoded object" - GetUnableToHeadPartECObject = "unable to head part of the erasure-encoded object" + GetUnableToGetECPartLocal = "failed to get EC part from local storage" + GetUnableToGetECPartRemote = "failed to get EC part from remote node" GetECObjectInRepContainer = "found erasure-coded object in REP container" GetAssemblingSplittedObjectCompleted = "assembling splitted object completed" GetAssemblingECObjectCompleted = "assembling erasure-coded object completed" diff --git a/pkg/services/object/get/assemble.go b/pkg/services/object/get/assemble.go index 66c4580b0..1bb60470e 100644 --- a/pkg/services/object/get/assemble.go +++ b/pkg/services/object/get/assemble.go @@ -140,7 +140,8 @@ func (r *request) getObjectWithIndependentRequest(ctx context.Context, prm Reque prm: prm, infoSplit: objectSDK.NewSplitInfo(), - infoEC: objectSDK.NewECInfo(), + infoEC: make(map[uint32]objectSDK.ECChunk), + partsEC: make(map[uint32]*objectSDK.Object), log: r.log, } diff --git a/pkg/services/object/get/assembleec.go b/pkg/services/object/get/assembleec.go index 5c999929a..d096fba3b 100644 --- a/pkg/services/object/get/assembleec.go +++ b/pkg/services/object/get/assembleec.go @@ -36,7 +36,7 @@ func (r *request) assembleEC(ctx context.Context) { r.log.Debug(logs.GetTryingToAssembleTheECObject) r.prm.common = r.prm.common.WithLocalOnly(false) - assembler := newAssemblerEC(r.address(), r.infoEC, r.ctxRange(), r, r.containerSource, r.log) + assembler := newAssemblerEC(r.address(), r.partsEC, r.infoEC, r.ctxRange(), r.headOnly(), r.containerSource) r.log.Debug(logs.GetAssemblingECObject, zap.Uint64("range_offset", r.ctxRange().GetOffset()), @@ -47,7 +47,7 @@ func (r *request) assembleEC(ctx context.Context) { zap.Uint64("range_length", r.ctxRange().GetLength()), ) - obj, err := assembler.Assemble(ctx, r.prm.objWriter, r.headOnly()) + obj, err := assembler.Assemble(ctx, r.prm.objWriter) if err != nil { r.log.Warn(logs.GetFailedToAssembleECObject, zap.Error(err), diff --git a/pkg/services/object/get/assemblerec.go b/pkg/services/object/get/assemblerec.go index 4a624e467..3266529af 100644 --- a/pkg/services/object/get/assemblerec.go +++ b/pkg/services/object/get/assemblerec.go @@ -2,51 +2,46 @@ package getsvc import ( "context" - "fmt" - "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/policy" - "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger" apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/erasurecode" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" - "go.uber.org/zap" - "golang.org/x/sync/errgroup" ) type assemblerec struct { - addr oid.Address - ecInfo *objectSDK.ECInfo - rng *objectSDK.Range - objGetter objectGetter - cs container.Source - log *logger.Logger + addr oid.Address + ecParts map[uint32]*objectSDK.Object + ecChunks map[uint32]objectSDK.ECChunk + rng *objectSDK.Range + head bool + cs container.Source } func newAssemblerEC( addr oid.Address, - ecInfo *objectSDK.ECInfo, + ecParts map[uint32]*objectSDK.Object, + ecChunks map[uint32]objectSDK.ECChunk, rng *objectSDK.Range, - objGetter objectGetter, + head bool, cs container.Source, - log *logger.Logger, ) *assemblerec { return &assemblerec{ - addr: addr, - rng: rng, - ecInfo: ecInfo, - objGetter: objGetter, - cs: cs, - log: log, + addr: addr, + rng: rng, + head: head, + ecParts: ecParts, + ecChunks: ecChunks, + cs: cs, } } // Assemble assembles erasure-coded object and writes it's content to ObjectWriter. // It returns parent object. -func (a *assemblerec) Assemble(ctx context.Context, writer ObjectWriter, headOnly bool) (*objectSDK.Object, error) { - if headOnly { +func (a *assemblerec) Assemble(ctx context.Context, writer ObjectWriter) (*objectSDK.Object, error) { + if a.head { return a.reconstructHeader(ctx, writer) } else if a.rng != nil { return a.reconstructRange(ctx, writer) @@ -65,7 +60,7 @@ func (a *assemblerec) getConstructor() (*erasurecode.Constructor, error) { } func (a *assemblerec) reconstructHeader(ctx context.Context, writer ObjectWriter) (*objectSDK.Object, error) { - parts := a.retrieveParts(ctx, true) + parts := a.retrieveParts() c, err := a.getConstructor() if err != nil { return nil, err @@ -78,7 +73,7 @@ func (a *assemblerec) reconstructHeader(ctx context.Context, writer ObjectWriter } func (a *assemblerec) reconstructRange(ctx context.Context, writer ObjectWriter) (*objectSDK.Object, error) { - parts := a.retrieveParts(ctx, false) + parts := a.retrieveParts() c, err := a.getConstructor() if err != nil { return nil, err @@ -101,7 +96,7 @@ func (a *assemblerec) reconstructRange(ctx context.Context, writer ObjectWriter) } func (a *assemblerec) reconstructObject(ctx context.Context, writer ObjectWriter) (*objectSDK.Object, error) { - parts := a.retrieveParts(ctx, false) + parts := a.retrieveParts() c, err := a.getConstructor() if err != nil { return nil, err @@ -119,41 +114,17 @@ func (a *assemblerec) reconstructObject(ctx context.Context, writer ObjectWriter return obj, err } -func (a *assemblerec) retrieveParts(mainCtx context.Context, headOnly bool) []*objectSDK.Object { - parts := make([]*objectSDK.Object, int(a.ecInfo.Chunks[0].Total)) - errGroup, ctx := errgroup.WithContext(mainCtx) - - for i := range a.ecInfo.Chunks { - chunk := a.ecInfo.Chunks[i] - errGroup.Go(func() error { - objID := new(oid.ID) - err := objID.ReadFromV2(chunk.ID) - if err != nil { - return fmt.Errorf("invalid object ID: %w", err) - } - var obj *objectSDK.Object - if headOnly { - obj, err = a.objGetter.HeadObject(ctx, *objID) - if err != nil { - a.log.Debug(logs.GetUnableToHeadPartECObject, zap.Stringer("part_id", objID), zap.Error(err)) - return nil - } - } else { - sow := NewSimpleObjectWriter() - obj, err = a.objGetter.GetObjectAndWritePayload(ctx, *objID, nil, sow) - if err != nil { - a.log.Debug(logs.GetUnableToGetPartECObject, zap.Stringer("part_id", objID), zap.Error(err)) - return nil - } - obj.SetPayload(sow.pld) - } - parts[chunk.Index] = obj - return nil - }) - } - - if err := errGroup.Wait(); err != nil { - a.log.Debug(logs.GetUnableToGetAllPartsECObject, zap.Error(err)) +func (a *assemblerec) retrieveParts() []*objectSDK.Object { + parts := make([]*objectSDK.Object, a.getTotalCount()) + for idx := range parts { + parts[idx] = a.ecParts[uint32(idx)] } return parts } + +func (a *assemblerec) getTotalCount() uint32 { + for _, ch := range a.ecChunks { + return ch.Total + } + return 0 +} diff --git a/pkg/services/object/get/container.go b/pkg/services/object/get/container.go index 6ea3c7806..830b4fb4c 100644 --- a/pkg/services/object/get/container.go +++ b/pkg/services/object/get/container.go @@ -4,16 +4,17 @@ import ( "context" "encoding/hex" "errors" + "fmt" "sync" "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/client" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/policy" - "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/util" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object_manager/placement" "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing" 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.uber.org/zap" "golang.org/x/sync/errgroup" ) @@ -69,7 +70,7 @@ func (r *request) processCurrentEpoch(ctx context.Context) bool { r.status = statusUndefined if policy.IsECPlacement(cnr.Value.PlacementPolicy()) { - return r.processECNodes(ctx, traverser, policy.ECDataCount(cnr.Value.PlacementPolicy())) + return r.processECNodes(ctx, traverser, policy.ECDataCount(cnr.Value.PlacementPolicy()), policy.ECParityCount(cnr.Value.PlacementPolicy())) } return r.processRepNodes(ctx, traverser) } @@ -109,10 +110,17 @@ func (r *request) processRepNodes(ctx context.Context, traverser *placement.Trav } } -func (r *request) processECNodes(ctx context.Context, traverser *placement.Traverser, dataCount int) bool { +func (r *request) processECNodes(ctx context.Context, traverser *placement.Traverser, dataCount, parityCount int) bool { ctx, span := tracing.StartSpanFromContext(ctx, "getService.processECNodes") defer span.End() + if !r.isRaw() && len(r.partsEC) >= dataCount { + return true + } + if r.isRaw() && len(r.infoEC) == dataCount+parityCount { + return true + } + err := r.traverseECNodes(ctx, traverser, dataCount) var errSplitInfo *objectSDK.SplitInfoError @@ -123,9 +131,9 @@ func (r *request) processECNodes(ctx context.Context, traverser *placement.Trave switch { case err == nil: // nil is returned if all nodes failed or incomplete EC info received - if len(r.infoEC.Chunks) > 0 { + if len(r.infoEC) > 0 { r.status = statusEC - r.err = objectSDK.NewECInfoError(r.infoEC) + r.err = r.createECInfoError() } else { r.status = statusUndefined r.err = new(apistatus.ObjectNotFound) @@ -187,7 +195,6 @@ func (r *request) traverseECNodes(ctx context.Context, traverser *placement.Trav } func (r *request) processECNodesRequests(ctx context.Context, nodes <-chan placement.Node, dataCount int) error { - var ecInfoGuard sync.Mutex eg, ctx := errgroup.WithContext(ctx) eg.SetLimit(dataCount) for node := range nodes { @@ -222,26 +229,82 @@ func (r *request) processECNodesRequests(ctx context.Context, nodes <-chan place // non EC error found, stop return err case errors.As(err, &errECInfo): - ecInfoGuard.Lock() - defer ecInfoGuard.Unlock() - r.infoEC = util.MergeECInfo(errECInfo.ECInfo(), r.infoEC) if r.isRaw() { - if len(r.infoEC.Chunks) == int(r.infoEC.Chunks[0].Total) { - return objectSDK.NewECInfoError(r.infoEC) - } - return nil + return r.appendECChunksWithCheck(errECInfo) } - if len(r.infoEC.Chunks) >= dataCount { - return objectSDK.NewECInfoError(r.infoEC) - } - return nil + return r.getECChunksRemote(ctx, errECInfo, info, dataCount) } }) } - return eg.Wait() } +func (r *request) appendECChunksWithCheck(errECInfo *objectSDK.ECInfoError) error { + r.ecGuard.Lock() + defer r.ecGuard.Unlock() + for _, ch := range errECInfo.ECInfo().Chunks { + r.infoEC[ch.Index] = objectSDK.ECChunk(ch) + } + if len(r.infoEC) == int(errECInfo.ECInfo().Chunks[0].Total) { + return r.createECInfoError() + } + return nil +} + +func (r *request) getECChunksRemote(ctx context.Context, errECInfo *objectSDK.ECInfoError, info client.NodeInfo, dataCount int) error { + for _, ch := range errECInfo.ECInfo().Chunks { + var objID oid.ID + err := objID.ReadFromV2(ch.ID) + if err != nil { + return fmt.Errorf("invalid object ID: %w", err) + } + + var address oid.Address + address.SetContainer(r.containerID()) + address.SetObject(objID) + var obj *objectSDK.Object + if r.headOnly() { + obj, err = r.headObjectFromNode(ctx, address, info) + } else { + obj, err = r.getObjectFromNode(ctx, address, info) + } + if err != nil { + r.log.Warn(logs.GetUnableToGetECPartRemote, zap.Error(err), zap.Stringer("part_address", address), + zap.String("node", hex.EncodeToString(info.PublicKey()))) + continue + } + + if err := r.appendECChunkAndObjectWithCheck(objectSDK.ECChunk(ch), obj, dataCount); err != nil { + return err + } + } + return nil +} + +func (r *request) appendECChunkAndObjectWithCheck(chunk objectSDK.ECChunk, object *objectSDK.Object, dataCount int) error { + if object == nil { + return nil + } + r.ecGuard.Lock() + defer r.ecGuard.Unlock() + + r.infoEC[chunk.Index] = chunk + r.partsEC[chunk.Index] = object + + if len(r.infoEC) >= dataCount && len(r.partsEC) >= dataCount { + return r.createECInfoError() + } + return nil +} + +func (r *request) createECInfoError() error { + ecInfo := objectSDK.NewECInfo() + for _, chunk := range r.infoEC { + ecInfo.AddChunk(objectSDK.ECChunk(chunk)) + } + return objectSDK.NewECInfoError(ecInfo) +} + type ecGetSuccessErr struct { Object *objectSDK.Object } diff --git a/pkg/services/object/get/get.go b/pkg/services/object/get/get.go index 9738935d2..b7f58e2b5 100644 --- a/pkg/services/object/get/get.go +++ b/pkg/services/object/get/get.go @@ -77,7 +77,8 @@ func (s *Service) get(ctx context.Context, prm RequestParameters) error { prm: prm, infoSplit: objectSDK.NewSplitInfo(), - infoEC: objectSDK.NewECInfo(), + infoEC: make(map[uint32]objectSDK.ECChunk), + partsEC: make(map[uint32]*objectSDK.Object), log: s.log, } diff --git a/pkg/services/object/get/local.go b/pkg/services/object/get/local.go index fcfc9befc..4acce0b37 100644 --- a/pkg/services/object/get/local.go +++ b/pkg/services/object/get/local.go @@ -3,12 +3,13 @@ package getsvc import ( "context" "errors" + "fmt" "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs" - "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/util" "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing" 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.uber.org/zap" ) @@ -45,9 +46,19 @@ func (r *request) executeLocal(ctx context.Context) { mergeSplitInfo(r.splitInfo(), errSplitInfo.SplitInfo()) r.err = objectSDK.NewSplitInfoError(r.infoSplit) case errors.As(err, &errECInfo): + if r.isRaw() { + r.appendECChunks(errECInfo) + r.status = statusEC + r.err = r.createECInfoError() + break + } + if err := r.getECChunksLocal(ctx, errECInfo); err != nil { + r.status = statusUndefined + r.err = err + break + } r.status = statusEC - util.MergeECInfo(errECInfo.ECInfo(), r.infoEC) - r.err = objectSDK.NewECInfoError(r.infoEC) + r.err = r.createECInfoError() case errors.As(err, &errOutOfRange): r.status = statusOutOfRange r.err = errOutOfRange @@ -63,3 +74,49 @@ func (r *request) get(ctx context.Context) (*objectSDK.Object, error) { } return r.localStorage.Get(ctx, r.address()) } + +func (r *request) appendECChunks(errECInfo *objectSDK.ECInfoError) { + r.ecGuard.Lock() + defer r.ecGuard.Unlock() + for _, ch := range errECInfo.ECInfo().Chunks { + r.infoEC[ch.Index] = objectSDK.ECChunk(ch) + } +} + +func (r *request) appendECChunkAndObject(chunk objectSDK.ECChunk, object *objectSDK.Object) { + if object == nil { + return + } + r.ecGuard.Lock() + defer r.ecGuard.Unlock() + + r.infoEC[chunk.Index] = chunk + r.partsEC[chunk.Index] = object +} + +func (r *request) getECChunksLocal(ctx context.Context, errECInfo *objectSDK.ECInfoError) error { + for _, ch := range errECInfo.ECInfo().Chunks { + var objID oid.ID + err := objID.ReadFromV2(ch.ID) + if err != nil { + return fmt.Errorf("invalid object ID: %w", err) + } + + var address oid.Address + address.SetContainer(r.containerID()) + address.SetObject(objID) + var obj *objectSDK.Object + if r.headOnly() { + obj, err = r.localStorage.Head(ctx, address, false) + } else { + obj, err = r.localStorage.Get(ctx, address) + } + if err != nil { + r.log.Warn(logs.GetUnableToGetECPartLocal, zap.Error(err), zap.Stringer("part_address", address)) + continue + } + + r.appendECChunkAndObject(objectSDK.ECChunk(ch), obj) + } + return nil +} diff --git a/pkg/services/object/get/remote.go b/pkg/services/object/get/remote.go index b7700143f..4c549457e 100644 --- a/pkg/services/object/get/remote.go +++ b/pkg/services/object/get/remote.go @@ -10,6 +10,7 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing" 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.uber.org/zap" ) @@ -102,3 +103,49 @@ func (r *request) getRemote(ctx context.Context, rs remoteStorage, info client.N return rs.Get(ctx, r.address(), prm) } + +func (r *request) getObjectFromNode(ctx context.Context, addr oid.Address, info client.NodeInfo) (*objectSDK.Object, error) { + rs, err := r.remoteStorageConstructor.Get(info) + if err != nil { + return nil, err + } + + key, err := r.key() + if err != nil { + return nil, err + } + + prm := RemoteRequestParams{ + Epoch: r.curProcEpoch, + TTL: 1, + PrivateKey: key, + SessionToken: r.prm.common.SessionToken(), + BearerToken: r.prm.common.BearerToken(), + XHeaders: r.prm.common.XHeaders(), + } + + return rs.Get(ctx, addr, prm) +} + +func (r *request) headObjectFromNode(ctx context.Context, addr oid.Address, info client.NodeInfo) (*objectSDK.Object, error) { + rs, err := r.remoteStorageConstructor.Get(info) + if err != nil { + return nil, err + } + + key, err := r.key() + if err != nil { + return nil, err + } + + prm := RemoteRequestParams{ + Epoch: r.curProcEpoch, + TTL: 1, + PrivateKey: key, + SessionToken: r.prm.common.SessionToken(), + BearerToken: r.prm.common.BearerToken(), + XHeaders: r.prm.common.XHeaders(), + } + + return rs.Head(ctx, addr, prm) +} diff --git a/pkg/services/object/get/request.go b/pkg/services/object/get/request.go index 1424c8b67..4536252f6 100644 --- a/pkg/services/object/get/request.go +++ b/pkg/services/object/get/request.go @@ -3,6 +3,7 @@ package getsvc import ( "context" "crypto/ecdsa" + "sync" "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs" clientcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/client" @@ -23,7 +24,9 @@ type request struct { infoSplit *objectSDK.SplitInfo - infoEC *objectSDK.ECInfo + ecGuard sync.Mutex + infoEC map[uint32]objectSDK.ECChunk + partsEC map[uint32]*objectSDK.Object log *logger.Logger @@ -220,7 +223,7 @@ func (r *request) writeCollectedObject(ctx context.Context) { // isForwardingEnabled returns true if common execution // parameters has request forwarding closure set. -func (r request) isForwardingEnabled() bool { +func (r *request) isForwardingEnabled() bool { return r.prm.forwarder != nil }