Retrying
Note
The article is being updated.
YDB is a distributed database management system with automatic load scaling.
Routine maintenance can be carried out on the server side, with server racks or entire data centers temporarily shut down.
This may result in errors arising from YDB operation.
There are different response scenarios depending on the error type.
YDB To ensure high database availability, SDKs provide built-in tools for retries, accounting for error types and responses to them.
Below are code examples showing the YDB SDK built-in tools for retries:
Go
Java
In the YDB Go SDK, correct error handling is implemented by several programming interfaces:
The basic logic of error handling is implemented by the helper
retry.Retry
function.
The details of making request retries are hidden as much as possible.
The user can affect the logic of executing theretry.Retry
function in two ways:- Via the context (where you can set the deadline and cancel).
- Via the operation’s idempotency flag
retry.WithIdempotent()
. By default, the operation is considered non-idempotent.
The user passes a custom function to
retry.Retry
that returns an error by its signature.
If the custom function returnsnil
, then repeat queries stop.
If the custom function returns an error, the YDB Go SDK tries to identify this error and executes retries depending on it.Example of code, using `retry.Retry` function:
package main
import (
"context"
"time"
"github.com/ydb-platform/ydb-go-sdk/v3"
"github.com/ydb-platform/ydb-go-sdk/v3/retry"
)
func main() {
db, err := ydb.Open(
ctx,
os.Getenv("YDB_CONNECTION_STRING"),
)
if err != nil {
panic(err)
}
defer func() {
_ = db.Close(ctx)
}()
var cancel context.CancelFunc
// fix deadline for retries
ctx, cancel := context.WithTimeout(ctx, time.Second)
err = retry.Retry(
ctx,
func(ctx context.Context) error {
whoAmI, err := db.Discovery().WhoAmI(ctx)
if err != nil {
return err
}
fmt.Println(whoAmI)
},
retry.WithIdempotent(),
)
if err != nil {
panic(err)
}
}
The
db.Table()
table query service immediately provides thetable.Client
programming interface that uses theretry
package and tracks the lifetime of the YDB sessions.
Two public functions are available to the user:db.Table().Do(ctx, op)
(whereop
provides a session) anddb.Table().DoTx(ctx, op)
(whereop
provides a transaction).
As in the previous case, the user can affect the logic of repeat queries using the context and the idempotence flag, while the YDB Go SDK interprets errors returned byop
.Queries to other YDB services (
db.Scripting()
,db.Scheme()
,db.Coordination()
,db.Ratelimiter()
, anddb.Discovery()
) also use theretry.Retry
function internally to make repeat queries.
In the YDB Java SDK, the request retry mechanism is implemented as the com.yandex.ydb.table.SessionRetryContext
helper class. This class is built using the SessionRetryContext.create
method, where you should pass the implementation of the SessionSupplier
interface (usually, this is an instance of the TableClient
class).
Additionally, the user can set some other options.
maxRetries(int maxRetries)
: The maximum number of operation retries, excluding the first execution. Defaults to10
.retryNotFound(boolean retryNotFound)
: The option to retry operations that return theNOT_FOUND
status. Enabled by default.idempotent(boolean idempotent)
: Indicates if an operation is idempotent. The system will retry idempotent operations for a wider list of errors. Disabled by default.
The SessionRetryContext
class provides two methods to run operations with retries.
CompletableFuture<Status> supplyStatus
: Run an operation that returns a status. Takes theFunction<Session, CompletableFuture<Status>> fn
lambda as an argument.CompletableFuture<Result<T>> supplyResult
: Run an operation that returns data. Takes theFunction<Session, CompletableFutureResult<T>> fn
lambda as an argument.
When using the SessionRetryContext
class, keep in mind that operation retries will be made in the following cases:
The lamda function returns the retryable error code.
While executing the lambda function, the
UnexpectedResultException
with the retryable error code is raised.Snippet of code using SessionRetryContext.supplyStatus:
private void createTable(TableClient tableClient, String database, String tableName) {
SessionRetryContext retryCtx = SessionRetryContext.create(tableClient).build();
TableDescription pets = TableDescription.newBuilder()
.addNullableColumn("species", PrimitiveType.utf8())
.addNullableColumn("name", PrimitiveType.utf8())
.addNullableColumn("color", PrimitiveType.utf8())
.addNullableColumn("price", PrimitiveType.float32())
.setPrimaryKeys("species", "name")
.build();
String tablePath = database + "/" + tableName;
retryCtx.supplyStatus(session -> session.createTable(tablePath, pets))
.join().expect("ok");
}
Snippet of code using SessionRetryContext.supplyResult:
private void selectData(TableClient tableClient, String tableName) {
SessionRetryContext retryCtx = SessionRetryContext.create(tableClient).build();
String selectQuery
= "DECLARE $species AS Utf8;"
+ "DECLARE $name AS Utf8;"
+ "SELECT * FROM " + tableName + " "
+ "WHERE species = $species AND name = $name;";
Params params = Params.of(
"$species", PrimitiveValue.utf8("cat"),
"$name", PrimitiveValue.utf8("Tom")
);
DataQueryResult data = retryCtx
.supplyResult(session -> session.executeDataQuery(selectQuery, TxControl.onlineRo(), params))
.join().expect("ok");
ResultSetReader rsReader = data.getResultSet(0);
logger.info("Result of select query:");
while (rsReader.next()) {
logger.info(" species: {}, name: {}, color: {}, price: {}",
rsReader.getColumn("species").getUtf8(),
rsReader.getColumn("name").getUtf8(),
rsReader.getColumn("color").getUtf8(),
rsReader.getColumn("price").getFloat32()
);
}
}