Memecahkan masalah dengan tag permintaan dan tag transaksi

Spanner menyediakan serangkaian tabel statistik bawaan untuk membantu Anda mendapatkan insight tentang kueri, pembacaan, dan transaksi. Untuk mengaitkan statistik dengan kode aplikasi dan meningkatkan pemecahan masalah, Anda dapat menambahkan tag (string bentuk bebas) ke operasi baca, kueri, dan transaksi Spanner dalam kode aplikasi. Tag ini diisi di tabel statistik yang membantu Anda melakukan korelasi dan penelusuran berdasarkan tag.

Spanner mendukung dua jenis tag; tag permintaan dan tag transaksi. Seperti namanya, Anda dapat menambahkan tag transaksi ke transaksi, dan meminta tag ke setiap kueri dan API baca. Anda dapat menetapkan tag transaksi pada cakupan transaksi dan menetapkan tag permintaan individual untuk setiap permintaan API yang berlaku dalam transaksi. Tag permintaan dan tag transaksi yang ditetapkan dalam kode aplikasi diisi di kolom tabel statistik berikut.

Tabel Statistik Jenis Tag yang diisi di tabel statistik
Statistik Kueri TopN Meminta tag
Statistik Baca TopN Meminta tag
Statistik Transaksi TopN Tag transaksi
Statistik Kunci TopN Tag transaksi

Meminta tag

Anda dapat menambahkan tag permintaan opsional ke kueri atau permintaan baca. Spanner mengelompokkan statistik berdasarkan tag permintaan, yang terlihat di kolom REQUEST_TAG pada tabel statistik kueri dan statistik baca.

Kapan harus menggunakan tag permintaan

Berikut adalah beberapa skenario yang mendapatkan manfaat dari penggunaan tag permintaan.

  • Menemukan sumber kueri atau pembacaan yang bermasalah: Spanner mengumpulkan statistik untuk pembacaan dan kueri dalam tabel statistik bawaan. Saat menemukan kueri lambat atau pembacaan yang menggunakan CPU tinggi dalam tabel statistik, jika Anda telah menetapkan tag ke kueri tersebut, Anda dapat mengidentifikasi sumber (aplikasi/microservice) yang memanggil operasi ini berdasarkan informasi dalam tag.
  • Mengidentifikasi pembacaan atau kueri dalam tabel statistik: Menetapkan tag permintaan membantu memfilter baris dalam tabel statistik berdasarkan tag yang Anda minati.
  • Menemukan apakah kueri dari aplikasi atau microservice tertentu lambat: Tag permintaan dapat membantu mengidentifikasi apakah kueri dari aplikasi atau microservice tertentu memiliki latensi yang lebih tinggi.
  • Mengelompokkan statistik untuk serangkaian operasi baca atau kueri: Anda dapat menggunakan tag permintaan untuk melacak, membandingkan, dan melaporkan performa di seluruh serangkaian operasi baca atau kueri yang serupa. Misalnya, jika beberapa kueri mengakses tabel/kumpulan tabel dengan pola akses yang sama, sebaiknya tambahkan tag yang sama ke semua kueri tersebut untuk melacaknya bersama-sama.

Cara menetapkan tag permintaan

Contoh berikut menunjukkan cara menetapkan tag permintaan menggunakan library klien Spanner.

C++

void SetRequestTag(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;
  spanner::SqlStatement select(
      "SELECT SingerId, AlbumId, AlbumTitle FROM Albums");
  using RowType = std::tuple<std::int64_t, std::int64_t, std::string>;

  auto opts = google::cloud::Options{}.set<spanner::RequestTagOption>(
      "app=concert,env=dev,action=select");
  auto rows = client.ExecuteQuery(std::move(select), std::move(opts));
  for (auto& row : spanner::StreamOf<RowType>(rows)) {
    if (!row) throw std::move(row).status();
    std::cout << "SingerId: " << std::get<0>(*row)
              << " AlbumId: " << std::get<1>(*row)
              << " AlbumTitle: " << std::get<2>(*row) << "\n";
  }
}

C#


using Google.Cloud.Spanner.Data;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

public class RequestTagAsyncSample
{
    public class Album
    {
        public int SingerId { get; set; }
        public int AlbumId { get; set; }
        public string AlbumTitle { get; set; }
    }

    public async Task<List<Album>> RequestTagAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        using var connection = new SpannerConnection(connectionString);
        using var cmd = connection.CreateSelectCommand(
            $"SELECT SingerId, AlbumId, AlbumTitle FROM Albums");
        // Sets the request tag to "app=concert,env=dev,action=select".
        // This request tag will only be set on this request.
        cmd.Tag = "app=concert,env=dev,action=select";

        var albums = new List<Album>();
        using var reader = await cmd.ExecuteReaderAsync();
        while (await reader.ReadAsync())
        {
            var album = new Album
            {
                SingerId = reader.GetFieldValue<int>("SingerId"),
                AlbumId = reader.GetFieldValue<int>("AlbumId"),
                AlbumTitle = reader.GetFieldValue<string>("AlbumTitle")
            };
            albums.Add(album);
            Console.WriteLine($"SingerId: {album.SingerId}, AlbumId: {album.AlbumId}, AlbumTitle: {album.AlbumTitle}");
        }
        return albums;
    }
}

Go


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

// queryWithTag reads from a database with request tag set
func queryWithTag(w io.Writer, db string) error {
	// db = `projects/<project>/instances/<instance-id>/database/<database-id>`
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	stmt := spanner.Statement{SQL: `SELECT SingerId, AlbumId, AlbumTitle FROM Albums`}
	iter := client.Single().QueryWithOptions(ctx, stmt, spanner.QueryOptions{RequestTag: "app=concert,env=dev,action=select"})
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var singerID, albumID int64
		var albumTitle string
		if err := row.Columns(&singerID, &albumID, &albumTitle); err != nil {
			return err
		}
		fmt.Fprintf(w, "%d %d %s\n", singerID, albumID, albumTitle)
	}
}

Java

static void setRequestTag(DatabaseClient databaseClient) {
  // Sets the request tag to "app=concert,env=dev,action=select".
  // This request tag will only be set on this request.
  try (ResultSet resultSet = databaseClient
      .singleUse()
      .executeQuery(
          Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"),
          Options.tag("app=concert,env=dev,action=select"))) {
    while (resultSet.next()) {
      System.out.printf(
          "SingerId: %d, AlbumId: %d, AlbumTitle: %s\n",
          resultSet.getLong(0),
          resultSet.getLong(1),
          resultSet.getString(2));
    }
  }
}

Node.js

/**
 * TODO(developer): Uncomment the following lines before running the sample.
 */
// const projectId = 'my-project-id';
// const instanceId = 'my-instance';
// const databaseId = 'my-database';

// Imports the Google Cloud client library
const {Spanner} = require('@google-cloud/spanner');

// Creates a client
const spanner = new Spanner({
  projectId: projectId,
});

async function queryTags() {
  // Gets a reference to a Cloud Spanner instance and database.
  const instance = spanner.instance(instanceId);
  const database = instance.database(databaseId);

  // Execute a query with a request tag.
  const [albums] = await database.run({
    sql: 'SELECT SingerId, AlbumId, AlbumTitle FROM Albums',
    requestOptions: {requestTag: 'app=concert,env=dev,action=select'},
    json: true,
  });
  albums.forEach(album => {
    console.log(
      `SingerId: ${album.SingerId}, AlbumId: ${album.AlbumId}, AlbumTitle: ${album.AlbumTitle}`
    );
  });
  await database.close();
}
queryTags();

PHP

use Google\Cloud\Spanner\SpannerClient;

/**
 * Executes a read with a request tag.
 * Example:
 * ```
 * spanner_set_request_tag($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function set_request_tag(string $instanceId, string $databaseId): void
{
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $snapshot = $database->snapshot();
    $results = $snapshot->execute(
        'SELECT SingerId, AlbumId, AlbumTitle FROM Albums',
        [
            'requestOptions' => [
                'requestTag' => 'app=concert,env=dev,action=select'
            ]
        ]
    );
    foreach ($results as $row) {
        printf('SingerId: %s, AlbumId: %s, AlbumTitle: %s' . PHP_EOL,
            $row['SingerId'], $row['AlbumId'], $row['AlbumTitle']);
    }
}

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)

with database.snapshot() as snapshot:
    results = snapshot.execute_sql(
        "SELECT SingerId, AlbumId, AlbumTitle FROM Albums",
        request_options={"request_tag": "app=concert,env=dev,action=select"},
    )

    for row in results:
        print("SingerId: {}, AlbumId: {}, AlbumTitle: {}".format(*row))

Ruby

# project_id  = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID"

require "google/cloud/spanner"

spanner = Google::Cloud::Spanner.new project: project_id
client = spanner.client instance_id, database_id

client.execute(
  "SELECT SingerId, AlbumId, MarketingBudget FROM Albums",
  request_options: { tag: "app=concert,env=dev,action=select" }
).rows.each do |row|
  puts "#{row[:SingerId]} #{row[:AlbumId]} #{row[:MarketingBudget]}"
end

Cara melihat tag permintaan di tabel statistik

Kueri berikut menampilkan statistik kueri selama interval 10 menit.

SELECT t.text,
       t.request_tag,
       t.execution_count,
       t.avg_latency_seconds,
       t.avg_rows,
       t.avg_bytes
FROM SPANNER_SYS.QUERY_STATS_TOP_10MINUTE AS t
LIMIT 3;

Mari kita ambil data berikut sebagai contoh hasil yang kita dapatkan dari kueri.

text request_tag execution_count avg_latency_seconds avg_rows avg_bytes
SELECT SingerId, AlbumId, AlbumTitle FROM Albums app=concert,env=dev,action=select 212 0,025 21 2365
select * from orders; app=catalogsearch,env=dev,action=list 55 0,02 16 33,35
SELECT SingerId, FirstName, LastName FROM Singers; [string kosong] 154 0,048 42 486,33

Dari tabel hasil ini, kita dapat melihat bahwa jika Anda telah menetapkan REQUEST_TAG untuk kueri, kueri tersebut akan diisi di tabel statistik. Jika tidak ada tag permintaan yang ditetapkan, tag tersebut akan ditampilkan sebagai string kosong.

Untuk kueri yang diberi tag, statistik digabungkan per tag (misalnya, tag permintaan app=concert,env=dev,action=select memiliki latensi rata-rata 0,025 detik). Jika tidak ada tag yang ditetapkan, statistik akan digabungkan per kueri (misalnya, kueri di baris ketiga memiliki latensi rata-rata 0,048 detik).

Tag transaksi

Tag transaksi opsional dapat ditambahkan ke setiap transaksi. Spanner mengelompokkan statistik menurut tag transaksi, yang terlihat di kolom TRANSACTION_TAG pada tabel statistik transaksi.

Kapan harus menggunakan tag transaksi

Berikut adalah beberapa skenario yang mendapatkan manfaat dari penggunaan tag transaksi.

  • Menemukan sumber transaksi yang bermasalah: Spanner mengumpulkan statistik untuk transaksi baca-tulis di tabel statistik transaksi. Jika menemukan transaksi lambat di tabel statistik transaksi, dan jika telah menetapkan tag ke transaksi tersebut, Anda dapat mengidentifikasi sumber (aplikasi/microservice) yang memanggil transaksi ini berdasarkan informasi dalam tag.
  • Mengidentifikasi transaksi dalam tabel statistik: Menetapkan tag transaksi membantu memfilter baris dalam tabel statistik transaksi berdasarkan tag yang Anda minati. Tanpa tag transaksi, menemukan operasi yang diwakili oleh statistik dapat menjadi proses yang rumit. Misalnya, untuk statistik transaksi, Anda harus memeriksa tabel dan kolom yang terlibat untuk mengidentifikasi transaksi yang tidak diberi tag.
  • Menemukan apakah transaksi dari aplikasi atau microservice tertentu lambat: Tag transaksi dapat membantu mengidentifikasi apakah transaksi dari aplikasi atau microservice tertentu memiliki latensi yang lebih tinggi.
  • Mengelompokkan statistik untuk sekumpulan transaksi: Anda dapat menggunakan tag transaksi untuk melacak, membandingkan, dan melaporkan performa untuk sekumpulan transaksi serupa.
  • Menemukan transaksi yang mengakses kolom yang terlibat dalam konflik kunci: Tag transaksi dapat membantu menentukan setiap transaksi yang menyebabkan konflik kunci di tabel Statistik kunci.
  • Men-streaming data perubahan pengguna dari Spanner menggunakan aliran data perubahan: Data aliran data perubahan berisi tag transaksi untuk transaksi yang mengubah data pengguna. Hal ini memungkinkan pembaca aliran perubahan untuk mengaitkan perubahan dengan jenis transaksi berdasarkan tag.

Cara menetapkan tag transaksi

Contoh berikut menunjukkan cara menetapkan tag transaksi menggunakan library klien Spanner. Saat menggunakan library klien, Anda dapat menetapkan tag transaksi di awal panggilan transaksi yang diterapkan ke semua operasi individual di dalam transaksi tersebut.

C++

void SetTransactionTag(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;
  using ::google::cloud::StatusOr;

  // Sets the transaction tag to "app=concert,env=dev". This will be
  // applied to all the individual operations inside this transaction.
  auto commit_options =
      google::cloud::Options{}.set<spanner::TransactionTagOption>(
          "app=concert,env=dev");
  auto commit = client.Commit(
      [&client](
          spanner::Transaction const& txn) -> StatusOr<spanner::Mutations> {
        spanner::SqlStatement update_statement(
            "UPDATE Venues SET Capacity = CAST(Capacity/4 AS INT64)"
            "  WHERE OutdoorVenue = false");
        // Sets the request tag to "app=concert,env=dev,action=update".
        // This will only be set on this request.
        auto update = client.ExecuteDml(
            txn, std::move(update_statement),
            google::cloud::Options{}.set<spanner::RequestTagOption>(
                "app=concert,env=dev,action=update"));
        if (!update) return std::move(update).status();

        spanner::SqlStatement insert_statement(
            "INSERT INTO Venues (VenueId, VenueName, Capacity, OutdoorVenue, "
            "                    LastUpdateTime)"
            " VALUES (@venueId, @venueName, @capacity, @outdoorVenue, "
            "         PENDING_COMMIT_TIMESTAMP())",
            {
                {"venueId", spanner::Value(81)},
                {"venueName", spanner::Value("Venue 81")},
                {"capacity", spanner::Value(1440)},
                {"outdoorVenue", spanner::Value(true)},
            });
        // Sets the request tag to "app=concert,env=dev,action=insert".
        // This will only be set on this request.
        auto insert = client.ExecuteDml(
            txn, std::move(insert_statement),
            google::cloud::Options{}.set<spanner::RequestTagOption>(
                "app=concert,env=dev,action=select"));
        if (!insert) return std::move(insert).status();
        return spanner::Mutations{};
      },
      commit_options);
  if (!commit) throw std::move(commit).status();
}

C#


using Google.Cloud.Spanner.Data;
using System.Threading.Tasks;

public class TransactionTagAsyncSample
{
    public async Task<int> TransactionTagAsync(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 =>
        {
            // Sets the transaction tag to "app=concert,env=dev".
            // This transaction tag will be applied to all the individual operations inside
            // the transaction.
            transaction.Tag = "app=concert,env=dev";

            // Sets the request tag to "app=concert,env=dev,action=update".
            // This request tag will only be set on this request.
            var updateCommand =
                connection.CreateDmlCommand("UPDATE Venues SET Capacity = DIV(Capacity, 4) WHERE OutdoorVenue = false");
            updateCommand.Tag = "app=concert,env=dev,action=update";
            updateCommand.Transaction = transaction;
            int rowsModified = await updateCommand.ExecuteNonQueryAsync();

            var insertCommand = connection.CreateDmlCommand(
                @"INSERT INTO Venues (VenueId, VenueName, Capacity, OutdoorVenue, LastUpdateTime)
                    VALUES (@venueId, @venueName, @capacity, @outdoorVenue, PENDING_COMMIT_TIMESTAMP())",
                new SpannerParameterCollection
                {
                    {"venueId", SpannerDbType.Int64, 81},
                    {"venueName", SpannerDbType.String, "Venue 81"},
                    {"capacity", SpannerDbType.Int64, 1440},
                    {"outdoorVenue", SpannerDbType.Bool, true}
                }
            );
            // Sets the request tag to "app=concert,env=dev,action=insert".
            // This request tag will only be set on this request.
            insertCommand.Tag = "app=concert,env=dev,action=insert";
            insertCommand.Transaction = transaction;
            rowsModified += await insertCommand.ExecuteNonQueryAsync();
            return rowsModified;
        });
    }
}

Go


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
)

// readWriteTransactionWithTag executes the update and insert queries on venues table with appropriate transaction and requests tag
func readWriteTransactionWithTag(w io.Writer, db string) error {
	// db = `projects/<project>/instances/<instance-id>/database/<database-id>`
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	_, err = client.ReadWriteTransactionWithOptions(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
		stmt := spanner.Statement{
			SQL: `UPDATE Venues SET Capacity = CAST(Capacity/4 AS INT64) WHERE OutdoorVenue = false`,
		}
		_, err := txn.UpdateWithOptions(ctx, stmt, spanner.QueryOptions{RequestTag: "app=concert,env=dev,action=update"})
		if err != nil {
			return err
		}
		fmt.Fprint(w, "Venue capacities updated.")
		stmt = spanner.Statement{
			SQL: `INSERT INTO Venues (VenueId, VenueName, Capacity, OutdoorVenue, LastUpdateTime) 
                   VALUES (@venueId, @venueName, @capacity, @outdoorVenue, PENDING_COMMIT_TIMESTAMP())`,
			Params: map[string]interface{}{
				"venueId":      81,
				"venueName":    "Venue 81",
				"capacity":     1440,
				"outdoorVenue": true,
			},
		}
		_, err = txn.UpdateWithOptions(ctx, stmt, spanner.QueryOptions{RequestTag: "app=concert,env=dev,action=insert"})
		if err != nil {
			return err
		}
		fmt.Fprint(w, "New venue inserted.")
		return nil
	}, spanner.TransactionOptions{TransactionTag: "app=concert,env=dev"})
	return err
}

Java

static void setTransactionTag(DatabaseClient databaseClient) {
  // Sets the transaction tag to "app=concert,env=dev".
  // This transaction tag will be applied to all the individual operations inside this
  // transaction.
  databaseClient
      .readWriteTransaction(Options.tag("app=concert,env=dev"))
      .run(transaction -> {
        // Sets the request tag to "app=concert,env=dev,action=update".
        // This request tag will only be set on this request.
        transaction.executeUpdate(
            Statement.of("UPDATE Venues"
                + " SET Capacity = CAST(Capacity/4 AS INT64)"
                + " WHERE OutdoorVenue = false"),
            Options.tag("app=concert,env=dev,action=update"));
        System.out.println("Venue capacities updated.");

        Statement insertStatement = Statement.newBuilder(
            "INSERT INTO Venues"
                + " (VenueId, VenueName, Capacity, OutdoorVenue, LastUpdateTime)"
                + " VALUES ("
                + " @venueId, @venueName, @capacity, @outdoorVenue, PENDING_COMMIT_TIMESTAMP()"
                + " )")
            .bind("venueId")
            .to(81)
            .bind("venueName")
            .to("Venue 81")
            .bind("capacity")
            .to(1440)
            .bind("outdoorVenue")
            .to(true)
            .build();

        // Sets the request tag to "app=concert,env=dev,action=insert".
        // This request tag will only be set on this request.
        transaction.executeUpdate(
            insertStatement,
            Options.tag("app=concert,env=dev,action=insert"));
        System.out.println("New venue inserted.");

        return null;
      });
}

Node.js

/**
 * TODO(developer): Uncomment the following lines before running the sample.
 */
// const projectId = 'my-project-id';
// const instanceId = 'my-instance';
// const databaseId = 'my-database';

// Imports the Google Cloud client library
const {Spanner} = require('@google-cloud/spanner');

// Creates a client
const spanner = new Spanner({
  projectId: projectId,
});

async function transactionTag() {
  // Gets a reference to a Cloud Spanner instance and database.
  const instance = spanner.instance(instanceId);
  const database = instance.database(databaseId);

  // Run a transaction with a transaction tag that will automatically be
  // included with each request in the transaction.
  try {
    await database.runTransactionAsync(
      {requestOptions: {transactionTag: 'app=cart,env=dev'}},
      async tx => {
        // Set the request tag to "app=concert,env=dev,action=update".
        // This request tag will only be set on this request.
        await tx.runUpdate({
          sql: 'UPDATE Venues SET Capacity = DIV(Capacity, 4) WHERE OutdoorVenue = false',
          requestOptions: {requestTag: 'app=concert,env=dev,action=update'},
        });
        console.log('Updated capacity of all indoor venues to 1/4.');

        await tx.runUpdate({
          sql: `INSERT INTO Venues (VenueId, VenueName, Capacity, OutdoorVenue, LastUpdateTime)
                VALUES (@venueId, @venueName, @capacity, @outdoorVenue, PENDING_COMMIT_TIMESTAMP())`,
          params: {
            venueId: 81,
            venueName: 'Venue 81',
            capacity: 1440,
            outdoorVenue: true,
          },
          types: {
            venueId: {type: 'int64'},
            venueName: {type: 'string'},
            capacity: {type: 'int64'},
            outdoorVenue: {type: 'bool'},
          },
          requestOptions: {requestTag: 'app=concert,env=dev,action=update'},
        });
        console.log('Inserted new outdoor venue');

        await tx.commit();
      }
    );
  } catch (err) {
    console.error('ERROR:', err);
  } finally {
    await database.close();
  }
}
transactionTag();

PHP

use Google\Cloud\Spanner\SpannerClient;
use Google\Cloud\Spanner\Transaction;

/**
 * Executes a transaction with a transaction tag.
 * Example:
 * ```
 * spanner_set_transaction_tag($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function set_transaction_tag(string $instanceId, string $databaseId): void
{
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $database->runTransaction(function (Transaction $t) {
        $t->executeUpdate(
            'UPDATE Venues SET Capacity = CAST(Capacity/4 AS INT64) WHERE OutdoorVenue = false',
            [
                'requestOptions' => ['requestTag' => 'app=concert,env=dev,action=update']
            ]
        );
        print('Venue capacities updated.' . PHP_EOL);
        $t->executeUpdate(
            'INSERT INTO Venues (VenueId, VenueName, Capacity, OutdoorVenue, LastUpdateTime) '
            . 'VALUES (@venueId, @venueName, @capacity, @outdoorVenue, PENDING_COMMIT_TIMESTAMP())',
            [
                'parameters' => [
                    'venueId' => 81,
                    'venueName' => 'Venue 81',
                    'capacity' => 1440,
                    'outdoorVenue' => true,
                ],
                'requestOptions' => ['requestTag' => 'app=concert,env=dev,action=insert']
            ]
        );
        print('New venue inserted.' . PHP_EOL);
        $t->commit();
    }, [
        'requestOptions' => ['transactionTag' => 'app=concert,env=dev']
    ]);
}

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 update_venues(transaction):
    # Sets the request tag to "app=concert,env=dev,action=update".
    #  This request tag will only be set on this request.
    transaction.execute_update(
        "UPDATE Venues SET Capacity = CAST(Capacity/4 AS INT64) WHERE OutdoorVenue = false",
        request_options={"request_tag": "app=concert,env=dev,action=update"},
    )
    print("Venue capacities updated.")

    # Sets the request tag to "app=concert,env=dev,action=insert".
    # This request tag will only be set on this request.
    transaction.execute_update(
        "INSERT INTO Venues (VenueId, VenueName, Capacity, OutdoorVenue, LastUpdateTime) "
        "VALUES (@venueId, @venueName, @capacity, @outdoorVenue, PENDING_COMMIT_TIMESTAMP())",
        params={
            "venueId": 81,
            "venueName": "Venue 81",
            "capacity": 1440,
            "outdoorVenue": True,
        },
        param_types={
            "venueId": param_types.INT64,
            "venueName": param_types.STRING,
            "capacity": param_types.INT64,
            "outdoorVenue": param_types.BOOL,
        },
        request_options={"request_tag": "app=concert,env=dev,action=insert"},
    )
    print("New venue inserted.")

database.run_in_transaction(update_venues, transaction_tag="app=concert,env=dev")

Ruby

# project_id  = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID"

require "google/cloud/spanner"

spanner = Google::Cloud::Spanner.new project: project_id
client = spanner.client instance_id, database_id

client.transaction request_options: { tag: "app=cart,env=dev" } do |tx|
  tx.execute_update \
    "UPDATE Venues SET Capacity = CAST(Capacity/4 AS INT64) WHERE OutdoorVenue = false",
    request_options: { tag: "app=concert,env=dev,action=update" }

  puts "Venue capacities updated."

  tx.execute_update \
    "INSERT INTO Venues (VenueId, VenueName, Capacity, OutdoorVenue) " \
    "VALUES (@venue_id, @venue_name, @capacity, @outdoor_venue)",
    params: {
      venue_id: 81,
      venue_name: "Venue 81",
      capacity: 1440,
      outdoor_venue: true
    },
    request_options: { tag: "app=concert,env=dev,action=insert" }

  puts "New venue inserted."
end

Cara melihat tag transaksi di tabel Statistik Transaksi

Kueri berikut menampilkan statistik transaksi selama interval 10 menit.

SELECT t.fprint,
       t.transaction_tag,
       t.read_columns,
       t.commit_attempt_count,
       t.avg_total_latency_seconds
FROM SPANNER_SYS.TXN_STATS_TOP_10MINUTE AS t
LIMIT 3;

Mari kita ambil data berikut sebagai contoh hasil yang kita dapatkan dari kueri.

fprint transaction_tag read_columns commit_attempt_count avg_total_latency_seconds
40015598317 app=concert,env=dev [Venues._exists,
Venues.VenueId,
Venues.VenueName,
Venues.Capacity]
278802 0,3508
20524969030 app=product,service=payment [Singers.SingerInfo] 129012 0,0142
77848338483 [string kosong] [Singers.FirstName, Singers.LastName, Singers._exists] 5357 0,048

Dari tabel hasil ini, kita dapat melihat bahwa jika Anda telah menetapkan TRANSACTION_TAG ke transaksi, TRANSACTION_TAG tersebut akan diisi di tabel statistik transaksi. Jika tidak ada tag transaksi yang ditetapkan, tag tersebut akan ditampilkan sebagai string kosong.

Untuk transaksi yang diberi tag, statistik digabungkan per tag transaksi (misalnya, tag transaksi app=concert,env=dev a memiliki latensi rata-rata 0,3508 detik). Jika tidak ada tag yang ditetapkan, statistik akan digabungkan per FPRINT (misalnya, 77848338483 di baris ketiga memiliki latensi rata-rata 0,048 detik).

Cara melihat tag transaksi di tabel Statistik Kunci

Kueri berikut menampilkan statistik kunci selama interval 10 menit.

Fungsi CAST() mengonversi kolom BYTES row_range_start_key menjadi STRING.

SELECT 
   CAST(s.row_range_start_key AS STRING) AS row_range_start_key,
   s.lock_wait_seconds,
   s.sample_lock_requests
FROM SPANNER_SYS.LOCK_STATS_TOP_10MINUTE s
LIMIT 2;

Mari kita ambil data berikut sebagai contoh hasil yang kita dapatkan dari kueri.

row_range_start_key lock_wait_seconds sample_lock_requests
Songs(2,1,1) 0,61 LOCK_MODE: ReaderShared
COLUMN: Singers.SingerInfo
TRANSACTION_TAG: app=product,service=shipping

LOCK_MODE: WriterShared
COLUMN: Singers.SingerInfo
TRANSACTION_TAG: app=product,service=payment
albums(2,1+) 0,48 LOCK_MODE: ReaderShared
COLUMN: users._exists1
TRANSACTION_TAG: [string kosong]

LOCK_MODE: WriterShared
COLUMN: users._exists
TRANSACTION_TAG: [string kosong]

Dari tabel hasil ini, kita dapat melihat bahwa jika Anda telah menetapkan TRANSACTION_TAG ke transaksi, transaksi tersebut akan diisi di tabel statistik kunci. Jika tidak ada tag transaksi yang ditetapkan, tag tersebut akan ditampilkan sebagai string kosong.

Pemetaan antara metode API dan tag permintaan/transaksi

Tag permintaan dan tag transaksi berlaku untuk metode API tertentu berdasarkan apakah mode transaksi adalah transaksi hanya baca atau transaksi baca-tulis. Umumnya, tag transaksi berlaku untuk transaksi baca-tulis, sedangkan tag permintaan berlaku untuk transaksi hanya baca. Tabel berikut menunjukkan pemetaan dari metode API ke jenis tag yang berlaku.

Metode API Mode Transaksi Tag Permintaan Tag Transaksi
Read,
StreamingRead
Transaksi hanya baca Ya Tidak
Transaksi baca-tulis Ya Ya
ExecuteSql,
ExecuteStreamingSql1
Transaksi hanya baca1 Ya1 Tidak
Transaksi baca-tulis Ya Ya
ExecuteBatchDml Transaksi baca-tulis Ya Ya
BeginTransaction Transaksi baca-tulis Tidak Ya
Commit Transaksi baca-tulis Tidak Ya

1 Untuk kueri aliran perubahan yang dijalankan menggunakan konektor Dataflow SpannerIO Apache Beam, REQUEST_TAG berisi nama tugas Dataflow.

Batasan

Saat menambahkan tag ke pembacaan, kueri, dan transaksi, pertimbangkan batasan berikut:

  • Panjang string tag dibatasi hingga 50 karakter. String yang melebihi batas ini akan terpotong.
  • Hanya karakter ASCII (32-126) yang diizinkan dalam tag. Karakter unicode arbitrer diganti dengan garis bawah.
  • Semua karakter garis bawah (_) di awal akan dihapus dari string.
  • Tag peka huruf besar/kecil. Misalnya, jika Anda menambahkan tag permintaan APP=cart,ENV=dev ke satu kumpulan kueri, dan menambahkan app=cart,env=dev ke kumpulan kueri lain, Spanner akan menggabungkan statistik secara terpisah untuk setiap tag.
  • Tag mungkin tidak ada di tabel statistik dalam keadaan berikut:

    • Jika Spanner tidak dapat menyimpan statistik untuk semua operasi bertag yang dijalankan selama interval dalam tabel, sistem akan memprioritaskan operasi dengan resource yang paling banyak digunakan selama interval yang ditentukan.

Penamaan tag

Saat menetapkan tag ke operasi database, penting untuk mempertimbangkan informasi yang ingin Anda sampaikan dalam setiap string tag. Konvensi atau pola yang Anda pilih akan membuat tag Anda lebih efektif. Misalnya, penamaan tag yang tepat akan mempermudah korelasi statistik dengan kode aplikasi.

Anda dapat memilih tag yang diinginkan dalam batasan yang dinyatakan. Namun, sebaiknya buat string tag sebagai kumpulan pasangan nilai kunci yang dipisahkan oleh koma.

Misalnya, asumsikan Anda menggunakan database Spanner untuk kasus penggunaan e-commerce. Sebaiknya sertakan informasi tentang aplikasi, lingkungan pengembangan, dan tindakan yang dilakukan oleh kueri dalam tag permintaan yang akan Anda tetapkan ke kueri tertentu. Anda dapat mempertimbangkan untuk menetapkan string tag dalam format nilai kunci sebagai app=cart,env=dev,action=update.Artinya, kueri dipanggil dari aplikasi keranjang di lingkungan pengembangan, dan digunakan untuk memperbarui keranjang.

Misalkan Anda memiliki kueri lain dari aplikasi penelusuran katalog dan menetapkan string tag sebagai app=catalogsearch,env=dev,action=list. Sekarang, jika salah satu kueri ini muncul di tabel statistik kueri sebagai kueri latensi tinggi, Anda dapat dengan mudah mengidentifikasi sumbernya menggunakan tag.

Berikut beberapa contoh cara pola pemberian tag dapat digunakan untuk mengatur statistik operasi Anda. Contoh ini tidak dimaksudkan untuk menjadi lengkap; Anda juga dapat menggabungkannya dalam string tag menggunakan pembatas seperti koma.

Kunci tag Contoh Pasangan nilai tag Deskripsi
Aplikasi app=cart
app=frontend
app=catalogsearch
Membantu mengidentifikasi aplikasi yang memanggil operasi.
Lingkungan env=prod
env=dev
env=test
env=staging
Membantu mengidentifikasi lingkungan yang terkait dengan operasi.
Framework framework=spring
framework=django
framework=jetty
Membantu mengidentifikasi framework yang terkait dengan operasi.
Tindakan action=list
action=retrieve
action=update
Membantu mengidentifikasi tindakan yang dilakukan oleh operasi.
Layanan service=payment
service=shipping
Membantu mengidentifikasi microservice yang memanggil operasi.

Hal-Hal yang Perlu Diperhatikan

  • Saat Anda menetapkan REQUEST_TAG, statistik untuk beberapa kueri yang memiliki string tag yang sama akan dikelompokkan dalam satu baris di tabel statistik kueri. Hanya teks salah satu kueri tersebut yang ditampilkan di kolom TEXT.
  • Saat Anda menetapkan REQUEST_TAG, statistik untuk beberapa operasi baca yang memiliki string tag yang sama akan dikelompokkan dalam satu baris di tabel statistik baca. Kumpulan semua kolom yang dibaca ditambahkan ke kolom READ_COLUMNS.
  • Saat Anda menetapkan TRANSACTION_TAG, statistik untuk transaksi yang memiliki string tag yang sama akan dikelompokkan dalam satu baris di tabel statistik transaksi. Kumpulan semua kolom yang ditulis oleh transaksi ditambahkan ke kolom WRITE_CONSTRUCTIVE_COLUMNS dan kumpulan semua kolom yang dibaca ditambahkan ke kolom READ_COLUMNS.

Memecahkan masalah skenario menggunakan tag

Menemukan sumber transaksi yang bermasalah

Kueri berikut menampilkan data mentah untuk transaksi teratas dalam jangka waktu yang dipilih.

SELECT
 fprint,
 transaction_tag,
 ROUND(avg_total_latency_seconds,4) as avg_total_latency_sec,
 ROUND(avg_commit_latency_seconds,4) as avg_commit_latency_sec,
 commit_attempt_count,
 commit_abort_count
FROM SPANNER_SYS.TXN_STATS_TOP_10MINUTE
WHERE interval_end = "2020-05-17T18:40:00"
ORDER BY avg_total_latency_seconds DESC;

Tabel berikut mencantumkan contoh data yang ditampilkan dari kueri kita, dengan tiga aplikasi, yaitu keranjang, produk, dan frontend, yang memiliki atau mengkueri database yang sama.

Setelah mengidentifikasi transaksi yang mengalami latensi tinggi, Anda dapat menggunakan tag terkait untuk mengidentifikasi bagian kode aplikasi yang relevan, dan memecahkan masalah lebih lanjut menggunakan statistik transaksi.

fprint transaction_tag avg_total_latency_sec avg_commit_latency_sec commit_attempt_count commit_abort_count
7129109266372596045 app=cart,service=order 0,3508 0,0139 278802 142205
9353100217060788102 app=cart,service=redis 0,1633 0,0142 129012 27177
9353100217060788102 app=product,service=payment 0,1423 0,0133 5357 636
898069986622520747 app=product,service=shipping 0,0159 0,0118 4269 1
9521689070912159706 app=frontend,service=ads 0,0093 0,0045 164 0
11079878968512225881 [string kosong] 0,031 0,015 14 0

Demikian pula, Tag Permintaan dapat digunakan untuk menemukan sumber kueri yang bermasalah dari tabel statistik kueri dan sumber pembacaan yang bermasalah dari tabel statistik pembacaan.

Menemukan latensi dan statistik lainnya untuk transaksi dari aplikasi atau layanan mikro tertentu

Jika Anda telah menggunakan nama aplikasi atau nama microservice dalam string tag, hal ini akan membantu memfilter tabel statistik transaksi menurut tag yang berisi nama aplikasi atau nama microservice tersebut.

Misalnya, Anda telah menambahkan transaksi baru ke aplikasi payment dan ingin melihat latensi dan statistik lainnya dari transaksi baru tersebut. Jika telah menggunakan nama aplikasi pembayaran dalam tag, Anda dapat memfilter tabel statistik transaksi hanya untuk tag yang berisi app=payment.

Kueri berikut menampilkan statistik transaksi untuk aplikasi pembayaran selama interval 10 menit.

SELECT
  transaction_tag,
  avg_total_latency_sec,
  avg_commit_latency_sec,
  commit_attempt_count,
  commit_abort_count
FROM SPANNER_SYS.TXN_STATS_TOP_10MINUTE
WHERE STARTS_WITH(transaction_tag, "app=payment")
LIMIT 3;

Berikut adalah beberapa contoh output:

transaction_tag avg_total_latency_sec avg_commit_latency_sec commit_attempt_count commit_abort_count
app=payment,action=update 0,3508 0,0139 278802 142205
app=payment,action=transfer 0,1633 0,0142 129012 27177
app=payment, action=retrieve 0,1423 0,0133 5357 636

Demikian pula, Anda dapat menemukan kueri atau operasi baca dari aplikasi tertentu di tabel statistik kueri atau statistik baca menggunakan tag permintaan.

Menemukan transaksi yang terlibat dalam konflik kunci

Untuk mengetahui transaksi dan kunci baris mana yang mengalami waktu tunggu kunci yang tinggi, kita membuat kueri tabel LOCK_STAT_TOP_10MINUTE, yang mencantumkan kunci baris, kolom, dan transaksi yang sesuai yang terlibat dalam konflik kunci.

SELECT CAST(s.row_range_start_key AS STRING) AS row_range_start_key,
       t.total_lock_wait_seconds,
       s.lock_wait_seconds,
       s.lock_wait_seconds/t.total_lock_wait_seconds frac_of_total,
       s.sample_lock_requests
FROM spanner_sys.lock_stats_total_10minute t, spanner_sys.lock_stats_top_10minute s
WHERE
  t.interval_end = "2020-05-17T18:40:00" and s.interval_end = t.interval_end;

Berikut adalah beberapa contoh output dari kueri kita:

row_range_start_key total_lock_wait_seconds lock_wait_seconds frac_of_total sample_lock_requests
Penyanyi(32) 2,37 1,76 1 LOCK_MODE: WriterShared
COLUMN: Singers.SingerInfo
TRANSACTION_TAG:
app=cart,service=order

LOCK_MODE: ReaderShared
COLUMN: Singers.SingerInfo
TRANSACTION_TAG:
app=cart,service=redis

Dari tabel hasil ini, kita dapat melihat konflik yang terjadi pada tabel Singers pada kunci SingerId=32. Singers.SingerInfo adalah kolom tempat konflik kunci terjadi antara ReaderShared dan WriterShared. Anda juga dapat mengidentifikasi transaksi yang sesuai (app=cart,service=order dan app=cart,service=redis) yang mengalami konflik.

Setelah transaksi yang menyebabkan konflik kunci diidentifikasi, Anda kini dapat berfokus pada transaksi ini menggunakan Statistik Transaksi untuk mendapatkan pemahaman yang lebih baik tentang apa yang dilakukan transaksi dan apakah Anda dapat menghindari konflik atau mengurangi waktu kunci ditahan. Untuk informasi selengkapnya, lihat Praktik terbaik untuk mengurangi pertentangan kunci.

Langkah selanjutnya