Memecahkan masalah dengan tag permintaan dan tag transaksi

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

Spanner mendukung dua jenis tag; tag request dan tag transaction. Seperti namanya, Anda dapat menambahkan tag transaksi ke transaksi, dan meminta tag ke masing-masing kueri dan membaca API. 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 di kode aplikasi akan diisi di kolom tabel statistik berikut.

Tabel Statistik Jenis Tag yang diisi di tabel statistik
Statistik Kueri TopN Minta tag
Statistik Bacaan TopN Minta tag
Statistik Transaksi TopN Tag transaksi
Statistik TopN Lock Tag transaksi

Minta tag

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

Kapan tag permintaan digunakan

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 di tabel statistik bawaan. Ketika Anda menemukan kueri lambat atau pembacaan yang memakan CPU tinggi dalam tabel statistik, jika 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 akan membantu memfilter baris dalam tabel statistik berdasarkan tag yang Anda minati.
  • Mengetahui 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 kumpulan pembacaan atau kueri: Anda dapat menggunakan tag permintaan untuk melacak, membandingkan, dan melaporkan performa di seluruh kumpulan pembacaan atau kueri yang serupa. Misalnya, jika beberapa kueri mengakses tabel/kumpulan tabel dengan pola akses yang sama, Anda dapat mempertimbangkan untuk menambahkan tag yang sama ke semua kueri tersebut agar dapat 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 untuk 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 dapat dari kueri.

teks request_tag execution_count avg_latency_seconds avg_rows avg_bytes
SELECT SingerId, AlbumId, AlbumTitle FROM Album app=concert,env=dev,action=select 212 0,025 21 2365
pilih * dari pesanan; app=catalogsearch,env=dev,action=list 55 0.02 16 33,35
{i>SELECT SingerId<i}, 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 sebuah kueri, kueri tersebut akan diisikan 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 transaksi individual. Spanner mengelompokkan statistik berdasarkan tag transaksi, yang dapat dilihat di kolom TRANSACTION_TAG pada tabel statistik transaksi.

Kapan harus menggunakan tag transaksi

Berikut adalah beberapa skenario yang dapat memanfaatkan penggunaan tag transaksi.

  • Menemukan sumber transaksi yang bermasalah: Spanner mengumpulkan statistik untuk transaksi baca-tulis di tabel statistik transaksi. Saat menemukan transaksi lambat di tabel statistik transaksi, jika telah menetapkan tag ke transaksi tersebut, Anda dapat mengidentifikasi sumber (aplikasi/layanan mikro) yang memanggil transaksi ini berdasarkan informasi dalam tag.
  • Mengidentifikasi transaksi di tabel statistik: Menetapkan tag transaksi akan 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.
  • Mengetahui 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 yang serupa.
  • Menemukan transaksi mana yang mengakses kolom yang terlibat dalam konflik kunci: Tag transaksi dapat membantu menentukan transaksi individual yang menyebabkan konflik kunci di tabel Statistik kunci.
  • Streaming data perubahan pengguna dari Spanner menggunakan aliran perubahan: Data data aliran 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 akan 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 dapat 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, transaksi 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 Kunci Statistik

Kueri berikut menampilkan statistik penguncian selama interval 10 menit.

Fungsi CAST() mengonversi kolom row_range_start_key myactivity 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 dapat dari kueri.

row_range_start_key lock_wait_seconds sample_lock_requests
Lagu(2;1;1) 0,61 LOCK_MODE: ReaderShared
KOLOM: Singers.SingerInfo
Transition_TAG: app=product,service=shipping

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

LOCK_MODE: WriterShared
COLUMN: users._exists
TRANS_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 menampilkan pemetaan dari metode API ke jenis tag yang berlaku.

Metode API Mode Transaksi Tag Permintaan Tag Transaksi
Baca,
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 data perubahan yang dijalankan menggunakan konektor Dataflow Apache Beam Spanner, REQUEST_TAG berisi nama tugas Dataflow.

Batasan

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

  • Panjang string tag dibatasi hingga 50 karakter. String yang melebihi batas ini akan terpotong.
  • Hanya karakter ASCII (32-126) yang diperbolehkan dalam tag. Karakter unicode arbitrer diganti dengan garis bawah.
  • Setiap karakter garis bawah (_) yang ada 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 lainnya, 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 yang diberi tag yang dijalankan selama interval dalam tabel, sistem akan memprioritaskan operasi dengan resource yang paling banyak menggunakan resource selama interval yang ditentukan.

Penamaan tag

Saat menetapkan tag ke operasi database, penting untuk mempertimbangkan informasi yang ingin Anda sampaikan di 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 Anda membuat string tag sebagai sekumpulan pasangan nilai kunci yang dipisahkan oleh koma.

Misalnya, anggaplah Anda menggunakan database Spanner untuk kasus penggunaan e-commerce. Sebaiknya sertakan informasi tentang aplikasi, lingkungan pengembangan, dan tindakan yang diambil oleh kueri di tag permintaan yang akan ditetapkan untuk 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 sumber dengan menggunakan tag.

Berikut beberapa contoh bagaimana pola pemberian tag dapat digunakan untuk mengatur statistik operasi Anda. Contoh ini tidak 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=fetch
action=update
Membantu mengidentifikasi tindakan yang diambil oleh operasi.
Layanan service=pembayaran
service=shipping
Membantu mengidentifikasi microservice yang memanggil operasi.

Hal yang Perlu Diperhatikan

  • Saat Anda menetapkan REQUEST_TAG, statistik untuk beberapa kueri yang memiliki string tag yang sama akan dikelompokkan dalam satu baris dalam tabel statistik kueri. Hanya teks dari 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 dalam tabel statistik baca. Kumpulan semua kolom yang dibaca akan 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 akan ditambahkan ke kolom WRITE_CONSTRUCTIVE_COLUMNS dan kumpulan semua kolom yang dibaca akan ditambahkan ke kolom READ_COLUMNS.

Skenario pemecahan masalah 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, tempat kita memiliki tiga aplikasi, yaitu cart, product, dan frontend, yang memiliki atau mengkueri database yang sama.

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

fprint transaction_tag avg_total_latency_sec avg_commit_latency_sec commit_attempt_count commit_abort_count
7129109266372596045 aplikasi=keranjang,layanan=pesanan 0,3508 0,0139 278802 142205
9353100217060788102 app=keranjang,layanan=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 bermasalah dari tabel statistik kueri dan sumber pembacaan bermasalah dari tabel statistik baca.

Menemukan latensi dan statistik lainnya untuk transaksi dari aplikasi atau microservice tertentu

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

Misalkan Anda telah menambahkan transaksi baru ke aplikasi pembayaran dan ingin melihat latensi serta statistik lain 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 dengan 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 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=fetch 0,1423 0,0133 5357 636

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

Menemukan transaksi yang terlibat dalam konflik kunci

Untuk mengetahui transaksi dan kunci baris mana yang mengalami waktu tunggu penguncian yang tinggi, kami membuat kueri tabel LOCK_STAT_TOP_10MINUTE yang mencantumkan kunci baris, kolom, dan transaksi terkait 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 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
Transcript_TAG:
app=cart,service=redis

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

Setelah transaksi yang menyebabkan konflik kunci diidentifikasi, kini Anda dapat fokus 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 mengetahui informasi selengkapnya, lihat Praktik terbaik untuk mengurangi pertentangan kunci.

Langkah selanjutnya