Penulisan yang dioptimalkan untuk throughput

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

Ringkasan

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

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

Perilaku default

Jika Anda tidak menetapkan waktu tunda commit, Spanner mungkin akan menetapkan penundaan kecil untuk Anda jika menurutnya hal tersebut akan mengamortisasi biaya penulisan Anda.

Kasus penggunaan umum

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

Jika Anda memiliki aplikasi yang toleran terhadap latensi dan ingin mengoptimalkan throughput, menetapkan waktu tunda commit 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 data individual, Anda dapat menetapkan waktu tunda commit ke nilai yang lebih lama seperti 100 md. Sebaiknya mulai dengan nilai 100 md, lalu sesuaikan ke atas dan ke bawah hingga kompromi latensi dan throughput memenuhi kebutuhan Anda. Untuk sebagian besar aplikasi, nilai antara 20 md dan 100 md berfungsi paling baik.

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

Menetapkan waktu tunda commit campuran

Anda dapat mengonfigurasi waktu tunda commit maks yang berbeda pada subset penulisan Anda. Jika Anda melakukannya, Spanner akan menggunakan waktu tunda terpendek yang dikonfigurasi untuk kumpulan penulisan. Namun, sebaiknya pilih satu nilai untuk sebagian besar kasus penggunaan karena hal ini akan menghasilkan perilaku yang lebih dapat diprediksi.

Batasan

Anda dapat menetapkan waktu tunda commit antara 0 dan 500 md. Menetapkan penundaan commit yang lebih tinggi dari 500 md akan menghasilkan error.

Menetapkan waktu tunda commit maks pada permintaan commit

Parameter waktu tunda commit maks 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 penggunaan CPU dan latensi Spanner menggunakan Google Cloud konsol. Saat Anda menetapkan waktu tunda yang lebih lama untuk permintaan tulis, Anda akan melihat penggunaan CPU berpotensi menurun, sementara latensi meningkat. Untuk mempelajari latensi dalam permintaan Spanner, lihat Mengambil dan memvisualisasikan latensi permintaan Spanner API.