---
metadata:
  - name: generator
    content: Diplodoc Platform v5.52.0
alternate:
  - https://ydb.tech/docs/en/recipes/ydb-sdk/retry.md?version=v26.1
  - https://ydb.tech/docs/ru/recipes/ydb-sdk/retry.md?version=v26.1
  - href: ru/recipes/ydb-sdk/retry.md
    type: text/markdown
    title: Markdown version
  - href: ../../llms.txt
    type: text/markdown
    title: llms.txt
sourcePath: ru/core/recipes/ydb-sdk/retry.md
---
> **Documentation Index:** Fetch the complete configuration index at https://ydb.tech/docs/ru/llms.txt

# Выполнение повторных попыток

YDB является распределенной СУБД с автоматическим масштабированием под нагрузку.
На серверной стороне могут проводиться работы, серверные стойки или целые дата-центры могут быть временно отключены.
В связи с этим допускаются некоторые ошибки при работе с YDB.
В зависимости от типа ошибки следует по разному реагировать на них.
YDB SDK для обеспечения высокой доступности предоставляют встроенные средства выполнения повторных попыток,
в которых учтены типы ошибок и закреплена реакция на них.

Ниже приведены примеры кода использования встроенных в YDB SDK средств выполнения повторных попыток:

{% list tabs %}

- C++

  {% list tabs %}

  - Native SDK

    В YDB C++ SDK выполнение повторных попыток с корректной обработкой ошибок реализовано в нескольких программных интерфейсах:

    {% cut "Синхронное выполнение повторных попыток" %}

    Для выполнения запросов с автоматическими повторными попытками используется метод `RetryQuerySync`.
    Метод принимает лямбда-функцию, которая получает объект сессии и возвращает результат запроса.
    YDB C++ SDK автоматически анализирует ошибки и выполняет повторные попытки в соответствии с их типом.

    Пример кода, использующего `RetryQuerySync`:

    ```c++
    #include <ydb-cpp-sdk/client/query/client.h>

    void ExecuteQueryWithRetry(NYdb::NQuery::TQueryClient client) {
        auto result = client.RetryQuerySync([](NYdb::NQuery::TSession session) -> NYdb::TStatus {
            auto query = R"(
                SELECT series_id, title
                FROM series
                WHERE series_id = 1;
            )";

            auto result = session.ExecuteQuery(
                query,
                NYdb::NQuery::TTxControl::BeginTx(NYdb::NQuery::TTxSettings::SerializableRW()).CommitTx()
            ).GetValueSync();

            if (!result.IsSuccess()) {
                return result;
            }

            // Обработка результата запроса
            auto resultSet = result.GetResultSet(0);
            NYdb::TResultSetParser parser(resultSet);
            while (parser.TryNextRow()) {
                std::cout << "Series"
                    << ", Id: " << parser.ColumnParser("series_id").GetOptionalUint64().value()
                    << ", Title: " << parser.ColumnParser("title").GetOptionalUtf8().value()
                    << std::endl;
            }

            return result;
        });

        if (!result.IsSuccess()) {
            // Обработка ошибки после всех попыток
            std::cerr << "Query failed: " << result.GetIssues().ToString() << std::endl;
        }
    }
    ```

    {% endcut %}

    {% cut "Асинхронное выполнение повторных попыток" %}

    Для асинхронного выполнения запросов с автоматическими повторными попытками используется метод `RetryQuery`.
    Метод возвращает `NThreading::TFuture`, что позволяет выполнять операции асинхронно.

    Пример кода, использующего `RetryQuery`:

    ```c++
    #include <ydb-cpp-sdk/client/query/client.h>

    void ExecuteQueryWithRetryAsync(NYdb::NQuery::TQueryClient client) {
        auto future = client.RetryQuery([](NYdb::NQuery::TSession session) -> NYdb::TAsyncStatus {
            auto query = R"(
                SELECT series_id, title, release_date
                FROM series
                WHERE series_id = 1;
            )";

            return session.ExecuteQuery(
                query,
                NYdb::NQuery::TTxControl::BeginTx(NYdb::NQuery::TTxSettings::SerializableRW()).CommitTx()
            ).Apply([](const NYdb::NQuery::TAsyncExecuteQueryResult& asyncResult) -> NYdb::TStatus {
                auto result = asyncResult.GetValue();
                if (!result.IsSuccess()) {
                    return result;
                }

                // Обработка результата запроса
                auto resultSet = result.GetResultSet(0);
                NYdb::TResultSetParser parser(resultSet);
                while (parser.TryNextRow()) {
                    std::cout << "Series"
                        << ", Id: " << parser.ColumnParser("series_id").GetOptionalUint64().value()
                        << ", Title: " << parser.ColumnParser("title").GetOptionalUtf8().value()
                        << std::endl;
                }

                return result;
            });
        });

        // Ожидание завершения
        auto status = future.GetValueSync();
        if (!status.IsSuccess()) {
            std::cerr << "Query failed: " << status.GetIssues().ToString() << std::endl;
        }
    }
    ```

    {% endcut %}

    {% cut "Выполнение повторных попыток при работе со стриминговыми запросами" %}

    Для выполнения стриминговых запросов с автоматическими повторными попытками используется метод `StreamExecuteQuery`.
    Стриминговые запросы позволяют обрабатывать большие объемы данных, получая результаты частями.

    Пример кода, использующего `RetryQuerySync` со `StreamExecuteQuery`:

    ```c++
    #include <ydb-cpp-sdk/client/query/client.h>

    void StreamQueryWithRetry(NYdb::NQuery::TQueryClient client) {
        auto result = client.RetryQuerySync([](NYdb::NQuery::TSession session) -> NYdb::TStatus {
            auto query = R"(
                SELECT series_id, title, release_date
                FROM series
                WHERE series_id > 0;
            )";

            auto resultStreamQuery = session.StreamExecuteQuery(
                query,
                NYdb::NQuery::TTxControl::NoTx()
            ).GetValueSync();

            if (!resultStreamQuery.IsSuccess()) {
                return resultStreamQuery;
            }

            // Обработка результатов по частям
            bool eos = false;
            while (!eos) {
                auto streamPart = resultStreamQuery.ReadNext().ExtractValueSync();

                if (!streamPart.IsSuccess()) {
                    eos = true;
                    if (!streamPart.EOS()) {
                        return streamPart;
                    }
                    continue;
                }

                if (streamPart.HasResultSet()) {
                    auto rs = streamPart.ExtractResultSet();
                    NYdb::TResultSetParser parser(rs);
                    while (parser.TryNextRow()) {
                        std::cout << "Series"
                            << ", Id: " << parser.ColumnParser("series_id").GetOptionalUint64().value()
                            << ", Title: " << parser.ColumnParser("title").GetOptionalUtf8().value()
                            << std::endl;
                    }
                }
            }

            return resultStreamQuery;
        });

        if (!result.IsSuccess()) {
            std::cerr << "Stream query failed: " << result.GetIssues().ToString() << std::endl;
        }
    }
    ```

    {% endcut %}

    {% cut "Настройка параметров повторных попыток" %}

    Пользователь может настраивать поведение механизма повторных попыток с помощью класса `TRetryOperationSettings`:

    * `MaxRetries(uint32_t)` - максимальное количество повторных попыток (по умолчанию 10)
    * `Idempotent(bool)` - признак идемпотентности операции. Идемпотентные операции повторяются для более широкого списка ошибок
    * `RetryNotFound(bool)` - повторять ли операции, вернувшие статус `NOT_FOUND` (по умолчанию true)
    * `MaxTimeout(TDuration)` - максимальное время выполнения всех попыток
    * `FastBackoffSettings(TBackoffSettings)` - настройки быстрых повторов
    * `SlowBackoffSettings(TBackoffSettings)` - настройки медленных повторов

    Пример использования настроек повторных попыток:

    ```c++
    #include <ydb-cpp-sdk/client/query/client.h>
    #include <ydb-cpp-sdk/client/retry/retry.h>

    void ExecuteWithCustomRetry(NYdb::NQuery::TQueryClient client) {
        auto retrySettings = NYdb::NRetry::TRetryOperationSettings()
            .Idempotent(true)
            .MaxRetries(20)
            .MaxTimeout(TDuration::Seconds(30));

        auto result = client.RetryQuerySync([](NYdb::NQuery::TSession session) -> NYdb::TStatus {
            auto query = R"(
                UPSERT INTO series (series_id, title)
                VALUES (10, "New Series");
            )";

            auto result = session.ExecuteQuery(
                query,
                NYdb::NQuery::TTxControl::BeginTx(NYdb::NQuery::TTxSettings::SerializableRW()).CommitTx()
            ).GetValueSync();

            if (!result.IsSuccess()) {
                return result;
            }

            // Обработка результата запроса
            std::cout << "Query executed successfully" << std::endl;
            return result;
        }, retrySettings);

        if (!result.IsSuccess()) {
            std::cerr << "Operation failed: " << result.GetIssues().ToString() << std::endl;
        }
    }
    ```

    {% endcut %}

  - userver

    В `ydb::TableClient` выполнение повторных попыток с корректной обработкой ошибок реализовано во всех методах. userver автоматически анализирует ошибки и выполняет повторные попытки в соответствии с их типом.

    Пользователь может настраивать поведение механизма повторных попыток с помощью `ydb::OperationSettings` и `ydb::RetryTxSettings`:

    * `retries` - максимальное количество повторных попыток
    * `is_idempotent` - признак идемпотентности операции. Идемпотентные операции повторяются для более широкого списка ошибок
    * `client_timeout_ms` или `timeout_ms` соответственно - максимальное время выполнения всех попыток
    * `get_session_timeout` (актуально только для `ydb::OperationSettings`) - таймаут на получение сессии
    * `get_session_settings`, `commit_settings` и `rollback_settings` (актуально только для `ydb::RetryTxSettings`) - настройки для запросов получения сессии, коммита или отката транзакции

    `ydb::RetryTxSettings` используется только для метода `ydb::TableClient::RetryTx`, который выполняет интерактивную транзакцию с выполнением повторных попыток при ошибках для всей транзакции.

    Секция [`ydb.operation-settings`](https://github.com/userver-framework/userver/blob/develop/ydb/src/ydb/component.yaml) в static config задаёт значения по умолчанию: если при вызове в коде поле не задано (`std::nullopt` или ноль там, где это означает "не задано"), используется значение из конфига, иначе из кода.

    {% cut "static config" %}

    ```yaml
    ydb:
        operation-settings:
            retries: 5
            client-timeout: 2s
            get-session-timeout: 10s
    ```

    {% endcut %}

    ```cpp
    #include <userver/ydb/table.hpp>

    void RetryExamples(ydb::TableClient& client) {
        client.ExecuteQuery(
            ydb::OperationSettings{
                .retries = 7,
                .is_idempotent = true,
            },
            ydb::Query{R"(
                UPSERT INTO series (series_id, title)
                VALUES (10, "New Series");
            )"}
        );

        client.RetryTx(
            ydb::RetryTxSettings{
                .retries = 3,
                .is_idempotent = true,
            },
            [](ydb::TxActor& tx) {
                tx.Execute(ydb::Query{R"(
                    UPSERT INTO series (series_id, title)
                    VALUES (11, "Other Series");
                )"});
            }
        );
    }
    ```

  {% endlist %}

- Go

  {% list tabs %}

  - Native SDK

    В YDB Go SDK корректная обработка ошибок закреплена в нескольких программных интерфейсах:

    {% cut "Функция повторов общего назначения" %}

    Основная логика обработки ошибок реализуется функцией-помощником `retry.Retry`
    Подробности выполнения повторных запросов максимально скрыты.
    Пользователь может влиять на логику работы функции `retry.Retry` двумя способами:

    * через контекст (можно устанавливать deadline и cancel);
    * через флаг идемпотентности операции `retry.WithIdempotent()`. По умолчанию операция считается неидемпотентной.

    Пользователь передает свою функцию в `retry.Retry`, которая по своей сигнатуре должна возвращать ошибку.
    В случае, если из пользовательской функции вернулся `nil`, то повторные запросы прекращаются.
    В случае, если из пользовательской функции вернулась ошибка, YDB Go SDK пытается идентифицировать эту ошибку и в зависимости от нее выполняет повторные попытки.

    Пример кода, использующего функцию `retry.Retry`:

    ```golang
    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 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)
                return nil
            },
            retry.WithIdempotent(true),
        )
        if err != nil {
            panic(err)
        }
    }
    ```

    {% endcut %}

    {% cut "Выполнение повторных попыток при ошибках на объекте сессии YDB" %}

    Для повторной обработки ошибок на уровне сессии сервиса таблиц YDB есть функция `db.Table().Do(ctx, op)`, которая предоставляет подготовленную сессию для выполнения запросов.
    Функция `db.Table().Do(ctx, op)` использует пакет `retry`, а также следит за временем жизни сессий YDB.
    Из пользовательской операции `op` согласно ее сигнатуре требуется возвращать ошибку или `nil`, чтобы драйвер смог по типу ошибки "понять" что нужно делать: поторять операцию или нет, с задержкой или нет, на этой же сессии или новой.
    Пользователь может влиять на логику выполнения повторных запросов через контекст и признак идемпотентности, а YDB Go SDK интерпретирует возвращаемые из `op` ошибки.

    Пример кода, использующего функцию `db.Table().Do(ctx, op)`:

    ```golang
    err := db.Table().Do(ctx, func(ctx context.Context, s table.Session) (err error) {
        desc, err = s.DescribeTableOptions(ctx)
        return
    }, table.WithIdempotent())
    if err != nil {
        return err
    }
    ```

    {% endcut %}

    {% cut "Выполнение повторных попыток при ошибках на объекте интерактивной транзакции YDB" %}

    Для повторной обработки ошибок на уровне интерактивной транзакции сервиса таблиц YDB есть функция `db.Table().DoTx(ctx, txOp)`, которая предоставляет подготовленную транзакцию YDB на сессии для выполнения запросов.
    Функция `db.Table().DoTx(ctx, txOp)` использует пакет `retry`, а также следит за временем жизни сессий YDB.
    Из пользовательской операции `txOp` согласно ее сигнатуре требуется возвращать ошибку или `nil`, чтобы драйвер смог по типу ошибки "понять" что нужно делать: поторять операцию или нет, с задержкой или нет, на этой же транзакции или новой.
    Пользователь может влиять на логику выполнения повторных запросов через контекст и признак идемпотентности, а YDB Go SDK интерпретирует возвращаемые из `op` ошибки.

    Пример кода, использующего функцию `db.Table().DoTx(ctx, op)`:

    ```golang
    err := db.Table().DoTx(ctx, func(ctx context.Context, tx table.TransactionActor) error {
        _, err := tx.Execute(ctx,
            "DECLARE $id AS Int64; INSERT INTO test (id, val) VALUES($id, 'asd')",
            table.NewQueryParameters(table.ValueParam("$id", types.Int64Value(100500))),
        )
        return err
    }, table.WithIdempotent())
    if err != nil {
        return err
    }
    ```

    {% endcut %}

    {% cut "Запросы к остальным сервисам YDB" %}

    (`db.Scripting()`, `db.Scheme()`, `db.Coordination()`, `db.Ratelimiter()`, `db.Discovery()`) также используют внутри себя функцию `retry.Retry` для выполнения повторных запросов и не требуют использования внешних вспомогательных функций для повторов.

    {% endcut %}

  - database/sql

    Стандартный пакет `database/sql` использует внутреннюю логику выполнения повторов на основе тех ошибок, что возвращает конкретная реализация драйвера.
    Так, в [коде](https://github.com/golang/go/tree/master/src/database/sql) пакета `database/sql` во многих местах можно встретить политику повторов из трех попыток:
    - 2 попытки выполнить на существующем соединении или новом (если пул соединений `database/sql` пуст)
    - 1 попытка выполнить на новом соединении.

    В большинстве случаев такой политики повторов достаточно, чтобы пережить временную недоступность нод YDB или проблемы с сессией YDB.

    YDB Go SDK предоставляет специальные функции для гарантированного выполнения пользовательской операции:

    {% cut "Выполнение повторных попыток при ошибках на объекте соединения `*sql.Conn`:" %}

    Для повторной обработки ошибок  на объекте соединения `*sql.Conn` есть вспомогательная функция `retry.Do(ctx, db, op)`, которая предоставляет подготовленное соединение `*sql.Conn` для выполнения запросов.
    В функцию `retry.Do` требуется передать контекст, объект базы данных, а также пользовательскую операцию, которую требуется выполнить.
    Из клиентского кода можно влиять на логику выполнения повторных запросов через контекст и признак идемпотентности, а YDB Go SDK в свою очередь интерпретирует возвращаемые из `op` ошибки.

    Пользовательская операция `op` должна возвращать ошибку или `nil`:

    - в случае, если из пользовательской функции вернулся `nil`, то повторные запросы прекращаются;
    - в случае, если из пользовательской функции вернулась ошибка, YDB Go SDK пытается идентифицировать эту ошибку и в зависимости от нее предпринимает повторные попытки.

    Пример кода, использующего функцию `retry.Do`:

    ```golang
    import (
        "context"
        "database/sql"
        "fmt"
        "log"

        "github.com/ydb-platform/ydb-go-sdk/v3/retry"
    )

    func main() {
        ...
        err = retry.Do(ctx, db, func(ctx context.Context, cc *sql.Conn) (err error) {
            row = cc.QueryRowContext(ctx, `
                    PRAGMA TablePathPrefix("/local");
                    DECLARE $seriesID AS Uint64;
                    DECLARE $seasonID AS Uint64;
                    DECLARE $episodeID AS Uint64;
                    SELECT views FROM episodes WHERE series_id = $seriesID AND season_id = $seasonID AND episode_id = $episodeID;
                `,
                sql.Named("seriesID", uint64(1)),
                sql.Named("seasonID", uint64(1)),
                sql.Named("episodeID", uint64(1)),
            )
            var views sql.NullFloat64
            if err = row.Scan(&views); err != nil {
                return fmt.Errorf("cannot scan views: %w", err)
            }
            if views.Valid {
                return fmt.Errorf("unexpected valid views: %v", views.Float64)
            }
            log.Printf("views = %v", views)
            return row.Err()
        }, retry.WithDoRetryOptions(retry.WithIdempotent(true)))
        if err != nil {
            log.Printf("retry.Do failed: %v\n", err)
        }
    }
    ```

    {% endcut %}

    {% cut "Выполнение повторных попыток при ошибках на объекте интерактивной транзакции `*sql.Tx`:" %}

    Для повторной обработки ошибок  на объекте интерактивной транзакции `*sql.Tx` есть вспомогательная функция `retry.DoTx(ctx, db, op)`, которая предоставляет подготовленную транзакцию `*sql.Tx` для выполнения запросов.
    В функцию `retry.DoTx` требуется передать контекст, объект базы данных, а также пользовательскую операцию, которую требуется выполнить.
    В функцию приходит подготовленная транзакция `*sql.Tx`, на которой следует выполнять запросы к YDB.
    Из клиентского кода можно влиять на логику выполнения повторных запросов через контекст и признак идемпотентности операции, а YDB Go SDK в свою очередь интерпретирует возвращаемые из `op` ошибки.

    Пользовательская операция `op` должна возвращать ошибку или `nil`:

    - в случае, если из пользовательской функции вернулся `nil`, то повторные запросы прекращаются;
    - в случае, если из пользовательской функции вернулась ошибка, YDB Go SDK пытается идентифицировать эту ошибку и в зависимости от нее предпринимает повторные попытки.

    Функция `retry.DoTx` использует режим изоляции read-write транзакции `sql.LevelDefault` по умолчанию, который можно изменить через опцию `retry.WithTxOptions`.

    Пример кода, использующего функцию `retry.Do`:

    ```golang
    import (
        "context"
        "database/sql"
        "fmt"
        "log"

        "github.com/ydb-platform/ydb-go-sdk/v3/retry"
    )

    func main() {
        ...
        err = retry.DoTx(ctx, db, func(ctx context.Context, tx *sql.Tx) error {
            row := tx.QueryRowContext(ctx,`
                    PRAGMA TablePathPrefix("/local");
                    DECLARE $seriesID AS Uint64;
                    DECLARE $seasonID AS Uint64;
                    DECLARE $episodeID AS Uint64;
                    SELECT views FROM episodes WHERE series_id = $seriesID AND season_id = $seasonID AND episode_id = $episodeID;
                `,
                sql.Named("seriesID", uint64(1)),
                sql.Named("seasonID", uint64(1)),
                sql.Named("episodeID", uint64(1)),
            )
            var views sql.NullFloat64
            if err = row.Scan(&views); err != nil {
                return fmt.Errorf("cannot select current views: %w", err)
            }
            if !views.Valid {
                return fmt.Errorf("unexpected invalid views: %v", views)
            }
            t.Logf("views = %v", views)
            if views.Float64 != 1 {
                return fmt.Errorf("unexpected views value: %v", views)
            }
            return nil
        }, retry.WithDoTxRetryOptions(retry.WithIdempotent(true)), retry.WithTxOptions(&sql.TxOptions{
            Isolation: sql.LevelSnapshot,
            ReadOnly:  true,
        }))
        if err != nil {
            log.Printf("do tx failed: %v\n", err)
        }
    }
    ```

    {% endcut %}

  {% endlist %}

- Java

  {% list tabs %}

  - Native SDK

    В YDB Java SDK механизм повторных запросов реализован в виде класс хелпера `SessionRetryContext`. Данный класс конструируется с помощью метода `SessionRetryContext.create` в который требуется передать реализацию интерфейса `SessionSupplier` - как правило это экземпляр класса `TableClient` или `QueryClient`.

    Дополнительно пользователь может задавать некоторые другие опции:

    * `maxRetries(int maxRetries)` - максимальное количество повторов операции, не включает в себя первое выполение. Значение по умолчанию `10`
    * `retryNotFound(boolean retryNotFound)` - опция повтора операций, вернувших статус `NOT_FOUND`. По умолчанию включено.
    * `idempotent(boolean idempotent)` - признак идемпотентности операций. Идемпотентные операции будут повторяться для более широкого списка ошибок. По умолчанию отключено.

    Для запуска операций с ретраями класс `SessionRetryContext` предоставляет два метода:

    * `CompletableFuture<Status> supplyStatus` - выполнение операции, возвращающей статус. В качестве аргумента принимает лямбду `Function<Session, CompletableFuture<Status>> fn`
    * `CompletableFuture<Result<T>> supplyResult` - выполнение операции, возвращающей данные. В качестве аргумента принимает лямбду `Function<Session, CompletableFuture<Result<T>>> fn`

    При использовании класса `SessionRetryContext` нужно учитывать, что повторное исполнение операции будет выполняться в следующих случаях:

    * Лямбда вернула [retryable](https://ydb.tech/docs/ru/reference/ydb-sdk/error_handling.md?version=v26.1) код ошибки
    * В рамках исполнения лямбды была вызвано `UnexpectedResultException` c [retryable](https://ydb.tech/docs/ru/reference/ydb-sdk/error_handling.md?version=v26.1) кодом ошибки

      {% cut "Пример кода, использующего SessionRetryContext.supplyStatus:" %}

      ```java
      private void createTable(TableClient tableClient, String database, String tableName) {
          SessionRetryContext retryCtx = SessionRetryContext.create(tableClient).build();
          TableDescription pets = TableDescription.newBuilder()
                  .addNullableColumn("species", PrimitiveType.Text)
                  .addNullableColumn("name", PrimitiveType.Text)
                  .addNullableColumn("color", PrimitiveType.Text)
                  .addNullableColumn("price", PrimitiveType.Float)
                  .setPrimaryKeys("species", "name")
                  .build();

          String tablePath = database + "/" + tableName;
          retryCtx.supplyStatus(session -> session.createTable(tablePath, pets))
                  .join().expectSuccess();
      }
      ```

      {% endcut %}

      {% cut "Пример кода, использующего SessionRetryContext.supplyResult:" %}

      ```java
      private void selectData(TableClient tableClient, String tableName) {
          SessionRetryContext retryCtx = SessionRetryContext.create(tableClient).build();
          String selectQuery
                  = "DECLARE $species AS Text;"
                  + "DECLARE $name AS Text;"
                  + "SELECT * FROM " + tableName + " "
                  + "WHERE species = $species AND name = $name;";

          Params params = Params.of(
                  "$species", PrimitiveValue.newText("cat"),
                  "$name", PrimitiveValue.newText("Tom")
          );

          DataQueryResult data = retryCtx
                  .supplyResult(session -> session.executeDataQuery(selectQuery, TxControl.onlineRo(), params))
                  .join().getValue();

          ResultSetReader rsReader = data.getResultSet(0);
          logger.info("Result of select query:");
          while (rsReader.next()) {
              logger.info("  species: {}, name: {}, color: {}, price: {}",
                      rsReader.getColumn("species").getText(),
                      rsReader.getColumn("name").getText(),
                      rsReader.getColumn("color").getText(),
                      rsReader.getColumn("price").getFloat()
              );
          }
      }
      ```

      {% endcut %}

  - JDBC

    Повторные попытки на уровне `SessionRetryContext` относятся к нативному API (`TableClient` / `QueryClient`). При работе через JDBC используйте ретраи на уровне приложения или подключайте нативный транспорт и клиент, как в разделе [Инициализация драйвера](https://ydb.tech/docs/ru/recipes/ydb-sdk/init.md?version=v26.1).

  {% endlist %}

- Python

  {% list tabs %}

  - Native SDK

    В YDB Python SDK выполнение повторных попыток реализовано в `QuerySessionPool` с использованием класса `RetrySettings` для настройки параметров повторов. Класс `RetrySettings` поддерживает следующие опции:

    * `max_retries` - максимальное количество повторных попыток (по умолчанию 10)
    * `idempotent` - признак идемпотентности операции. Идемпотентные операции повторяются для более широкого списка ошибок (по умолчанию False)
    * `backoff_ceiling`, `backoff_slot_duration` - параметры алгоритма экспоненциальной задержки
    * `fast_backoff_settings`, `slow_backoff_settings` - настройки быстрых и медленных повторов

    Для выполнения запросов с повторными попытками `QuerySessionPool` предоставляет методы `retry_operation_sync` и `execute_with_retries`. Метод `execute_with_retries` предназначен для разовых запросов с неявным режимом транзакции (implicit). Для остальных случаев (явные транзакции, несколько операций в одной транзакции) используйте `retry_operation_sync`.

    Пример кода, использующего execute_with_retries:

    ```python
    import ydb

    def execute_query(pool: ydb.QuerySessionPool):
        result_sets = pool.execute_with_retries(
            "SELECT series_id, title FROM series WHERE series_id = 1;",
            retry_settings=ydb.RetrySettings(idempotent=True),
        )
        # ...
    ```

    Пример кода, использующего retry_operation_sync:

    ```python
    import ydb

    def execute_query(pool: ydb.QuerySessionPool):
        def callee(session: ydb.QuerySession):
              with session.transaction().execute(
                  "SELECT 1",
                  commit_tx=True,
              ) as result_sets:
                  pass

        result = pool.retry_operation_sync(
            callee,
            retry_settings=ydb.RetrySettings(max_retries=20, idempotent=True),
        )
        # ...
    ```

  - Native SDK (Asyncio)

    Пример кода, использующего execute_with_retries:

    ```python
    import ydb

    async def execute_query(pool: ydb.aio.QuerySessionPool):
        result_sets = await pool.execute_with_retries(
            "SELECT series_id, title FROM series WHERE series_id = 1;",
            retry_settings=ydb.RetrySettings(idempotent=True),
        )
        # ...
    ```

    Пример кода, использующего retry_operation_sync:

    ```python
    import ydb

    async def execute_query(pool: ydb.aio.QuerySessionPool):
        async def callee(session):
            async with session.transaction(tx_mode=ydb.QuerySerializableReadWrite()) as tx:
                async with await tx.execute("SELECT 1", commit_tx=True) as result_sets:
                    pass

        await pool.retry_operation_async(
            callee,
            retry_settings=ydb.RetrySettings(max_retries=20, idempotent=True),
        )
        # ...
    ```

  - SQLAlchemy

    При использовании YDB через SQLAlchemy выполнение повторных попыток происходит под капотом и не регулируется снаружи.

  {% endlist %}

- C#

  В YDB C# SDK повторные попытки реализованы на двух уровнях.

  {% list tabs %}

  - OpenRetryableConnectionAsync

    Метод `OpenRetryableConnectionAsync` создаёт соединение с автоматическими повторными попытками при transient-ошибках. Соединение, полученное таким образом, не поддерживает интерактивные транзакции — для работы с транзакциями используйте `ExecuteInTransactionAsync`.

    ```C#
    using Ydb.Sdk.Ado;

    await using var dataSource = new YdbDataSource("Host=localhost;Port=2136;Database=/local");

    await using var connection = await dataSource.OpenRetryableConnectionAsync();
    var command = new YdbCommand("SELECT series_id, title FROM series WHERE series_id = $series_id", connection);
    command.Parameters.Add(new YdbParameter("$series_id", YdbDbType.Uint64, 1U));

    await using var reader = await command.ExecuteReaderAsync();
    while (await reader.ReadAsync())
    {
        Console.WriteLine($"series_id: {reader.GetUint64(0)}, title: {reader.GetString(1)}");
    }
    ```

  - ExecuteInTransactionAsync

    Метод `ExecuteInTransactionAsync` выполняет несколько операций в рамках одной транзакции с автоматическим повтором при конфликтах:

    ```C#
    using Ydb.Sdk.Ado;

    await using var dataSource = new YdbDataSource("Host=localhost;Port=2136;Database=/local");

    await dataSource.ExecuteInTransactionAsync(async connection =>
    {
        var command = connection.CreateCommand();
        command.CommandText = "UPSERT INTO series (series_id, title) VALUES (1, \"IT Crowd\")";
        await command.ExecuteNonQueryAsync();
    });
    ```

  {% endlist %}

- JavaScript

  Повторные попытки и переподключения сделаны внутри sdk, отдельно ничего пользователю настраивать не нужно.

  Сам ретраер доступен в отдельном пакете `@ydbjs/retry`.

  ```javascript
  import { retry } from '@ydbjs/retry'

  let attempts = 0
  const result = retry({ retry: isError, budget: 3 }, async () => {
    if (attempts >= 2) {
      return 'success'
    }

    attempts++
    throw new Error('test error')
  })
  ```

- Rust

  Повторные попытки для запросов через Query Service выполняет `QueryClient`: вспомогательные методы для выполнения одного транзакционного SQL-запроса (`query_row`, `exec` и т.д.) ретраятся автоматически; для нескольких операций в одной транзакции — `retry_transaction`.

  ```rust
  use ydb::{AccessTokenCredentials, ClientBuilder, YdbResult};

  #[tokio::main]
  async fn main() -> YdbResult<()> {
      let client = ClientBuilder::new_from_connection_string(
          "grpc://localhost:2136?database=local",
      )?
      .with_credentials(AccessTokenCredentials::from("..."))
      .client()?;

      client.wait().await?;

      let mut qc = client.query_client().clone_with_idempotent_operations(true);

      // один SQL-запрос на query-клиенте: внутренние повторные попытки
      let mut row = qc
          .query_row("SELECT series_id, title FROM series WHERE series_id = 1")
          .await?;

      // несколько операций в одной транзакции с ретраями
      let title: String = qc
          .retry_transaction(async |tx| {
              let mut row = tx
                  .query_row("SELECT series_id, title FROM series WHERE series_id = 1")
                  .await?;
              Ok(row.remove_field_by_name("title")?.try_into()?)
          })
          .await?;

      Ok(())
  }
  ```

- PHP

  В YDB PHP SDK повторные попытки для запросов к Table API задаются через `Table::retryTransaction()` (транзакция + коммит + ретраи при поддерживаемых ошибках) или `Table::retrySession()` (одна сессия без обёртки «транзакция целиком»). Второй аргумент `retryTransaction` — признак идемпотентности (`true` расширяет набор ошибок, при которых выполняется повтор).

  Пример с `retryTransaction`:

  ```php
  <?php

  use YdbPlatform\Ydb\Session;
  use YdbPlatform\Ydb\Ydb;

  $ydb = new Ydb($config);

  $result = $ydb->table()->retryTransaction(
      function (Session $session) {
          return $session->query(
              'SELECT series_id, title FROM series WHERE series_id = 1;'
          );
      },
      true
  );

  // $result->rows(), $result->rowCount(), ...
  ```

{% endlist %}
