Operaciones de escritura optimizadas para la capacidad de procesamiento

En esta página, se describe cómo configurar el tiempo máximo de demora de confirmación (escritura) para optimizar el rendimiento de escritura en Spanner.

Descripción general

Para garantizar la coherencia de los datos, Spanner envía solicitudes de escritura a todas las réplicas de votación de la base de datos. Este proceso de replicación puede generar una sobrecarga computacional. Para obtener más información, consulta Replicación.

Las escrituras optimizadas para el rendimiento ofrecen la opción de amortizar estos costos de procesamiento ejecutando un grupo de escrituras en conjunto. Para ello, Spanner introduce una pequeña demora y recopila un grupo de escrituras que deben enviarse a los mismos participantes de votación. Ejecutar escrituras de esta manera puede proporcionar mejoras sustanciales en la capacidad de procesamiento a costa de un ligero aumento en la latencia.

Comportamiento predeterminado

Si no estableces un tiempo de demora de confirmación, es posible que Spanner establezca una pequeña demora por ti si cree que eso amortizará el costo de tus escrituras.

Casos de uso habituales

Puedes configurar manualmente el tiempo de demora de tus solicitudes de escritura según las necesidades de tu aplicación. También puedes inhabilitar las demoras de confirmación para las aplicaciones que son muy sensibles a la latencia. Para ello, establece el tiempo máximo de demora de confirmación en 0 ms.

Si tienes una aplicación tolerante a la latencia y deseas optimizar la capacidad de procesamiento, establecer un tiempo de demora de confirmación más largo mejora significativamente la capacidad de procesamiento y genera una mayor latencia para cada escritura. Por ejemplo, si cargas de forma masiva una gran cantidad de datos y a la aplicación no le importa la rapidez con la que Spanner escribe los datos individuales, puedes establecer el tiempo de demora de la confirmación en un valor más largo, como 100 ms. Te recomendamos que comiences con un valor de 100 ms y, luego, lo ajustes hasta que las compensaciones entre la latencia y el rendimiento satisfagan tus necesidades. En la mayoría de las aplicaciones, un valor entre 20 ms y 100 ms funciona mejor.

Si tienes una aplicación sensible a la latencia, Spanner también lo es de forma predeterminada. Si tienes una carga de trabajo con picos, es posible que Spanner establezca una pequeña demora. Puedes experimentar con establecer un valor de 0 ms para determinar si la latencia reducida a costa de un mayor rendimiento es razonable para tu aplicación.

Cómo establecer tiempos de demora de confirmación mixtos

Puedes configurar diferentes tiempos máximos de demora en la confirmación para subconjuntos de tus escrituras. Si lo haces, Spanner usará el tiempo de demora más corto configurado para el conjunto de escrituras. Sin embargo, te recomendamos que elijas un solo valor para la mayoría de los casos de uso, ya que esto genera un comportamiento más predecible.

Limitaciones

Puedes establecer un tiempo de demora de confirmación entre 0 y 500 ms. Si estableces demoras de confirmación superiores a 500 ms, se producirá un error.

Establece un retraso máximo en las solicitudes de confirmación

El parámetro de demora máxima de la confirmación forma parte del método CommitRequest. Puedes acceder a este método con la API de RPC, la API de REST o la biblioteca cliente de 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

Supervisa la latencia de solicitudes de escritura

Puedes supervisar la utilización de CPU y la latencia de Spanner con la consola deGoogle Cloud . Cuando estableces un tiempo de demora más largo para tus solicitudes de escritura, es posible que veas una disminución en la utilización de la CPU, mientras que la latencia aumenta. Para obtener información sobre la latencia en las solicitudes de Spanner, consulta Cómo capturar y visualizar la latencia de las solicitudes de la API de Spanner.