[#1143] shard: Introduce explicit Degraded mode

`Degraded` mode is set automatically after error counter is over the
threshold. `ReadOnly` mode can still be set by an administrator.

Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
Evgenii Stratonikov 2022-03-17 14:55:25 +03:00 committed by fyrchik
parent 9eb70c18c3
commit 6472a170eb
21 changed files with 52 additions and 18 deletions

View file

@ -78,6 +78,7 @@ const (
shardModeReadOnly = "read-only" shardModeReadOnly = "read-only"
shardModeReadWrite = "read-write" shardModeReadWrite = "read-write"
shardModeDegraded = "degraded"
) )
const ( const (
@ -124,9 +125,10 @@ func initControlSetShardModeCmd() {
flags.String(controlRPC, controlRPCDefault, controlRPCUsage) flags.String(controlRPC, controlRPCDefault, controlRPCUsage)
flags.StringVarP(&shardID, shardIDFlag, "", "", "ID of the shard in base58 encoding") flags.StringVarP(&shardID, shardIDFlag, "", "", "ID of the shard in base58 encoding")
flags.StringVarP(&shardMode, shardModeFlag, "", "", flags.StringVarP(&shardMode, shardModeFlag, "", "",
fmt.Sprintf("new shard mode keyword ('%s', '%s')", fmt.Sprintf("new shard mode keyword ('%s', '%s', '%s')",
shardModeReadWrite, shardModeReadWrite,
shardModeReadOnly, shardModeReadOnly,
shardModeDegraded,
), ),
) )
flags.Bool(shardClearErrorsFlag, false, "Set shard error count to 0") flags.Bool(shardClearErrorsFlag, false, "Set shard error count to 0")
@ -490,6 +492,8 @@ func prettyPrintShards(cmd *cobra.Command, ii []*control.ShardInfo) {
mode = "read-write" mode = "read-write"
case control.ShardMode_READ_ONLY: case control.ShardMode_READ_ONLY:
mode = "read-only" mode = "read-only"
case control.ShardMode_DEGRADED:
mode = "degraded"
default: default:
mode = "unknown" mode = "unknown"
} }
@ -526,6 +530,8 @@ func setShardMode(cmd *cobra.Command, _ []string) {
mode = control.ShardMode_READ_WRITE mode = control.ShardMode_READ_WRITE
case shardModeReadOnly: case shardModeReadOnly:
mode = control.ShardMode_READ_ONLY mode = control.ShardMode_READ_ONLY
case shardModeDegraded:
mode = control.ShardMode_DEGRADED
} }
req := new(control.SetShardModeRequest) req := new(control.SetShardModeRequest)

View file

@ -77,6 +77,8 @@ func (x *Config) Mode() (m shard.Mode) {
m = shard.ModeReadWrite m = shard.ModeReadWrite
case "read-only": case "read-only":
m = shard.ModeReadOnly m = shard.ModeReadOnly
case "degraded":
m = shard.ModeDegraded
default: default:
panic(fmt.Sprintf("unknown shard mode: %s", s)) panic(fmt.Sprintf("unknown shard mode: %s", s))
} }

View file

@ -50,13 +50,13 @@ func (e *StorageEngine) reportShardError(
return return
} }
err = sh.SetMode(shard.ModeReadOnly) err = sh.SetMode(shard.ModeDegraded)
if err != nil { if err != nil {
e.log.Error("failed to move shard in read-only mode", e.log.Error("failed to move shard in degraded mode",
zap.Uint32("error count", errCount), zap.Uint32("error count", errCount),
zap.Error(err)) zap.Error(err))
} else { } else {
e.log.Info("shard is moved in read-only due to error threshold", e.log.Info("shard is moved in degraded mode due to error threshold",
zap.Stringer("shard_id", sh.ID()), zap.Stringer("shard_id", sh.ID()),
zap.Uint32("error count", errCount)) zap.Uint32("error count", errCount))
} }

View file

@ -118,7 +118,7 @@ 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.ModeReadOnly) checkShardState(t, e, id[0], errThreshold+i, shard.ModeDegraded)
checkShardState(t, e, id[1], 0, shard.ModeReadWrite) checkShardState(t, e, id[1], 0, shard.ModeReadWrite)
} }
@ -185,7 +185,7 @@ func TestBlobstorFailback(t *testing.T) {
require.True(t, errors.Is(err, object.ErrRangeOutOfBounds), "got: %v", err) require.True(t, errors.Is(err, object.ErrRangeOutOfBounds), "got: %v", err)
} }
checkShardState(t, e, id[0], 4, shard.ModeReadOnly) checkShardState(t, e, id[0], 4, shard.ModeDegraded)
checkShardState(t, e, id[1], 0, shard.ModeReadWrite) checkShardState(t, e, id[1], 0, shard.ModeReadWrite)
} }

View file

@ -31,7 +31,7 @@ func (p *DeletePrm) WithAddresses(addr ...*addressSDK.Address) *DeletePrm {
// 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() == ModeReadOnly { if s.GetMode() != ModeReadWrite {
return nil, ErrReadOnlyMode return nil, ErrReadOnlyMode
} }

View file

@ -174,7 +174,7 @@ func (gc *gc) stop() {
// with GC-marked graves. // with GC-marked graves.
// Does nothing if shard is in "read-only" mode. // Does nothing if shard is in "read-only" mode.
func (s *Shard) removeGarbage() { func (s *Shard) removeGarbage() {
if s.GetMode() == ModeReadOnly { if s.GetMode() != ModeReadWrite {
return return
} }

View file

@ -51,7 +51,7 @@ func (p *InhumePrm) MarkAsGarbage(addr ...*addressSDK.Address) *InhumePrm {
// //
// Returns ErrReadOnlyMode error if shard is in "read-only" mode. // Returns ErrReadOnlyMode error if shard is in "read-only" mode.
func (s *Shard) Inhume(prm *InhumePrm) (*InhumeRes, error) { func (s *Shard) Inhume(prm *InhumePrm) (*InhumeRes, error) {
if s.GetMode() == ModeReadOnly { if s.GetMode() != ModeReadWrite {
return nil, ErrReadOnlyMode return nil, ErrReadOnlyMode
} }

View file

@ -14,7 +14,7 @@ import (
// //
// Locked list should be unique. Panics if it is empty. // Locked list should be unique. Panics if it is empty.
func (s *Shard) Lock(idCnr cid.ID, locker oid.ID, locked []oid.ID) error { func (s *Shard) Lock(idCnr cid.ID, locker oid.ID, locked []oid.ID) error {
if s.GetMode() == ModeReadOnly { if s.GetMode() != ModeReadWrite {
return ErrReadOnlyMode return ErrReadOnlyMode
} }

View file

@ -21,6 +21,12 @@ const (
// ModeReadOnly is a Mode value for shard that does not // ModeReadOnly is a Mode value for shard that does not
// accept write operation but is readable. // accept write operation but is readable.
ModeReadOnly ModeReadOnly
// ModeDegraded is a Mode value for shard that is set automatically
// after a certain number of errors is encountered. It is the same as
// `ModeReadOnly` but also enables fallback algorithms for getting object
// in case metabase is corrupted.
ModeDegraded
) )
func (m Mode) String() string { func (m Mode) String() string {
@ -31,6 +37,8 @@ func (m Mode) String() string {
return "READ_WRITE" return "READ_WRITE"
case ModeReadOnly: case ModeReadOnly:
return "READ_ONLY" return "READ_ONLY"
case ModeDegraded:
return "DEGRADED"
} }
} }
@ -46,6 +54,8 @@ func (s *Shard) SetMode(m Mode) error {
switch m { switch m {
case ModeReadOnly: case ModeReadOnly:
s.writeCache.SetMode(writecache.ModeReadOnly) s.writeCache.SetMode(writecache.ModeReadOnly)
case ModeDegraded:
s.writeCache.SetMode(writecache.ModeDegraded)
case ModeReadWrite: case ModeReadWrite:
s.writeCache.SetMode(writecache.ModeReadWrite) s.writeCache.SetMode(writecache.ModeReadWrite)
} }

View file

@ -27,7 +27,7 @@ func (p *ToMoveItPrm) WithAddress(addr *addressSDK.Address) *ToMoveItPrm {
// ToMoveIt calls metabase.ToMoveIt method to mark object as relocatable to // ToMoveIt calls metabase.ToMoveIt method to mark object as relocatable to
// another shard. // another shard.
func (s *Shard) ToMoveIt(prm *ToMoveItPrm) (*ToMoveItRes, error) { func (s *Shard) ToMoveIt(prm *ToMoveItPrm) (*ToMoveItRes, error) {
if s.GetMode() == ModeReadOnly { if s.GetMode() != ModeReadWrite {
return nil, ErrReadOnlyMode return nil, ErrReadOnlyMode
} }

View file

@ -33,7 +33,7 @@ func (p *PutPrm) WithObject(obj *object.Object) *PutPrm {
// //
// 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() == ModeReadOnly { if s.GetMode() != ModeReadWrite {
return nil, ErrReadOnlyMode return nil, ErrReadOnlyMode
} }

View file

@ -16,7 +16,7 @@ import (
func (c *cache) Delete(addr *addressSDK.Address) error { func (c *cache) Delete(addr *addressSDK.Address) error {
c.modeMtx.RLock() c.modeMtx.RLock()
defer c.modeMtx.RUnlock() defer c.modeMtx.RUnlock()
if c.mode == ModeReadOnly { if c.readOnly() {
return ErrReadOnly return ErrReadOnly
} }

View file

@ -65,7 +65,7 @@ func (c *cache) flush() {
sz := 0 sz := 0
c.modeMtx.RLock() c.modeMtx.RLock()
if c.mode == ModeReadOnly { if c.readOnly() {
c.modeMtx.RUnlock() c.modeMtx.RUnlock()
time.Sleep(time.Second) time.Sleep(time.Second)
continue continue
@ -127,7 +127,7 @@ func (c *cache) flushBigObjects() {
select { select {
case <-tick.C: case <-tick.C:
c.modeMtx.RLock() c.modeMtx.RLock()
if c.mode == ModeReadOnly { if c.readOnly() {
c.modeMtx.RUnlock() c.modeMtx.RUnlock()
break break
} }

View file

@ -36,7 +36,7 @@ func (p *IterationPrm) WithIgnoreErrors(ignore bool) *IterationPrm {
func (c *cache) Iterate(prm *IterationPrm) error { func (c *cache) Iterate(prm *IterationPrm) error {
c.modeMtx.RLock() c.modeMtx.RLock()
defer c.modeMtx.RUnlock() defer c.modeMtx.RUnlock()
if c.mode != ModeReadOnly { if !c.readOnly() {
return nil return nil
} }

View file

@ -14,6 +14,9 @@ const (
// ModeReadOnly is a mode in which write-cache doesn't flush anything to a metabase. // ModeReadOnly is a mode in which write-cache doesn't flush anything to a metabase.
ModeReadOnly ModeReadOnly
// ModeDegraded is similar to a shard's degraded mode.
ModeDegraded
) )
// ErrReadOnly is returned when Put/Write is performed in a read-only mode. // ErrReadOnly is returned when Put/Write is performed in a read-only mode.
@ -50,3 +53,9 @@ func (c *cache) SetMode(m Mode) {
time.Sleep(time.Second) time.Sleep(time.Second)
} }
} }
// readOnly returns true if current mode is read-only.
// `c.modeMtx` must be taken.
func (c *cache) readOnly() bool {
return c.mode != ModeReadWrite
}

View file

@ -21,7 +21,7 @@ func (c *cache) persistLoop() {
select { select {
case <-tick.C: case <-tick.C:
c.modeMtx.RLock() c.modeMtx.RLock()
if c.mode == ModeReadOnly { if c.readOnly() {
c.modeMtx.RUnlock() c.modeMtx.RUnlock()
continue continue
} }

View file

@ -15,7 +15,7 @@ var ErrBigObject = errors.New("too big object")
func (c *cache) Put(o *objectSDK.Object) error { func (c *cache) Put(o *objectSDK.Object) error {
c.modeMtx.RLock() c.modeMtx.RLock()
defer c.modeMtx.RUnlock() defer c.modeMtx.RUnlock()
if c.mode == ModeReadOnly { if c.readOnly() {
return ErrReadOnly return ErrReadOnly
} }

View file

@ -40,6 +40,8 @@ func (s *Server) ListShards(_ context.Context, req *control.ListShardsRequest) (
mode = control.ShardMode_READ_WRITE mode = control.ShardMode_READ_WRITE
case shard.ModeReadOnly: case shard.ModeReadOnly:
mode = control.ShardMode_READ_ONLY mode = control.ShardMode_READ_ONLY
case shard.ModeDegraded:
mode = control.ShardMode_DEGRADED
default: default:
mode = control.ShardMode_SHARD_MODE_UNDEFINED mode = control.ShardMode_SHARD_MODE_UNDEFINED
} }

View file

@ -29,6 +29,8 @@ func (s *Server) SetShardMode(_ context.Context, req *control.SetShardModeReques
mode = shard.ModeReadWrite mode = shard.ModeReadWrite
case control.ShardMode_READ_ONLY: case control.ShardMode_READ_ONLY:
mode = shard.ModeReadOnly mode = shard.ModeReadOnly
case control.ShardMode_DEGRADED:
mode = shard.ModeDegraded
default: default:
return nil, status.Error(codes.Internal, fmt.Sprintf("unknown shard mode: %s", requestedMode)) return nil, status.Error(codes.Internal, fmt.Sprintf("unknown shard mode: %s", requestedMode))
} }

Binary file not shown.

View file

@ -151,4 +151,7 @@ enum ShardMode {
// Read-only. // Read-only.
READ_ONLY = 2; READ_ONLY = 2;
// Degraded.
DEGRADED = 3;
} }