diff --git a/CHANGELOG.md b/CHANGELOG.md index ecdb9e01f..b151ae2ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +* Made error "Request exceeded a limit on the number of schema operations, try again later" retryable * Fixed deadlock in `Endpoint.String()` method * Added the `AvailabilityPeriod` to the Consumer type in topics diff --git a/internal/xerrors/operation.go b/internal/xerrors/operation.go index dc145543c..73f2747ce 100644 --- a/internal/xerrors/operation.go +++ b/internal/xerrors/operation.go @@ -5,6 +5,7 @@ import ( "fmt" "slices" "strconv" + "strings" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Issue" @@ -187,6 +188,12 @@ func (e *operationError) Type() Type { } return TypeRetryable + case Ydb.StatusIds_GENERIC_ERROR: + if e.hasSchemaOperationsLimitExceeded() { + return TypeRetryable + } + + return TypeUndefined default: return TypeUndefined } @@ -198,6 +205,12 @@ func (e *operationError) hasIssueCodes(codes ...Ydb.StatusIds_StatusCode) bool { }) } +func (e *operationError) hasSchemaOperationsLimitExceeded() bool { + return iterateByIssues(e, func(message string, code Ydb.StatusIds_StatusCode, severity uint32) (stop bool) { + return strings.Contains(message, "Request exceeded a limit on the number of schema operations, try again later") + }) +} + func (e *operationError) BackoffType() backoff.Type { switch e.code { case Ydb.StatusIds_OVERLOADED: @@ -214,6 +227,12 @@ func (e *operationError) BackoffType() backoff.Type { } return backoff.TypeFast + case Ydb.StatusIds_GENERIC_ERROR: + if e.hasSchemaOperationsLimitExceeded() { + return backoff.TypeSlow + } + + return backoff.TypeNoBackoff default: return backoff.TypeNoBackoff } diff --git a/internal/xerrors/operation_test.go b/internal/xerrors/operation_test.go index 9688bc5d9..34e3e0ae6 100644 --- a/internal/xerrors/operation_test.go +++ b/internal/xerrors/operation_test.go @@ -7,6 +7,8 @@ import ( "github.com/stretchr/testify/require" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Issue" + + "github.com/ydb-platform/ydb-go-sdk/v3/internal/backoff" ) func TestIsOperationError(t *testing.T) { @@ -182,3 +184,51 @@ func Test_operationError_Error(t *testing.T) { }) } } + +func Test_operationError_SchemaOperationsLimitExceeded(t *testing.T) { + for _, tt := range []struct { + err error + expectedType Type + expectedBackoff backoff.Type + }{ + { + err: Operation( + WithStatusCode(Ydb.StatusIds_GENERIC_ERROR), + WithIssues([]*Ydb_Issue.IssueMessage{{ + Message: "Request exceeded a limit on the number of schema operations, try again later", + }}), + ), + expectedType: TypeRetryable, + expectedBackoff: backoff.TypeSlow, + }, + { + err: Operation( + WithStatusCode(Ydb.StatusIds_GENERIC_ERROR), + WithIssues([]*Ydb_Issue.IssueMessage{{ + Message: "Some other error message", + }}), + ), + expectedType: TypeUndefined, + expectedBackoff: backoff.TypeNoBackoff, + }, + { + err: Operation( + WithStatusCode(Ydb.StatusIds_GENERIC_ERROR), + WithIssues([]*Ydb_Issue.IssueMessage{{ + Issues: []*Ydb_Issue.IssueMessage{{ + Message: "Request exceeded a limit on the number of schema operations, try again later", + }}, + }}), + ), + expectedType: TypeRetryable, + expectedBackoff: backoff.TypeSlow, + }, + } { + t.Run("", func(t *testing.T) { + var op *operationError + require.ErrorAs(t, tt.err, &op) + require.Equal(t, tt.expectedType, op.Type()) + require.Equal(t, tt.expectedBackoff, op.BackoffType()) + }) + } +}