Gravações otimizadas de throughput

Nesta página, descrevemos como configurar o tempo máximo de atraso de commit (gravação) para otimizar a capacidade de gravação no Spanner.

Visão geral

Para garantir a consistência dos dados, o Spanner envia solicitações de gravação para todas as réplicas de votação no banco de dados. Esse processo de replicação pode ter uma sobrecarga computacional. Para mais informações, consulte Replicação.

As gravações otimizadas para capacidade de transmissão oferecem a opção de amortizar esses custos de computação executando um grupo de gravações juntas. Para isso, o Spanner introduz um pequeno atraso e coleta um grupo de gravações que precisam ser enviadas aos mesmos participantes da votação. Executar gravações dessa forma pode proporcionar melhorias significativas na capacidade de processamento ao custo de um aumento leve na latência.

Comportamento padrão

Se você não definir um tempo de atraso de commit, o Spanner poderá definir um pequeno atraso se achar que isso vai amortizar o custo das suas gravações.

Casos de uso comuns

É possível definir manualmente o tempo de atraso das solicitações de gravação, dependendo das necessidades do seu aplicativo. Também é possível desativar os atrasos de confirmação para aplicativos que são altamente sensíveis à latência definindo o tempo máximo de atraso de confirmação como 0 ms.

Se você tiver um aplicativo tolerante à latência e quiser otimizar a capacidade de processamento, defina um tempo de atraso de confirmação mais longo. Isso melhora significativamente a capacidade de processamento, mas aumenta a latência de cada gravação. Por exemplo, se você estiver carregando em massa uma grande quantidade de dados e o aplicativo não se importar com a rapidez com que o Spanner grava dados individuais, defina o tempo de atraso do commit como um valor maior, como 100 ms. Recomendamos começar com um valor de 100 ms e ajustar para cima e para baixo até que as compensações de latência e capacidade atendam às suas necessidades. Para a maioria dos aplicativos, um valor entre 20 ms e 100 ms funciona melhor.

Se você tiver um aplicativo sensível à latência, o Spanner também será sensível à latência por padrão. Se você tiver uma carga de trabalho instável, o Spanner poderá definir um pequeno atraso. Você pode testar a definição de um valor de 0 ms para determinar se a latência reduzida ao custo de um aumento na taxa de transferência é razoável para seu aplicativo.

Definir tempos de atraso de commit mistos

É possível configurar diferentes tempos máximos de atraso de confirmação em subconjuntos de gravações. Se você fizer isso, o Spanner usará o menor tempo de espera configurado para o conjunto de gravações. No entanto, recomendamos escolher um único valor para a maioria dos casos de uso, já que isso resulta em um comportamento mais previsível.

Limitações

É possível definir um tempo de atraso de confirmação entre 0 e 500 ms. Definir atrasos de confirmação maiores que 500 ms resulta em um erro.

Definir o atraso máximo de confirmação em solicitações de confirmação

O parâmetro de atraso máximo de confirmação faz parte do método CommitRequest. É possível acessar esse método com a API RPC, a API REST ou usando a biblioteca de cliente do Cloud Spanner.

C#


using Google.Cloud.Spanner.Data;
using System;
using System.Threading.Tasks;

public class CommitDelayAsyncSample
{
    public async Task<int> CommitDelayAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        using var connection = new SpannerConnection(connectionString);
        await connection.OpenAsync();

        return await connection.RunWithRetriableTransactionAsync(async transaction =>
        {
            transaction.TransactionOptions.MaxCommitDelay = TimeSpan.FromMilliseconds(100);

            using var insertSingerCmd = connection.CreateInsertCommand("Singers",
                new SpannerParameterCollection
                {
                    { "SingerId", SpannerDbType.Int64, 1 },
                    { "FirstName", SpannerDbType.String, "Marc" },
                    { "LastName", SpannerDbType.String, "Richards" }
                });
            insertSingerCmd.Transaction = transaction;
            int rowsInserted = await insertSingerCmd.ExecuteNonQueryAsync();

            using var insertAlbumCmd = connection.CreateInsertCommand("Albums",
                new SpannerParameterCollection
                {
                    { "SingerId", SpannerDbType.Int64, 1 },
                    { "AlbumId", SpannerDbType.Int64, 2 },
                    { "AlbumTitle", SpannerDbType.String, "Go, Go, Go" }
                });
            insertAlbumCmd.Transaction = transaction;
            rowsInserted += await insertAlbumCmd.ExecuteNonQueryAsync();

            return rowsInserted;
        });
    }
}

Go


import (
	"context"
	"fmt"
	"io"
	"time"

	"cloud.google.com/go/spanner"
)

func setMaxCommitDelay(w io.Writer, db string) error {
	// db is the fully-qualified database name of the form `projects/<project>/instances/<instance-id>/database/<database-id>`
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return fmt.Errorf("setMaxCommitDelay.NewClient: %w", err)
	}
	defer client.Close()

	commitDelay := 100 * time.Millisecond
	resp, err := client.ReadWriteTransactionWithOptions(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
		stmt := spanner.Statement{
			SQL: `INSERT Singers (SingerId, FirstName, LastName)
					VALUES (111, 'Virginia', 'Watson')`,
		}
		rowCount, err := txn.Update(ctx, stmt)
		if err != nil {
			return err
		}
		fmt.Fprintf(w, "%d record(s) inserted.\n", rowCount)
		return nil
	}, spanner.TransactionOptions{CommitOptions: spanner.CommitOptions{MaxCommitDelay: &commitDelay, ReturnCommitStats: true}})
	if err != nil {
		return fmt.Errorf("setMaxCommitDelay.ReadWriteTransactionWithOptions: %w", err)
	}
	fmt.Fprintf(w, "%d mutations in transaction\n", resp.CommitStats.MutationCount)
	return nil
}

Java


import com.google.cloud.spanner.CommitResponse;
import com.google.cloud.spanner.DatabaseClient;
import com.google.cloud.spanner.DatabaseId;
import com.google.cloud.spanner.Mutation;
import com.google.cloud.spanner.Options;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerOptions;
import java.time.Duration;
import java.util.Arrays;

public class SetMaxCommitDelaySample {

  static void setMaxCommitDelay() {
    // TODO(developer): Replace these variables before running the sample.
    final String projectId = "my-project";
    final String instanceId = "my-instance";
    final String databaseId = "my-database";

    try (Spanner spanner =
        SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) {
      final DatabaseClient databaseClient = spanner
          .getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId));
      setMaxCommitDelay(databaseClient);
    }
  }

  static void setMaxCommitDelay(DatabaseClient databaseClient) {
    final CommitResponse commitResponse = databaseClient.writeWithOptions(Arrays.asList(
        Mutation.newInsertOrUpdateBuilder("Albums")
            .set("SingerId")
            .to("1")
            .set("AlbumId")
            .to("1")
            .set("MarketingBudget")
            .to("200000")
            .build(),
        Mutation.newInsertOrUpdateBuilder("Albums")
            .set("SingerId")
            .to("2")
            .set("AlbumId")
            .to("2")
            .set("MarketingBudget")
            .to("400000")
            .build()
    ), Options.maxCommitDelay(Duration.ofMillis(100)));

    System.out.println(
        "Updated data with timestamp + " + commitResponse.getCommitTimestamp() + ".");
  }
}

Node.js

const {Spanner, protos} = 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,
});

async function setMaxCommitDelay() {
  const instance = spanner.instance(instanceId);
  const database = instance.database(databaseId);

  database.runTransaction(async (err, transaction) => {
    if (err) {
      console.error(err);
      return;
    }
    try {
      const [rowCount] = await transaction.runUpdate({
        sql: 'INSERT Singers (SingerId, FirstName, LastName) VALUES (111, @firstName, @lastName)',
        params: {
          firstName: 'Virginia',
          lastName: 'Watson',
        },
      });

      console.log(
        `Successfully inserted ${rowCount} record into the Singers table.`,
      );

      await transaction.commit({
        maxCommitDelay: protos.google.protobuf.Duration({
          seconds: 0, // 0 seconds
          nanos: 100000000, // 100 milliseconds
        }),
      });
    } catch (err) {
      console.error('ERROR:', err);
    } finally {
      // Close the database when finished.
      database.close();
    }
  });
}
setMaxCommitDelay();

Python

# 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)

def insert_singers(transaction):
    row_ct = transaction.execute_update(
        "INSERT Singers (SingerId, FirstName, LastName) "
        " VALUES (111, 'Grace', 'Bennis')"
    )

    print("{} record(s) inserted.".format(row_ct))

database.run_in_transaction(
    insert_singers, max_commit_delay=datetime.timedelta(milliseconds=100)
)

Ruby

require "google/cloud/spanner"

##
# This is a snippet for showcasing how to pass max_commit_delay in  commit_options.
#
# @param project_id  [String] The ID of the Google Cloud project.
# @param instance_id [String] The ID of the spanner instance.
# @param database_id [String] The ID of the database.
#
def spanner_set_max_commit_delay project_id:, instance_id:, database_id:
  # Instantiates a client
  spanner = Google::Cloud::Spanner.new project: project_id
  client  = spanner.client instance_id, database_id

  records = [
    { SingerId: 1, AlbumId: 1, MarketingBudget: 200_000 },
    { SingerId: 2, AlbumId: 2, MarketingBudget: 400_000 }
  ]
  # max_commit_delay is the amount of latency in millisecond, this request
  # is willing to incur in order to improve throughput.
  # The commit delay must be at least 0ms and at most 500ms.
  # Default value is nil.
  commit_options = {
    return_commit_stats: true,
    max_commit_delay: 100
  }
  resp = client.upsert "Albums", records, commit_options: commit_options
  puts "Updated data with #{resp.stats.mutation_count} mutations."
end

Monitorar a latência de solicitações de gravação

É possível monitorar a utilização da CPU e a latência do Spanner usando o consoleGoogle Cloud . Quando você define um tempo de atraso maior para suas solicitações de gravação, espere ver a utilização da CPU potencialmente diminuir, enquanto a latência aumenta. Para saber mais sobre a latência em solicitações do Spanner, consulte Capturar e visualizar a latência da solicitação da API Spanner.