Scritture ottimizzate per la velocità effettiva

Questa pagina descrive come configurare il tempo massimo di ritardo del commit (scrittura) per ottimizzare il throughput di scrittura in Spanner.

Panoramica

Per garantire la coerenza dei dati, Spanner invia richieste di scrittura a tutte le repliche con diritto di voto nel database. Questo processo di replica può comportare un sovraccarico di calcolo. Per saperne di più, consulta la sezione Replica.

Le scritture ottimizzate per la velocità effettiva offrono la possibilità di ammortizzare questi costi di calcolo eseguendo un gruppo di scritture insieme. Per farlo, Spanner introduce un piccolo ritardo e raccoglie un gruppo di scritture che devono essere inviate agli stessi partecipanti al voto. L'esecuzione di scritture in questo modo può fornire miglioramenti sostanziali della velocità effettiva a scapito di una latenza leggermente maggiore.

Comportamento predefinito

Se non imposti un tempo di ritardo del commit, Spanner potrebbe impostare un piccolo ritardo per te se ritiene che ciò ammortizzerà il costo delle tue scritture.

Casi d'uso comuni

Puoi impostare manualmente il tempo di ritardo delle richieste di scrittura in base alle esigenze della tua applicazione. Puoi anche disattivare i ritardi di commit per le applicazioni molto sensibili alla latenza impostando il tempo massimo di ritardo di commit su 0 ms.

Se hai un'applicazione tollerante alla latenza e vuoi ottimizzare la velocità effettiva, l'impostazione di un tempo di ritardo del commit più lungo migliora significativamente la velocità effettiva, ma comporta una latenza maggiore per ogni scrittura. Ad esempio, se stai caricando in blocco una grande quantità di dati e l'applicazione non si preoccupa della velocità con cui Spanner scrive i singoli dati, puoi impostare il tempo di ritardo del commit su un valore più lungo, ad esempio 100 ms. Ti consigliamo di iniziare con un valore di 100 ms e poi di regolarlo verso l'alto e verso il basso finché i compromessi tra latenza e velocità effettiva non soddisfano le tue esigenze. Per la maggior parte delle applicazioni, un valore compreso tra 20 ms e 100 ms è l'ideale.

Se hai un'applicazione sensibile alla latenza, anche Spanner è sensibile alla latenza per impostazione predefinita. Se hai un carico di lavoro irregolare, Spanner potrebbe impostare un piccolo ritardo. Puoi provare a impostare un valore di 0 ms per determinare se la latenza ridotta a costo di un aumento del throughput è ragionevole per la tua applicazione.

Impostare tempi di commit misti

Puoi configurare tempi di ritardo del commit massimi diversi per i sottoinsiemi delle tue scritture. Se lo fai, Spanner utilizza il tempo di ritardo più breve configurato per l'insieme di scritture. Tuttavia, consigliamo di scegliere un unico valore per la maggior parte dei casi d'uso, in quanto ciò comporta un comportamento più prevedibile.

Limitazioni

Puoi impostare un tempo di ritardo del commit compreso tra 0 e 500 ms. L'impostazione di ritardi del commit superiori a 500 ms genera un errore.

Imposta il ritardo massimo di commit per le richieste di commit

Il parametro di ritardo massimo del commit fa parte del metodo CommitRequest. Puoi accedere a questo metodo con l'API RPC, l'API REST o utilizzando la libreria client 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

Monitorare la latenza delle richieste di scrittura

Puoi monitorare l'utilizzo della CPU e la latenza di Spanner utilizzando la consoleGoogle Cloud . Quando imposti un tempo di ritardo più lungo per le richieste di scrittura, prevedi una potenziale diminuzione dell'utilizzo della CPU, mentre la latenza aumenta. Per informazioni sulla latenza nelle richieste Spanner, consulta Acquisire e visualizzare la latenza delle richieste API Spanner.