From 6e4b9a9865a5c04174a4e7084c1b5bed6f4079c1 Mon Sep 17 00:00:00 2001 From: Konstantin Prokopenko Date: Mon, 24 Nov 2025 20:09:10 +0300 Subject: [PATCH 1/4] feat: make schema operations limit exceeded error retryable This change makes the error 'Request exceeded a limit on the number of schema operations, try again later' retryable. The error is detected by checking the error message in the issues list and is treated as a retryable error with slow backoff (similar to OVERLOADED errors). Fixes #1933 --- internal/xerrors/operation.go | 17 ++++++++++ internal/xerrors/operation_test.go | 50 ++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/internal/xerrors/operation.go b/internal/xerrors/operation.go index dc145543c..60c882c8d 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,11 @@ func (e *operationError) Type() Type { } return TypeRetryable + case Ydb.StatusIds_GENERIC_ERROR: + if e.hasSchemaOperationsLimitExceeded() { + return TypeRetryable + } + return TypeUndefined default: return TypeUndefined } @@ -198,6 +204,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 +226,11 @@ 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..3fbcfb941 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()) + }) + } +} From f5a31634a7cfdbad7cf344e3453f2f5674408a30 Mon Sep 17 00:00:00 2001 From: Konstantin Prokopenko Date: Mon, 24 Nov 2025 20:10:30 +0300 Subject: [PATCH 2/4] docs: add changelog entry for schema operations limit retryable error --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fc8cb2f8..e2fcf74b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ ## v3.118.3 +* Made error "Request exceeded a limit on the number of schema operations, try again later" retryable * Fixed `context` checking in `ydb.Open` ## v3.118.2 From b143f5e0f7b6eac36e53ff6573f9b059677ad2e8 Mon Sep 17 00:00:00 2001 From: Konstantin Prokopenko Date: Mon, 24 Nov 2025 20:16:05 +0300 Subject: [PATCH 3/4] fix linter --- internal/xerrors/operation.go | 2 ++ internal/xerrors/operation_test.go | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/internal/xerrors/operation.go b/internal/xerrors/operation.go index 60c882c8d..73f2747ce 100644 --- a/internal/xerrors/operation.go +++ b/internal/xerrors/operation.go @@ -192,6 +192,7 @@ func (e *operationError) Type() Type { if e.hasSchemaOperationsLimitExceeded() { return TypeRetryable } + return TypeUndefined default: return TypeUndefined @@ -230,6 +231,7 @@ func (e *operationError) BackoffType() backoff.Type { 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 3fbcfb941..34e3e0ae6 100644 --- a/internal/xerrors/operation_test.go +++ b/internal/xerrors/operation_test.go @@ -187,8 +187,8 @@ func Test_operationError_Error(t *testing.T) { func Test_operationError_SchemaOperationsLimitExceeded(t *testing.T) { for _, tt := range []struct { - err error - expectedType Type + err error + expectedType Type expectedBackoff backoff.Type }{ { @@ -198,7 +198,7 @@ func Test_operationError_SchemaOperationsLimitExceeded(t *testing.T) { Message: "Request exceeded a limit on the number of schema operations, try again later", }}), ), - expectedType: TypeRetryable, + expectedType: TypeRetryable, expectedBackoff: backoff.TypeSlow, }, { @@ -208,7 +208,7 @@ func Test_operationError_SchemaOperationsLimitExceeded(t *testing.T) { Message: "Some other error message", }}), ), - expectedType: TypeUndefined, + expectedType: TypeUndefined, expectedBackoff: backoff.TypeNoBackoff, }, { @@ -220,7 +220,7 @@ func Test_operationError_SchemaOperationsLimitExceeded(t *testing.T) { }}, }}), ), - expectedType: TypeRetryable, + expectedType: TypeRetryable, expectedBackoff: backoff.TypeSlow, }, } { From 6dcb253f15bfc596ec407d308685341d759161a5 Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Tue, 25 Nov 2025 13:36:38 +0300 Subject: [PATCH 4/4] Apply suggestions from code review --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2fcf74b0..a9d5ce623 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ -## v3.118.3 * Made error "Request exceeded a limit on the number of schema operations, try again later" retryable + +## v3.118.3 * Fixed `context` checking in `ydb.Open` ## v3.118.2