Operasi tulis yang dioptimalkan throughput

Halaman ini menjelaskan cara mengonfigurasi waktu tunda commit (penulisan) maksimum untuk mengoptimalkan throughput penulisan di Spanner.

Ringkasan

Untuk memastikan konsistensi data, Spanner mengirim permintaan tulis ke semua replika pemungutan suara dalam database. Proses replikasi ini dapat memiliki overhead komputasi. Untuk mengetahui informasi selengkapnya, lihat Replikasi.

Penulisan yang dioptimalkan untuk throughput memberikan opsi untuk mengamortisasi biaya komputasi ini dengan menjalankan sekelompok penulisan secara bersamaan. Untuk melakukannya, Spanner memperkenalkan penundaan kecil dan mengumpulkan sekelompok operasi tulis yang perlu dikirim ke peserta pemungutan suara yang sama. Menjalankan operasi tulis dengan cara ini dapat memberikan peningkatan throughput yang substansial dengan biaya peningkatan latensi yang sedikit.

Perilaku default

Jika Anda tidak menetapkan waktu penundaan penerapan, Spanner mungkin menetapkan penundaan kecil untuk Anda jika Spanner berpikir bahwa hal itu akan mengamortisasi biaya penulisan Anda.

Kasus penggunaan umum

Anda dapat menetapkan waktu tunda permintaan penulisan secara manual, bergantung pada kebutuhan aplikasi Anda. Anda juga dapat menonaktifkan penundaan penerapan untuk aplikasi yang sangat sensitif terhadap latensi dengan menyetel waktu penundaan penerapan maksimum ke 0 md.

Jika Anda memiliki aplikasi yang toleran terhadap latensi dan ingin mengoptimalkan throughput, menetapkan waktu tunda penerapan yang lebih lama akan meningkatkan throughput secara signifikan sekaligus menimbulkan latensi yang lebih tinggi untuk setiap penulisan. Misalnya, jika Anda memuat data dalam jumlah besar secara massal dan aplikasi tidak peduli seberapa cepat Spanner menulis setiap data, Anda dapat menyetel waktu penundaan commit ke nilai yang lebih lama seperti 100 md. Sebaiknya mulai dengan nilai 100 md, lalu sesuaikan naik dan turun hingga kompromi latensi dan throughput memenuhi kebutuhan Anda. Untuk sebagian besar aplikasi, nilai antara 20 md dan 100 md adalah yang terbaik.

Jika Anda memiliki aplikasi yang sensitif terhadap latensi, Spanner juga sensitif terhadap latensi secara default. Jika Anda memiliki beban kerja yang tidak teratur, Spanner dapat menetapkan penundaan kecil. Anda dapat bereksperimen dengan menyetel nilai 0 md untuk menentukan apakah pengurangan latensi dengan biaya peningkatan throughput dapat diterima untuk aplikasi Anda.

Menetapkan waktu tunda penerapan campuran

Anda dapat mengonfigurasi waktu tunda penerapan maksimum yang berbeda pada subkumpulan penulisan Anda. Jika Anda melakukannya, Spanner akan menggunakan waktu penundaan terpendek yang dikonfigurasi untuk serangkaian penulisan. Namun, sebaiknya pilih satu nilai untuk sebagian besar kasus penggunaan karena hal ini menghasilkan perilaku yang lebih dapat diprediksi.

Batasan

Anda dapat menetapkan waktu penundaan penerapan antara 0 dan 500 md. Menetapkan penundaan penerapan lebih dari 500 md akan menyebabkan error.

Menetapkan penundaan penerapan maksimum pada permintaan penerapan

Parameter penundaan penerapan maksimum adalah bagian dari metode CommitRequest. Anda dapat mengakses metode ini dengan RPC API, REST API, atau menggunakan library klien 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

Memantau latensi permintaan tulis

Anda dapat memantau pemakaian CPU dan latensi Spanner menggunakan konsolGoogle Cloud . Jika Anda menetapkan waktu tunda yang lebih lama untuk permintaan penulisan, Anda dapat melihat penggunaan CPU yang berpotensi menurun, sementara latensi meningkat. Untuk mempelajari latensi dalam permintaan Spanner, lihat Merekam dan memvisualisasikan latensi permintaan Spanner API.