Écritures optimisées pour le débit

Cette page explique comment configurer le délai maximal de commit (écriture) pour optimiser le débit d'écriture dans Spanner.

Présentation

Pour garantir la cohérence des données, Spanner envoie des requêtes d'écriture à toutes les répliques votantes de la base de données. Ce processus de réplication peut entraîner une surcharge de calcul. Pour en savoir plus, consultez Réplication.

Les écritures optimisées pour le débit permettent d'amortir ces coûts de calcul en exécutant un groupe d'écritures ensemble. Pour ce faire, Spanner introduit un léger délai et collecte un groupe d'écritures qui doivent être envoyées aux mêmes participants votants. L'exécution des écritures de cette manière peut améliorer considérablement le débit au prix d'une latence légèrement accrue.

Comportement par défaut

Si vous ne définissez pas de délai de validation, Spanner peut définir un petit délai pour vous s'il pense que cela amortira le coût de vos écritures.

Cas d'utilisation courants

Vous pouvez définir manuellement le délai de vos requêtes d'écriture en fonction des besoins de votre application. Vous pouvez également désactiver les délais de validation pour les applications très sensibles à la latence en définissant le délai de validation maximal sur 0 ms.

Si vous disposez d'une application tolérante à la latence et que vous souhaitez optimiser le débit, définir un délai de validation plus long améliore considérablement le débit, tout en entraînant une latence plus élevée pour chaque écriture. Par exemple, si vous chargez en bloc une grande quantité de données et que l'application ne se soucie pas de la vitesse à laquelle Spanner écrit les données individuelles, vous pouvez définir un délai de validation plus long, par exemple 100 ms. Nous vous recommandons de commencer par une valeur de 100 ms, puis de l'ajuster jusqu'à ce que le compromis entre latence et débit réponde à vos besoins. Pour la plupart des applications, une valeur comprise entre 20 ms et 100 ms est optimale.

Si vous disposez d'une application sensible à la latence, Spanner l'est également par défaut. Si votre charge de travail est irrégulière, Spanner peut définir un petit délai. Vous pouvez essayer de définir une valeur de 0 ms pour déterminer si la latence réduite au détriment du débit accru est raisonnable pour votre application.

Définir des délais de validation mixtes

Vous pouvez configurer différentes durées maximales de délai d'engagement pour des sous-ensembles de vos écritures. Dans ce cas, Spanner utilise le délai le plus court configuré pour l'ensemble des écritures. Toutefois, nous vous recommandons de choisir une seule valeur pour la plupart des cas d'utilisation, car cela permet d'obtenir un comportement plus prévisible.

Limites

Vous pouvez définir un délai de validation compris entre 0 et 500 ms. Si vous définissez un délai de validation supérieur à 500 ms, une erreur se produit.

Définir un délai maximal pour les requêtes de validation

Le paramètre de délai de validation maximal fait partie de la méthode CommitRequest. Vous pouvez accéder à cette méthode avec l'API RPC, l'API REST ou à l'aide de la bibliothèque cliente 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

Surveiller la latence des requêtes d'écriture

Vous pouvez surveiller l'utilisation du processeur et la latence de Spanner à l'aide de la consoleGoogle Cloud . Lorsque vous définissez un délai plus long pour vos requêtes d'écriture, attendez-vous à ce que l'utilisation du processeur diminue potentiellement, tandis que la latence augmente. Pour en savoir plus sur la latence des requêtes Spanner, consultez Capturer et visualiser la latence des requêtes de l'API Spanner.