Gravações otimizadas de débito

Esta página descreve como configurar o tempo máximo de confirmação (escrita) para otimizar a taxa de transferência de escrita no Spanner.

Vista geral

Para garantir a consistência dos dados, o Spanner envia pedidos de escrita para todas as réplicas de votação na base de dados. Este processo de replicação pode ter uma sobrecarga computacional. Para mais informações, consulte o artigo Replicação.

As escritas otimizadas para débito oferecem a opção de amortizar estes custos de computação executando um grupo de escritas em conjunto. Para o fazer, o Spanner introduz um pequeno atraso e recolhe um grupo de gravações que têm de ser enviadas aos mesmos participantes na votação. A execução de escritas desta forma pode proporcionar melhorias significativas na taxa de transferência ao custo de uma latência ligeiramente aumentada.

Comportamento predefinido

Se não definir um tempo de atraso de confirmação, o Spanner pode definir um pequeno atraso por si se considerar que isso vai amortizar o custo das suas gravações.

Exemplos de utilização comuns

Pode definir manualmente o tempo de atraso dos seus pedidos de gravação consoante as necessidades da sua aplicação. Também pode desativar os atrasos de confirmação para aplicações altamente sensíveis à latência definindo o tempo máximo de atraso de confirmação como 0 ms.

Se tiver uma aplicação tolerante à latência e quiser otimizar a taxa de transferência, a definição de um tempo de atraso de confirmação mais longo melhora significativamente a taxa de transferência, ao mesmo tempo que incorre numa latência mais elevada para cada gravação. Por exemplo, se estiver a carregar em massa uma grande quantidade de dados e a aplicação não se importar com a rapidez com que o Spanner escreve dados individuais, pode definir o tempo de atraso de confirmação para um valor mais longo, como 100 ms. Recomendamos que comece com um valor de 100 ms e, em seguida, ajuste-o para cima e para baixo até que as compensações de latência e débito satisfaçam as suas necessidades. Para a maioria das aplicações, um valor entre 20 ms e 100 ms funciona melhor.

Se tiver uma aplicação sensível à latência, o Spanner também é sensível à latência por predefinição. Se tiver uma carga de trabalho instável, o Spanner pode definir um pequeno atraso. Pode experimentar definir um valor de 0 ms para determinar se a latência reduzida ao custo de um débito aumentado é razoável para a sua aplicação.

Defina tempos de atraso de confirmação mistos

Pode configurar diferentes tempos de atraso de confirmação máximos em subconjuntos das suas gravações. Se o fizer, o Spanner usa o tempo de atraso mais curto configurado para o conjunto de escritas. No entanto, recomendamos que escolha um único valor para a maioria dos casos de utilização, uma vez que isto resulta num comportamento mais previsível.

Limitações

Pode definir um tempo de atraso de confirmação entre 0 e 500 ms. Se definir atrasos de confirmação superiores a 500 ms, ocorre um erro.

Defina o atraso máximo de confirmação em pedidos de confirmação

O parâmetro de atraso de confirmação máximo faz parte do método CommitRequest. Pode aceder a este método com a API RPC, API REST ou através da biblioteca 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

Monitorize a latência do pedido de gravação

Pode monitorizar a utilização da CPU e a latência do Spanner através da Google Cloud consola. Quando define um tempo de atraso mais longo para os seus pedidos de gravação, espere ver uma potencial diminuição da utilização da CPU, enquanto a latência aumenta. Para saber mais sobre a latência nos pedidos do Spanner, consulte o artigo Capture e visualize a latência dos pedidos da API Spanner.