Durchsatzoptimierte Schreibvorgänge

Auf dieser Seite wird beschrieben, wie Sie die maximale Commit-Verzögerungszeit (Schreibvorgänge) konfigurieren, um den Schreibdurchsatz in Cloud Spanner zu optimieren.

Übersicht

Damit die Datenkonsistenz gewahrt bleibt, sendet Cloud Spanner Schreibanfragen an alle abstimmenden Replikate in der Datenbank. Dieser Replikationsprozess kann Rechenaufwand verursachen. Weitere Informationen finden Sie unter Replikation.

Durchsatzoptimierte Schreibvorgänge bieten die Möglichkeit, diese Rechenkosten durch die gemeinsame Ausführung einer Gruppe von Schreibvorgängen zu amortisieren. Dazu führt Cloud Spanner eine kurze Verzögerung ein und fasst eine Gruppe von Schreibvorgängen zusammen, die an dieselben Abstimmungsteilnehmer gesendet werden müssen. Wenn Schreibvorgänge auf diese Weise ausgeführt werden, kann der Durchsatz erheblich verbessert werden, allerdings auf Kosten einer leicht erhöhten Latenz.

Standardverhalten

Wenn Sie keine Commit-Verzögerungszeit festlegen, kann Cloud Spanner eine kurze Verzögerung für Sie festlegen, wenn dadurch die Kosten für Ihre Schreibvorgänge voraussichtlich amortisiert werden.

Gängige Anwendungsfälle

Sie haben die Möglichkeit, die Verzögerungszeit Ihrer Schreibanfragen je nach Anforderungen Ihrer Anwendung manuell festzulegen. Außerdem können Sie Commit-Verzögerungen für Anwendungen deaktivieren, die sehr latenzempfindlich sind. Dazu legen Sie die maximale Commit-Verzögerungszeit auf 0 ms fest.

Falls Ihre Anwendung latenztolerant ist und der Durchsatz optimiert werden soll, können Sie eine längere Commit-Verzögerungszeit festlegen. Dies verbessert den Durchsatz deutlich, führt aber auch zu einer höheren Latenz für jeden Schreibvorgang. Wenn Sie beispielsweise eine große Datenmenge im Bulk laden und es für die Anwendung unerheblich ist, wie schnell Cloud Spanner einzelne Daten schreibt, können Sie die Commit-Verzögerungszeit auf einen längeren Wert wie 100 ms festlegen. Fangen Sie am besten mit 100 ms an und passen Sie den Wert dann nach oben oder unten an, bis das Verhältnis zwischen Latenz und Durchsatz Ihren Anforderungen entspricht. Für die meisten Anwendungen ist ein Wert zwischen 20 ms und 100 ms am besten geeignet.

Wenn Sie eine latenzempfindliche Anwendung haben, ist Cloud Spanner standardmäßig ebenfalls latenzempfindlich. Bei einer ungleichmäßigen Arbeitslast kann Cloud Spanner eine geringe Verzögerung festlegen. Sie können mit einem Wert von 0 ms experimentieren, um festzustellen, ob die reduzierte Latenz auf Kosten eines erhöhten Durchsatzes für Ihre Anwendung vertretbar ist.

Unterschiedliche Commit-Verzögerungszeiten festlegen

Sie können für Teilmengen Ihrer Schreibvorgänge unterschiedliche maximale Commit-Verzögerungszeiten konfigurieren. In diesem Fall verwendet Cloud Spanner die kürzeste für die Gruppe von Schreibvorgängen konfigurierte Verzögerungszeit. Bei den meisten Anwendungsfällen empfiehlt es sich jedoch, einen einzelnen Wert auszuwählen, da dies das Verhalten vorhersehbarer macht.

Beschränkungen

Sie können eine Commit-Verzögerungszeit zwischen 0 und 500 ms festlegen. Wenn Sie eine Commit-Verzögerung von mehr als 500 ms festlegen, wird ein Fehler ausgegeben.

Maximale Commit-Verzögerung für Commit-Anfragen festlegen

Der Parameter für die maximale Commit-Verzögerung ist Teil der Methode CommitRequest. Sie können auf diese Methode über die RPC API, die REST API oder die Cloud Spanner-Clientbibliothek zugreifen.

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

Latenz von Schreibanfragen überwachen

Sie können die CPU-Auslastung und Latenz von Cloud Spanner über dieGoogle Cloud Console überwachen. Wenn Sie eine längere Verzögerungszeit für die Schreibanfragen festlegen, müssen Sie damit rechnen, dass die CPU-Auslastung möglicherweise sinkt, während die Latenz steigt. Informationen zur Latenz bei Cloud Spanner-Anfragen finden Sie unter Latenz von Spanner API-Anfragen erfassen und visualisieren.