이 페이지에서는 Spanner 클라이언트 라이브러리를 사용하여 기본 시간 제한 구성을 재정의하고 재시도 정책을 구성하는 방법을 설명합니다.
클라이언트 라이브러리는 다음 구성 파일에서 정의된 기본 시간 제한 및 재시도 정책 설정을 사용합니다.
- spanner_grpc_service_config.json
- spanner_admin_instance_grpc_service_config.json
- spanner_admin_database_grpc_service_config.json
구성 파일에서 짧은 시간(예: CreateSession
)이 소요되는 작업의 기본 제한 시간은 30초입니다. 쿼리 또는 읽기와 같은 더 긴 작업의 기본 제한 시간은 3,600초입니다. 이러한 기본값을 사용하는 것이 좋습니다. 그러나 필요한 경우 애플리케이션에서 커스텀 제한 시간이나 재시도 정책을 설정할 수 있습니다.
제한 시간을 변경하기로 결정한 경우 애플리케이션이 결과를 대기할 실제 시간으로 설정합니다.
과도한 재시도로 인해 백엔드에 과부하가 발생하고 요청이 제한될 수 있으므로 기본값보다 더 공격적인 재시도 정책을 구성하지 마세요.
재시도 정책은 다음 특성으로 각 스니펫에 정의됩니다.
- 요청을 다시 시작하기 전의 초기 또는 시작 대기 시간
- 최대 지연
- 최댓값에 도달할 때까지 다음 대기 시간을 계산하기 위해 이전 대기 시간과 함께 사용할 배율
- 재시도 작업에 대한 오류 코드 집합
다음 샘플에서는 지정된 작업에 대해 제한 시간이 60초로 설정됩니다.
작업이 이 제한 시간보다 오래 걸리면 DEADLINE_EXCEEDED
오류와 함께 작업이 실패합니다.
작업이 UNAVAILABLE
오류 코드와 함께 실패하는 경우(예: 일시적인 네트워크 문제가 있는 경우) 작업이 재시도됩니다. 클라이언트는 첫 번째 재시도를 시작하기 전에 500ms를 기다립니다. 첫 번째 재시도가 실패하면 클라이언트는 두 번째 재시도를 시작하기 전에 1.5 * 500ms = 750ms를 기다립니다. 이 재시도 지연은 작업이 성공하거나 최대 재시도 지연 시간인 16초에 도달할 때까지 계속 증가합니다. 작업 시도에 소요된 총 시간이 총 제한 시간 값 60초를 초과하면 작업이 DEADLINE_EXCEEDED
오류와 함께 실패합니다.
C++
namespace spanner = ::google::cloud::spanner;
using ::google::cloud::StatusOr;
[](std::string const& project_id, std::string const& instance_id,
std::string const& database_id) {
// Use a truncated exponential backoff with jitter to wait between
// retries:
// https://en.wikipedia.org/wiki/Exponential_backoff
// https://cloud.google.com/storage/docs/exponential-backoff
auto client = spanner::Client(spanner::MakeConnection(
spanner::Database(project_id, instance_id, database_id),
google::cloud::Options{}
.set<spanner::SpannerRetryPolicyOption>(
std::make_shared<spanner::LimitedTimeRetryPolicy>(
/*maximum_duration=*/std::chrono::seconds(60)))
.set<spanner::SpannerBackoffPolicyOption>(
std::make_shared<spanner::ExponentialBackoffPolicy>(
/*initial_delay=*/std::chrono::milliseconds(500),
/*maximum_delay=*/std::chrono::seconds(16),
/*scaling=*/1.5))));
std::int64_t rows_inserted;
auto commit_result = client.Commit(
[&client, &rows_inserted](
spanner::Transaction txn) -> StatusOr<spanner::Mutations> {
auto insert = client.ExecuteDml(
std::move(txn),
spanner::SqlStatement(
"INSERT INTO Singers (SingerId, FirstName, LastName)"
" VALUES (20, 'George', 'Washington')"));
if (!insert) return std::move(insert).status();
rows_inserted = insert->RowsModified();
return spanner::Mutations{};
});
if (!commit_result) throw std::move(commit_result).status();
std::cout << "Rows inserted: " << rows_inserted;
}
C#
using Google.Api.Gax;
using Google.Api.Gax.Grpc;
using Google.Cloud.Spanner.Common.V1;
using Google.Cloud.Spanner.V1;
using Grpc.Core;
using System;
using System.Threading;
using System.Threading.Tasks;
using static Google.Cloud.Spanner.V1.TransactionOptions.Types;
public class CustomTimeoutsAndRetriesAsyncSample
{
public async Task<long> CustomTimeoutsAndRetriesAsync(string projectId, string instanceId, string databaseId)
{
// Create a SessionPool.
SpannerClient client = SpannerClient.Create();
SessionPool sessionPool = new SessionPool(client, new SessionPoolOptions());
// Acquire a session with a read-write transaction to run a query.
DatabaseName databaseName =
DatabaseName.FromProjectInstanceDatabase(projectId, instanceId, databaseId);
TransactionOptions transactionOptions = new TransactionOptions
{
ReadWrite = new ReadWrite()
};
using PooledSession session = await sessionPool.AcquireSessionAsync(
databaseName, transactionOptions, CancellationToken.None);
ExecuteSqlRequest request = new ExecuteSqlRequest
{
Sql = "INSERT Singers (SingerId, FirstName, LastName) VALUES (20, 'George', 'Washington')"
};
// Prepare the call settings with custom timeout and retry settings.
CallSettings settings = CallSettings
.FromExpiration(Expiration.FromTimeout(TimeSpan.FromSeconds(60)))
.WithRetry(RetrySettings.FromExponentialBackoff(
maxAttempts: 12,
initialBackoff: TimeSpan.FromMilliseconds(500),
maxBackoff: TimeSpan.FromSeconds(16),
backoffMultiplier: 1.5,
retryFilter: RetrySettings.FilterForStatusCodes(
new StatusCode[] { StatusCode.Unavailable })));
ResultSet result = await session.ExecuteSqlAsync(request, settings);
await session.CommitAsync(new CommitRequest(), null);
return result.Stats.RowCountExact;
}
}
Go
import (
"context"
"fmt"
"io"
"time"
"cloud.google.com/go/spanner"
spapi "cloud.google.com/go/spanner/apiv1"
gax "github.com/googleapis/gax-go/v2"
"google.golang.org/grpc/codes"
)
func setCustomTimeoutAndRetry(w io.Writer, db string) error {
// Set a custom retry setting.
co := &spapi.CallOptions{
ExecuteSql: []gax.CallOption{
gax.WithRetry(func() gax.Retryer {
return gax.OnCodes([]codes.Code{
codes.Unavailable,
}, gax.Backoff{
Initial: 500 * time.Millisecond,
Max: 16000 * time.Millisecond,
Multiplier: 1.5,
})
}),
},
}
client, err := spanner.NewClientWithConfig(context.Background(), db, spanner.ClientConfig{CallOptions: co})
if err != nil {
return err
}
defer client.Close()
// Set timeout to 60s.
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
_, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
stmt := spanner.Statement{
SQL: `INSERT Singers (SingerId, FirstName, LastName)
VALUES (20, 'George', 'Washington')`,
}
rowCount, err := txn.Update(ctx, stmt)
if err != nil {
return err
}
fmt.Fprintf(w, "%d record(s) inserted.\n", rowCount)
return nil
})
return err
}
Java
import com.google.api.gax.retrying.RetrySettings;
import com.google.api.gax.rpc.StatusCode.Code;
import com.google.cloud.spanner.DatabaseClient;
import com.google.cloud.spanner.DatabaseId;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.Statement;
import org.threeten.bp.Duration;
class CustomTimeoutAndRetrySettingsExample {
static void executeSqlWithCustomTimeoutAndRetrySettings() {
// TODO(developer): Replace these variables before running the sample.
String projectId = "my-project";
String instanceId = "my-instance";
String databaseId = "my-database";
executeSqlWithCustomTimeoutAndRetrySettings(projectId, instanceId, databaseId);
}
// Create a Spanner client with custom ExecuteSql timeout and retry settings.
static void executeSqlWithCustomTimeoutAndRetrySettings(
String projectId, String instanceId, String databaseId) {
SpannerOptions.Builder builder = SpannerOptions.newBuilder().setProjectId(projectId);
// Set custom timeout and retry settings for the ExecuteSql RPC.
// This must be done in a separate chain as the setRetryableCodes and setRetrySettings methods
// return a UnaryCallSettings.Builder instead of a SpannerOptions.Builder.
builder
.getSpannerStubSettingsBuilder()
.executeSqlSettings()
// Configure which errors should be retried.
.setRetryableCodes(Code.UNAVAILABLE)
.setRetrySettings(
RetrySettings.newBuilder()
// Configure retry delay settings.
// The initial amount of time to wait before retrying the request.
.setInitialRetryDelay(Duration.ofMillis(500))
// The maximum amount of time to wait before retrying. I.e. after this value is
// reached, the wait time will not increase further by the multiplier.
.setMaxRetryDelay(Duration.ofSeconds(16))
// The previous wait time is multiplied by this multiplier to come up with the next
// wait time, until the max is reached.
.setRetryDelayMultiplier(1.5)
// Configure RPC and total timeout settings.
// Timeout for the first RPC call. Subsequent retries will be based off this value.
.setInitialRpcTimeout(Duration.ofSeconds(60))
// The max for the per RPC timeout.
.setMaxRpcTimeout(Duration.ofSeconds(60))
// Controls the change of timeout for each retry.
.setRpcTimeoutMultiplier(1.0)
// The timeout for all calls (first call + all retries).
.setTotalTimeout(Duration.ofSeconds(60))
.build());
// Create a Spanner client using the custom retry and timeout settings.
try (Spanner spanner = builder.build().getService()) {
DatabaseClient client =
spanner.getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId));
client
.readWriteTransaction()
.run(transaction -> {
String sql =
"INSERT INTO Singers (SingerId, FirstName, LastName)\n"
+ "VALUES (20, 'George', 'Washington')";
long rowCount = transaction.executeUpdate(Statement.of(sql));
System.out.printf("%d record inserted.%n", rowCount);
return null;
});
}
}
}
Node.js
// Imports the Google Cloud client library
const {Spanner} = require('@google-cloud/spanner');
/**
* TODO(developer): Uncomment the following lines before running the sample.
*/
// const projectId = 'my-project-id';
// const instanceId = 'my-instance';
// const databaseId = 'my-database';
// Creates a client
const spanner = new Spanner({
projectId: projectId,
});
const UNAVAILABLE_STATUS_CODE = 14;
const retryAndTimeoutSettings = {
retry: {
// The set of error codes that will be retried.
retryCodes: [UNAVAILABLE_STATUS_CODE],
backoffSettings: {
// Configure retry delay settings.
// The initial amount of time to wait before retrying the request.
initialRetryDelayMillis: 500,
// The maximum amount of time to wait before retrying. I.e. after this
// value is reached, the wait time will not increase further by the
// multiplier.
maxRetryDelayMillis: 16000,
// The previous wait time is multiplied by this multiplier to come up
// with the next wait time, until the max is reached.
retryDelayMultiplier: 1.5,
// Configure RPC and total timeout settings.
// Timeout for the first RPC call. Subsequent retries will be based off
// this value.
initialRpcTimeoutMillis: 60000,
// Controls the change of timeout for each retry.
rpcTimeoutMultiplier: 1.0,
// The max for the per RPC timeout.
maxRpcTimeoutMillis: 60000,
// The timeout for all calls (first call + all retries).
totalTimeoutMillis: 60000,
},
},
};
// Gets a reference to a Cloud Spanner instance and database
const instance = spanner.instance(instanceId);
const database = instance.database(databaseId);
const table = database.table('Singers');
const row = {
SingerId: 16,
FirstName: 'Martha',
LastName: 'Waller',
};
await table.insert(row, retryAndTimeoutSettings);
console.log('record inserted.');
Python
from google.api_core import exceptions as core_exceptions
from google.api_core import retry
# instance_id = "your-spanner-instance"
# database_id = "your-spanner-db-id"
spanner_client = spanner.Client()
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)
retry = retry.Retry(
# Customize retry with an initial wait time of 500 milliseconds.
initial=0.5,
# Customize retry with a maximum wait time of 16 seconds.
maximum=16,
# Customize retry with a wait time multiplier per iteration of 1.5.
multiplier=1.5,
# Customize retry with a timeout on
# how long a certain RPC may be retried in
# case the server returns an error.
timeout=60,
# Configure which errors should be retried.
predicate=retry.if_exception_type(
core_exceptions.ServiceUnavailable,
),
)
# Set a custom retry and timeout setting.
with database.snapshot() as snapshot:
results = snapshot.execute_sql(
"SELECT SingerId, AlbumId, AlbumTitle FROM Albums",
# Set custom retry setting for this request
retry=retry,
# Set custom timeout of 60 seconds for this request
timeout=60,
)
for row in results:
print("SingerId: {}, AlbumId: {}, AlbumTitle: {}".format(*row))
Ruby
# project_id = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID"
require "google/cloud/spanner"
spanner = Google::Cloud::Spanner.new project: project_id
client = spanner.client instance_id, database_id
row_count = 0
timeout = 60.0
retry_policy = {
initial_delay: 0.5,
max_delay: 16.0,
multiplier: 1.5,
retry_codes: ["UNAVAILABLE"]
}
call_options = { timeout: timeout, retry_policy: retry_policy }
client.transaction do |transaction|
row_count = transaction.execute_update(
"INSERT INTO Singers (SingerId, FirstName, LastName) VALUES (10, 'Virginia', 'Watson')",
call_options: call_options
)
end
puts "#{row_count} record inserted."