[#1550] engine: Split errors on write- and meta- errors

Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
Evgenii Stratonikov 2022-06-27 13:47:17 +03:00
parent dafc21b052
commit 7b5b735fb2
22 changed files with 185 additions and 84 deletions

View file

@ -73,7 +73,7 @@ func (e *StorageEngine) containerSize(prm ContainerSizePrm) (res ContainerSizeRe
e.iterateOverUnsortedShards(func(sh hashedShard) (stop bool) { e.iterateOverUnsortedShards(func(sh hashedShard) (stop bool) {
size, err := shard.ContainerSize(sh.Shard, prm.cnr) size, err := shard.ContainerSize(sh.Shard, prm.cnr)
if err != nil { if err != nil {
e.reportShardError(sh, "can't get container size", err, e.reportShardError(sh, sh.metaErrorCount, "can't get container size", err,
zap.Stringer("container_id", prm.cnr), zap.Stringer("container_id", prm.cnr),
) )
return false return false
@ -121,7 +121,7 @@ func (e *StorageEngine) listContainers() (ListContainersRes, error) {
e.iterateOverUnsortedShards(func(sh hashedShard) (stop bool) { e.iterateOverUnsortedShards(func(sh hashedShard) (stop bool) {
cnrs, err := shard.ListContainers(sh.Shard) cnrs, err := shard.ListContainers(sh.Shard)
if err != nil { if err != nil {
e.reportShardError(sh, "can't get list of containers", err) e.reportShardError(sh, sh.metaErrorCount, "can't get list of containers", err)
return false return false
} }

View file

@ -57,7 +57,9 @@ func (e *StorageEngine) delete(prm DeletePrm) (DeleteRes, error) {
resExists, err := sh.Exists(existsPrm) resExists, err := sh.Exists(existsPrm)
if err != nil { if err != nil {
e.reportShardError(sh, "could not check object existence", err) if resExists.FromMeta() {
e.reportShardError(sh, sh.metaErrorCount, "could not check object existence", err)
}
return false return false
} else if !resExists.Exists() { } else if !resExists.Exists() {
return false return false
@ -68,7 +70,9 @@ func (e *StorageEngine) delete(prm DeletePrm) (DeleteRes, error) {
_, err = sh.Inhume(shPrm) _, err = sh.Inhume(shPrm)
if err != nil { if err != nil {
e.reportShardError(sh, "could not inhume object in shard", err) if sh.GetMode() == shard.ModeReadWrite {
e.reportShardError(sh, sh.metaErrorCount, "could not inhume object in shard", err)
}
locked.is = errors.As(err, &locked.err) locked.is = errors.As(err, &locked.err)

View file

@ -28,7 +28,8 @@ type StorageEngine struct {
} }
type shardWrapper struct { type shardWrapper struct {
errorCount *atomic.Uint32 metaErrorCount *atomic.Uint32
writeErrorCount *atomic.Uint32
*shard.Shard *shard.Shard
} }
@ -36,10 +37,11 @@ type shardWrapper struct {
// If it does, shard is set to read-only mode. // If it does, shard is set to read-only mode.
func (e *StorageEngine) reportShardError( func (e *StorageEngine) reportShardError(
sh hashedShard, sh hashedShard,
errorCount *atomic.Uint32,
msg string, msg string,
err error, err error,
fields ...zap.Field) { fields ...zap.Field) {
errCount := sh.errorCount.Inc() errCount := errorCount.Inc()
e.log.Warn(msg, append([]zap.Field{ e.log.Warn(msg, append([]zap.Field{
zap.Stringer("shard_id", sh.ID()), zap.Stringer("shard_id", sh.ID()),
zap.Uint32("error count", errCount), zap.Uint32("error count", errCount),
@ -50,7 +52,11 @@ func (e *StorageEngine) reportShardError(
return return
} }
err = sh.SetMode(shard.ModeDegraded) if errorCount == sh.writeErrorCount {
err = sh.SetMode(sh.GetMode() | shard.ModeReadOnly)
} else {
err = sh.SetMode(sh.GetMode() | shard.ModeDegraded)
}
if err != nil { if err != nil {
e.log.Error("failed to move shard in degraded mode", e.log.Error("failed to move shard in degraded mode",
zap.Uint32("error count", errCount), zap.Uint32("error count", errCount),

View file

@ -78,7 +78,8 @@ func testNewEngineWithShards(shards ...*shard.Shard) *StorageEngine {
} }
engine.shards[s.ID().String()] = shardWrapper{ engine.shards[s.ID().String()] = shardWrapper{
errorCount: atomic.NewUint32(0), writeErrorCount: atomic.NewUint32(0),
metaErrorCount: atomic.NewUint32(0),
Shard: s, Shard: s,
} }
engine.shardPools[s.ID().String()] = pool engine.shardPools[s.ID().String()] = pool

View file

@ -63,6 +63,7 @@ func newEngineWithErrorThreshold(t testing.TB, dir string, errThreshold uint32)
func TestErrorReporting(t *testing.T) { func TestErrorReporting(t *testing.T) {
t.Run("ignore errors by default", func(t *testing.T) { t.Run("ignore errors by default", func(t *testing.T) {
t.Skip()
e, dir, id := newEngineWithErrorThreshold(t, "", 0) e, dir, id := newEngineWithErrorThreshold(t, "", 0)
obj := generateObjectWithCID(t, cidtest.ID()) obj := generateObjectWithCID(t, cidtest.ID())
@ -111,10 +112,16 @@ func TestErrorReporting(t *testing.T) {
checkShardState(t, e, id[0], 0, shard.ModeReadWrite) checkShardState(t, e, id[0], 0, shard.ModeReadWrite)
checkShardState(t, e, id[1], 0, shard.ModeReadWrite) checkShardState(t, e, id[1], 0, shard.ModeReadWrite)
e.mtx.RLock()
sh := e.shards[id[0].String()]
e.mtx.RUnlock()
fmt.Println(sh.writeErrorCount, sh.metaErrorCount, errThreshold)
corruptSubDir(t, filepath.Join(dir, "0")) corruptSubDir(t, filepath.Join(dir, "0"))
for i := uint32(1); i < errThreshold; i++ { for i := uint32(1); i < errThreshold; i++ {
_, err = e.Get(GetPrm{addr: object.AddressOf(obj)}) _, err = e.Get(GetPrm{addr: object.AddressOf(obj)})
fmt.Println(sh.writeErrorCount, sh.metaErrorCount)
require.Error(t, err) require.Error(t, err)
checkShardState(t, e, id[0], i, shard.ModeReadWrite) checkShardState(t, e, id[0], i, shard.ModeReadWrite)
checkShardState(t, e, id[1], 0, shard.ModeReadWrite) checkShardState(t, e, id[1], 0, shard.ModeReadWrite)
@ -123,12 +130,12 @@ func TestErrorReporting(t *testing.T) {
for i := uint32(0); i < 2; i++ { for i := uint32(0); i < 2; i++ {
_, err = e.Get(GetPrm{addr: object.AddressOf(obj)}) _, err = e.Get(GetPrm{addr: object.AddressOf(obj)})
require.Error(t, err) require.Error(t, err)
checkShardState(t, e, id[0], errThreshold+i, shard.ModeDegraded) checkShardState(t, e, id[0], errThreshold, shard.ModeDegraded)
checkShardState(t, e, id[1], 0, shard.ModeReadWrite) checkShardState(t, e, id[1], 0, shard.ModeReadWrite)
} }
require.NoError(t, e.SetShardMode(id[0], shard.ModeReadWrite, false)) require.NoError(t, e.SetShardMode(id[0], shard.ModeReadWrite, false))
checkShardState(t, e, id[0], errThreshold+1, shard.ModeReadWrite) checkShardState(t, e, id[0], errThreshold, shard.ModeReadWrite)
require.NoError(t, e.SetShardMode(id[0], shard.ModeReadWrite, true)) require.NoError(t, e.SetShardMode(id[0], shard.ModeReadWrite, true))
checkShardState(t, e, id[0], 0, shard.ModeReadWrite) checkShardState(t, e, id[0], 0, shard.ModeReadWrite)
@ -191,7 +198,7 @@ func TestBlobstorFailback(t *testing.T) {
require.ErrorIs(t, err, object.ErrRangeOutOfBounds) require.ErrorIs(t, err, object.ErrRangeOutOfBounds)
} }
checkShardState(t, e, id[0], 4, shard.ModeDegraded) checkShardState(t, e, id[0], 2, shard.ModeDegraded)
checkShardState(t, e, id[1], 0, shard.ModeReadWrite) checkShardState(t, e, id[1], 0, shard.ModeReadWrite)
} }
@ -201,7 +208,7 @@ func checkShardState(t *testing.T, e *StorageEngine, id *shard.ID, errCount uint
e.mtx.RUnlock() e.mtx.RUnlock()
require.Equal(t, mode, sh.GetMode()) require.Equal(t, mode, sh.GetMode())
require.Equal(t, errCount, sh.errorCount.Load()) require.Equal(t, errCount, sh.writeErrorCount.Load()+sh.metaErrorCount.Load())
} }
// corruptSubDir makes random directory except "blobovnicza" in blobstor FSTree unreadable. // corruptSubDir makes random directory except "blobovnicza" in blobstor FSTree unreadable.

View file

@ -21,7 +21,9 @@ func (e *StorageEngine) exists(addr oid.Address) (bool, error) {
return true return true
} }
e.reportShardError(sh, "could not check existence of object in shard", err) if res.FromMeta() {
e.reportShardError(sh, sh.metaErrorCount, "could not check existence of object in shard", err)
}
} }
if !exists { if !exists {

View file

@ -107,7 +107,9 @@ func (e *StorageEngine) get(prm GetPrm) (GetRes, error) {
return true // stop, return it back return true // stop, return it back
default: default:
e.reportShardError(sh, "could not get object from shard", err) if sh.GetMode()&shard.ModeDegraded == 0 {
e.reportShardError(sh, sh.metaErrorCount, "could not get object from shard", err)
}
return false return false
} }
} }
@ -139,8 +141,9 @@ func (e *StorageEngine) get(prm GetPrm) (GetRes, error) {
if obj == nil { if obj == nil {
return GetRes{}, outError return GetRes{}, outError
} }
e.reportShardError(shardWithMeta, "meta info was present, but object is missing", e.log.Warn("meta info was present, but object is missing",
metaError, zap.Stringer("address", prm.addr)) zap.String("err", metaError.Error()),
zap.Stringer("address", prm.addr))
} }
return GetRes{ return GetRes{

View file

@ -112,7 +112,9 @@ func (e *StorageEngine) head(prm HeadPrm) (HeadRes, error) {
return true // stop, return it back return true // stop, return it back
default: default:
e.reportShardError(sh, "could not head object from shard", err) if res.FromMeta() {
e.reportShardError(sh, sh.metaErrorCount, "could not head object from shard", err)
}
return false return false
} }
} }

View file

@ -18,7 +18,7 @@ func (e *StorageEngine) DumpInfo() (i Info) {
for _, sh := range e.shards { for _, sh := range e.shards {
info := sh.DumpInfo() info := sh.DumpInfo()
info.ErrorCount = sh.errorCount.Load() info.ErrorCount = sh.metaErrorCount.Load()
i.Shards = append(i.Shards, info) i.Shards = append(i.Shards, info)
} }

View file

@ -108,6 +108,11 @@ func (e *StorageEngine) inhumeAddr(addr oid.Address, prm shard.InhumePrm, checkE
} }
}() }()
if sh.GetMode() != shard.ModeReadWrite {
// Inhume is a modifying operation on metabase, so return here.
return false
}
if checkExists { if checkExists {
existPrm.WithAddress(addr) existPrm.WithAddress(addr)
exRes, err := sh.Exists(existPrm) exRes, err := sh.Exists(existPrm)
@ -120,7 +125,9 @@ func (e *StorageEngine) inhumeAddr(addr oid.Address, prm shard.InhumePrm, checkE
var siErr *objectSDK.SplitInfoError var siErr *objectSDK.SplitInfoError
if !errors.As(err, &siErr) { if !errors.As(err, &siErr) {
e.reportShardError(sh, "could not check for presents in shard", err) if exRes.FromMeta() {
e.reportShardError(sh, sh.metaErrorCount, "could not check for presents in shard", err)
}
return return
} }
@ -132,13 +139,12 @@ func (e *StorageEngine) inhumeAddr(addr oid.Address, prm shard.InhumePrm, checkE
_, err := sh.Inhume(prm) _, err := sh.Inhume(prm)
if err != nil { if err != nil {
e.reportShardError(sh, "could not inhume object in shard", err)
if errors.As(err, &errLocked) { if errors.As(err, &errLocked) {
status = 1 status = 1
return true return true
} }
e.reportShardError(sh, sh.metaErrorCount, "could not inhume object in shard", err)
return false return false
} }

View file

@ -72,7 +72,10 @@ func (e *StorageEngine) lockSingle(idCnr cid.ID, locker, locked oid.ID, checkExi
if err != nil { if err != nil {
var siErr *objectSDK.SplitInfoError var siErr *objectSDK.SplitInfoError
if !errors.As(err, &siErr) { if !errors.As(err, &siErr) {
e.reportShardError(sh, "could not check locked object for presence in shard", err) // In non-degraded mode the error originated from the metabase.
if exRes.FromMeta() {
e.reportShardError(sh, sh.metaErrorCount, "could not check locked object for presence in shard", err)
}
return return
} }
@ -84,7 +87,7 @@ func (e *StorageEngine) lockSingle(idCnr cid.ID, locker, locked oid.ID, checkExi
err := sh.Lock(idCnr, locker, []oid.ID{locked}) err := sh.Lock(idCnr, locker, []oid.ID{locked})
if err != nil { if err != nil {
e.reportShardError(sh, "could not lock object in shard", err) e.reportShardError(sh, sh.metaErrorCount, "could not lock object in shard", err)
if errors.As(err, &errIrregular) { if errors.As(err, &errIrregular) {
status = 1 status = 1

View file

@ -76,6 +76,9 @@ func (e *StorageEngine) put(prm PutPrm) (PutRes, error) {
exists, err := sh.Exists(existPrm) exists, err := sh.Exists(existPrm)
if err != nil { if err != nil {
if exists.FromMeta() {
e.reportShardError(sh, sh.metaErrorCount, "could not check object existence", err)
}
return // this is not ErrAlreadyRemoved error so we can go to the next shard return // this is not ErrAlreadyRemoved error so we can go to the next shard
} }
@ -101,12 +104,20 @@ func (e *StorageEngine) put(prm PutPrm) (PutRes, error) {
var putPrm shard.PutPrm var putPrm shard.PutPrm
putPrm.WithObject(prm.obj) putPrm.WithObject(prm.obj)
_, err = sh.Put(putPrm) var res shard.PutRes
res, err = sh.Put(putPrm)
if err != nil { if err != nil {
if res.FromMeta() {
e.reportShardError(sh, sh.metaErrorCount, "could not put object in shard", err)
return
} else if res.FromBlobstor() {
e.reportShardError(sh, sh.writeErrorCount, "could not put object in shard", err)
return
} else {
e.log.Warn("could not put object in shard", e.log.Warn("could not put object in shard",
zap.Stringer("shard", sh.ID()), zap.Stringer("shard", sh.ID()),
zap.String("error", err.Error()), zap.String("error", err.Error()))
) }
return return
} }

View file

@ -126,7 +126,9 @@ func (e *StorageEngine) getRange(prm RngPrm) (RngRes, error) {
return true // stop, return it back return true // stop, return it back
default: default:
e.reportShardError(sh, "could not get object from shard", err) if !res.HasMeta() {
e.reportShardError(sh, sh.metaErrorCount, "could not get object from shard", err)
}
return false return false
} }
} }
@ -162,7 +164,8 @@ func (e *StorageEngine) getRange(prm RngPrm) (RngRes, error) {
if obj == nil { if obj == nil {
return RngRes{}, outError return RngRes{}, outError
} }
e.reportShardError(shardWithMeta, "meta info was present, but object is missing", e.reportShardError(shardWithMeta, shardWithMeta.metaErrorCount,
"meta info was present, but object is missing",
metaError, metaError,
zap.Stringer("address", prm.addr), zap.Stringer("address", prm.addr),
) )

View file

@ -68,7 +68,7 @@ func (e *StorageEngine) _select(prm SelectPrm) (SelectRes, error) {
e.iterateOverUnsortedShards(func(sh hashedShard) (stop bool) { e.iterateOverUnsortedShards(func(sh hashedShard) (stop bool) {
res, err := sh.Select(shPrm) res, err := sh.Select(shPrm)
if err != nil { if err != nil {
e.reportShardError(sh, "could not select objects from shard", err) e.reportShardError(sh, sh.metaErrorCount, "could not select objects from shard", err)
return false return false
} }
@ -113,7 +113,7 @@ func (e *StorageEngine) list(limit uint64) (SelectRes, error) {
e.iterateOverUnsortedShards(func(sh hashedShard) (stop bool) { e.iterateOverUnsortedShards(func(sh hashedShard) (stop bool) {
res, err := sh.List() // consider limit result of shard iterator res, err := sh.List() // consider limit result of shard iterator
if err != nil { if err != nil {
e.reportShardError(sh, "could not select objects from shard", err) e.reportShardError(sh, sh.metaErrorCount, "could not select objects from shard", err)
} else { } else {
for _, addr := range res.AddressList() { // save only unique values for _, addr := range res.AddressList() { // save only unique values
if _, ok := uniqueMap[addr.EncodeToString()]; !ok { if _, ok := uniqueMap[addr.EncodeToString()]; !ok {

View file

@ -50,7 +50,8 @@ func (e *StorageEngine) AddShard(opts ...shard.Option) (*shard.ID, error) {
} }
e.shards[strID] = shardWrapper{ e.shards[strID] = shardWrapper{
errorCount: atomic.NewUint32(0), metaErrorCount: atomic.NewUint32(0),
writeErrorCount: atomic.NewUint32(0),
Shard: sh, Shard: sh,
} }
@ -135,7 +136,8 @@ func (e *StorageEngine) SetShardMode(id *shard.ID, m shard.Mode, resetErrorCount
for shID, sh := range e.shards { for shID, sh := range e.shards {
if id.String() == shID { if id.String() == shID {
if resetErrorCounter { if resetErrorCounter {
sh.errorCount.Store(0) sh.metaErrorCount.Store(0)
sh.writeErrorCount.Store(0)
} }
return sh.SetMode(m) return sh.SetMode(m)
} }

View file

@ -21,7 +21,7 @@ func (e *StorageEngine) TreeMove(d pilorama.CIDDescriptor, treeID string, m *pil
if errors.Is(err, shard.ErrReadOnlyMode) { if errors.Is(err, shard.ErrReadOnlyMode) {
return nil, err return nil, err
} }
e.reportShardError(sh, "can't perform `TreeMove`", err, e.reportShardError(sh, sh.writeErrorCount, "can't perform `TreeMove`", err,
zap.Stringer("cid", d.CID), zap.Stringer("cid", d.CID),
zap.String("tree", treeID)) zap.String("tree", treeID))
continue continue
@ -41,7 +41,7 @@ func (e *StorageEngine) TreeAddByPath(d pilorama.CIDDescriptor, treeID string, a
if errors.Is(err, shard.ErrReadOnlyMode) { if errors.Is(err, shard.ErrReadOnlyMode) {
return nil, err return nil, err
} }
e.reportShardError(sh, "can't perform `TreeAddByPath`", err, e.reportShardError(sh, sh.writeErrorCount, "can't perform `TreeAddByPath`", err,
zap.Stringer("cid", d.CID), zap.Stringer("cid", d.CID),
zap.String("tree", treeID)) zap.String("tree", treeID))
continue continue
@ -60,7 +60,7 @@ func (e *StorageEngine) TreeApply(d pilorama.CIDDescriptor, treeID string, m *pi
if errors.Is(err, shard.ErrReadOnlyMode) { if errors.Is(err, shard.ErrReadOnlyMode) {
return err return err
} }
e.reportShardError(sh, "can't perform `TreeApply`", err, e.reportShardError(sh, sh.writeErrorCount, "can't perform `TreeApply`", err,
zap.Stringer("cid", d.CID), zap.Stringer("cid", d.CID),
zap.String("tree", treeID)) zap.String("tree", treeID))
continue continue
@ -79,9 +79,9 @@ func (e *StorageEngine) TreeGetByPath(cid cidSDK.ID, treeID string, attr string,
nodes, err = sh.TreeGetByPath(cid, treeID, attr, path, latest) nodes, err = sh.TreeGetByPath(cid, treeID, attr, path, latest)
if err != nil { if err != nil {
if !errors.Is(err, pilorama.ErrTreeNotFound) { if !errors.Is(err, pilorama.ErrTreeNotFound) {
e.reportShardError(sh, "can't perform `TreeGetByPath`", err, //e.reportShardError(sh, "can't perform `TreeGetByPath`", err,
zap.Stringer("cid", cid), // zap.Stringer("cid", cid),
zap.String("tree", treeID)) // zap.String("tree", treeID))
} }
continue continue
} }
@ -99,7 +99,7 @@ func (e *StorageEngine) TreeGetMeta(cid cidSDK.ID, treeID string, nodeID piloram
m, p, err = sh.TreeGetMeta(cid, treeID, nodeID) m, p, err = sh.TreeGetMeta(cid, treeID, nodeID)
if err != nil { if err != nil {
if !errors.Is(err, pilorama.ErrTreeNotFound) { if !errors.Is(err, pilorama.ErrTreeNotFound) {
e.reportShardError(sh, "can't perform `TreeGetMeta`", err, e.reportShardError(sh, sh.writeErrorCount, "can't perform `TreeGetMeta`", err,
zap.Stringer("cid", cid), zap.Stringer("cid", cid),
zap.String("tree", treeID)) zap.String("tree", treeID))
} }
@ -118,9 +118,9 @@ func (e *StorageEngine) TreeGetChildren(cid cidSDK.ID, treeID string, nodeID pil
nodes, err = sh.TreeGetChildren(cid, treeID, nodeID) nodes, err = sh.TreeGetChildren(cid, treeID, nodeID)
if err != nil { if err != nil {
if !errors.Is(err, pilorama.ErrTreeNotFound) { if !errors.Is(err, pilorama.ErrTreeNotFound) {
e.reportShardError(sh, "can't perform `TreeGetChildren`", err, //e.reportShardError(sh, "can't perform `TreeGetChildren`", err,
zap.Stringer("cid", cid), // zap.Stringer("cid", cid),
zap.String("tree", treeID)) // zap.String("tree", treeID))
} }
continue continue
} }
@ -137,9 +137,9 @@ func (e *StorageEngine) TreeGetOpLog(cid cidSDK.ID, treeID string, height uint64
lm, err = sh.TreeGetOpLog(cid, treeID, height) lm, err = sh.TreeGetOpLog(cid, treeID, height)
if err != nil { if err != nil {
if !errors.Is(err, pilorama.ErrTreeNotFound) { if !errors.Is(err, pilorama.ErrTreeNotFound) {
e.reportShardError(sh, "can't perform `TreeGetOpLog`", err, //e.reportShardError(sh, "can't perform `TreeGetOpLog`", err,
zap.Stringer("cid", cid), // zap.Stringer("cid", cid),
zap.String("tree", treeID)) // zap.String("tree", treeID))
} }
continue continue
} }

View file

@ -30,7 +30,7 @@ func (s *Shard) handleMetabaseFailure(stage string, err error) error {
// Open opens all Shard's components. // Open opens all Shard's components.
func (s *Shard) Open() error { func (s *Shard) Open() error {
components := []interface{ Open() error }{ components := []interface{ Open() error }{
s.blobStor, s.pilorama, s.blobStor, s.metaBase, s.pilorama,
} }
if s.hasWriteCache() { if s.hasWriteCache() {
@ -69,6 +69,8 @@ func (s *Shard) Init() error {
var components []initializer var components []initializer
metaIndex := -1
if s.GetMode() != ModeDegraded { if s.GetMode() != ModeDegraded {
var initMetabase initializer var initMetabase initializer
@ -78,6 +80,7 @@ func (s *Shard) Init() error {
initMetabase = s.metaBase initMetabase = s.metaBase
} }
metaIndex = 1
components = []initializer{ components = []initializer{
s.blobStor, initMetabase, s.pilorama, s.blobStor, initMetabase, s.pilorama,
} }
@ -89,9 +92,9 @@ func (s *Shard) Init() error {
components = append(components, s.writeCache) components = append(components, s.writeCache)
} }
for _, component := range components { for i, component := range components {
if err := component.Init(); err != nil { if err := component.Init(); err != nil {
if component == s.metaBase { if i == metaIndex {
err = s.handleMetabaseFailure("init", err) err = s.handleMetabaseFailure("init", err)
if err != nil { if err != nil {
return err return err

View file

@ -29,7 +29,8 @@ func (p *DeletePrm) WithAddresses(addr ...oid.Address) {
// Delete removes data from the shard's writeCache, metaBase and // Delete removes data from the shard's writeCache, metaBase and
// blobStor. // blobStor.
func (s *Shard) Delete(prm DeletePrm) (DeleteRes, error) { func (s *Shard) Delete(prm DeletePrm) (DeleteRes, error) {
if s.GetMode() != ModeReadWrite { mode := s.GetMode()
if s.GetMode()&ModeReadOnly != 0 {
return DeleteRes{}, ErrReadOnlyMode return DeleteRes{}, ErrReadOnlyMode
} }
@ -61,10 +62,13 @@ func (s *Shard) Delete(prm DeletePrm) (DeleteRes, error) {
} }
} }
err := meta.Delete(s.metaBase, prm.addr...) var err error
if mode&ModeDegraded == 0 { // Skip metabase errors in degraded mode.
err = meta.Delete(s.metaBase, prm.addr...)
if err != nil { if err != nil {
return DeleteRes{}, err // stop on metabase error ? return DeleteRes{}, err // stop on metabase error ?
} }
}
for i := range prm.addr { // delete small object for i := range prm.addr { // delete small object
if id, ok := smalls[prm.addr[i]]; ok { if id, ok := smalls[prm.addr[i]]; ok {

View file

@ -15,6 +15,7 @@ type ExistsPrm struct {
// ExistsRes groups the resulting values of Exists operation. // ExistsRes groups the resulting values of Exists operation.
type ExistsRes struct { type ExistsRes struct {
ex bool ex bool
metaErr bool
} }
// WithAddress is an Exists option to set object checked for existence. // WithAddress is an Exists option to set object checked for existence.
@ -31,6 +32,11 @@ func (p ExistsRes) Exists() bool {
return p.ex return p.ex
} }
// FromMeta returns true if the error resulted from the metabase.
func (p ExistsRes) FromMeta() bool {
return p.metaErr
}
// Exists checks if object is presented in shard. // Exists checks if object is presented in shard.
// //
// Returns any error encountered that does not allow to // Returns any error encountered that does not allow to
@ -38,11 +44,16 @@ func (p ExistsRes) Exists() bool {
// //
// Returns an error of type apistatus.ObjectAlreadyRemoved if object has been marked as removed. // Returns an error of type apistatus.ObjectAlreadyRemoved if object has been marked as removed.
func (s *Shard) Exists(prm ExistsPrm) (ExistsRes, error) { func (s *Shard) Exists(prm ExistsPrm) (ExistsRes, error) {
exists, err := meta.Exists(s.metaBase, prm.addr) var exists bool
if err != nil { var err error
// If the shard is in degraded mode, try to consult blobstor directly.
// Otherwise, just return an error. mode := s.GetMode()
if s.GetMode() == ModeDegraded { if mode&ModeDegraded == 0 { // In Degraded mode skip metabase consulting.
exists, err = meta.Exists(s.metaBase, prm.addr)
}
metaErr := err != nil
if err != nil && mode&ModeDegraded != 0 {
var p blobstor.ExistsPrm var p blobstor.ExistsPrm
p.SetAddress(prm.addr) p.SetAddress(prm.addr)
@ -53,11 +64,13 @@ func (s *Shard) Exists(prm ExistsPrm) (ExistsRes, error) {
zap.Stringer("address", prm.addr), zap.Stringer("address", prm.addr),
zap.String("error", err.Error())) zap.String("error", err.Error()))
err = nil err = nil
} } else if err == nil {
err = bErr
} }
} }
return ExistsRes{ return ExistsRes{
ex: exists, ex: exists,
metaErr: metaErr,
}, err }, err
} }

View file

@ -77,7 +77,6 @@ func (s *Shard) Get(prm GetPrm) (GetRes, error) {
return res.Object(), nil return res.Object(), nil
} }
small = func(stor *blobstor.BlobStor, id *blobovnicza.ID) (*objectSDK.Object, error) { small = func(stor *blobstor.BlobStor, id *blobovnicza.ID) (*objectSDK.Object, error) {
var getSmallPrm blobstor.GetSmallPrm var getSmallPrm blobstor.GetSmallPrm
getSmallPrm.SetAddress(prm.addr) getSmallPrm.SetAddress(prm.addr)

View file

@ -18,6 +18,7 @@ type HeadPrm struct {
// HeadRes groups the resulting values of Head operation. // HeadRes groups the resulting values of Head operation.
type HeadRes struct { type HeadRes struct {
obj *objectSDK.Object obj *objectSDK.Object
meta bool
} }
// WithAddress is a Head option to set the address of the requested object. // WithAddress is a Head option to set the address of the requested object.
@ -43,6 +44,11 @@ func (r HeadRes) Object() *objectSDK.Object {
return r.obj return r.obj
} }
// FromMeta returns true if the error is related to the metabase.
func (r HeadRes) FromMeta() bool {
return r.meta
}
// Head reads header of the object from the shard. // Head reads header of the object from the shard.
// //
// Returns any error encountered. // Returns any error encountered.
@ -67,13 +73,25 @@ func (s *Shard) Head(prm HeadPrm) (HeadRes, error) {
// otherwise object seems to be flushed to metabase // otherwise object seems to be flushed to metabase
} }
if s.GetMode()&ModeDegraded != 0 { // In degraded mode, fallback to blobstor.
var getPrm GetPrm
getPrm.WithIgnoreMeta(true)
getPrm.WithAddress(getPrm.addr)
res, err := s.Get(getPrm)
if err != nil {
return HeadRes{}, err
}
return HeadRes{obj: res.obj.CutPayload()}, nil
}
var headParams meta.GetPrm var headParams meta.GetPrm
headParams.WithAddress(prm.addr) headParams.WithAddress(prm.addr)
headParams.WithRaw(prm.raw) headParams.WithRaw(prm.raw)
res, err := s.metaBase.Get(headParams) res, err := s.metaBase.Get(headParams)
if err != nil { if err != nil {
return HeadRes{}, err return HeadRes{meta: true}, err
} }
return HeadRes{ return HeadRes{

View file

@ -15,7 +15,10 @@ type PutPrm struct {
} }
// PutRes groups the resulting values of Put operation. // PutRes groups the resulting values of Put operation.
type PutRes struct{} type PutRes struct {
metaErr bool
blobErr bool
}
// WithObject is a Put option to set object to save. // WithObject is a Put option to set object to save.
func (p *PutPrm) WithObject(obj *object.Object) { func (p *PutPrm) WithObject(obj *object.Object) {
@ -24,6 +27,14 @@ func (p *PutPrm) WithObject(obj *object.Object) {
} }
} }
func (r *PutRes) FromMeta() bool {
return r.metaErr
}
func (r *PutRes) FromBlobstor() bool {
return r.blobErr
}
// Put saves the object in shard. // Put saves the object in shard.
// //
// Returns any error encountered that // Returns any error encountered that
@ -31,7 +42,8 @@ func (p *PutPrm) WithObject(obj *object.Object) {
// //
// Returns ErrReadOnlyMode error if shard is in "read-only" mode. // Returns ErrReadOnlyMode error if shard is in "read-only" mode.
func (s *Shard) Put(prm PutPrm) (PutRes, error) { func (s *Shard) Put(prm PutPrm) (PutRes, error) {
if s.GetMode() != ModeReadWrite { mode := s.GetMode()
if mode&ModeReadOnly != 0 {
return PutRes{}, ErrReadOnlyMode return PutRes{}, ErrReadOnlyMode
} }
@ -56,14 +68,16 @@ func (s *Shard) Put(prm PutPrm) (PutRes, error) {
) )
if res, err = s.blobStor.Put(putPrm); err != nil { if res, err = s.blobStor.Put(putPrm); err != nil {
return PutRes{}, fmt.Errorf("could not put object to BLOB storage: %w", err) return PutRes{blobErr: true}, fmt.Errorf("could not put object to BLOB storage: %w", err)
} }
if mode&ModeDegraded == 0 { // In degraded mode, skip metabase.
// put to metabase // put to metabase
if err := meta.Put(s.metaBase, prm.obj, res.BlobovniczaID()); err != nil { if err := meta.Put(s.metaBase, prm.obj, res.BlobovniczaID()); err != nil {
// may we need to handle this case in a special way // may we need to handle this case in a special way
// since the object has been successfully written to BlobStor // since the object has been successfully written to BlobStor
return PutRes{}, fmt.Errorf("could not put object to metabase: %w", err) return PutRes{metaErr: true}, fmt.Errorf("could not put object to metabase: %w", err)
}
} }
return PutRes{}, nil return PutRes{}, nil