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:

    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 the retry.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 returns nil, 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(true),
          )
          if err != nil {
              panic(err)
          }
      }
      
    • The db.Table() table query service immediately provides the table.Client programming interface that uses the retry package and tracks the lifetime of the YDB sessions.
      Two public functions are available to the user: db.Table().Do(ctx, op) (where op provides a session) and db.Table().DoTx(ctx, op) (where op 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 by op.

    • Queries to other YDB services (db.Scripting(), db.Scheme(), db.Coordination(), db.Ratelimiter(), and db.Discovery()) also use the retry.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 to 10.
    • retryNotFound(boolean retryNotFound): The option to retry operations that return the NOT_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 the Function<Session, CompletableFuture<Status>> fn lambda as an argument.
    • CompletableFuture<Result<T>> supplyResult: Run an operation that returns data. Takes the Function<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()
              );
          }
      }