Fehlerbehebung mit Anfrage- und Transaktions-Tags

Spanner bietet eine Reihe von integrierten Statistiktabellen, mit denen Sie einen Einblick in Ihre Abfragen, Lesevorgänge und Transaktionen erhalten können. Sie können den Spanner-Lese-, Abfrage- und Transaktionsvorgängen in Ihrem Anwendungscode ein Tag (einen String im freien Format) hinzufügen, um Statistiken mit Ihrem Anwendungscode zu korrelieren und die Fehlerbehebung zu verbessern. Diese Tags werden in Statistiktabellen eingetragen, sodass Sie basierend auf Tags korrelieren und suchen können.

Spanner unterstützt zwei Arten von Tags: Anfrage-Tags und Transaktions-Tags. Wie die Namen vermuten lassen, können Sie Transaktions-Tags einer Transaktion hinzufügen und Anfrage-Tags an einzelne Abfragen und Lese-APIs. Sie können ein Transaktions-Tag auf Transaktionsebene festlegen und einzelne Anfrage-Tags für jede anwendbare API-Anfrage in der Transaktion festlegen. Anfrage- und Transaktions-Tags, die im Anwendungscode festgelegt sind, werden in die Spalten der folgenden Statistiktabellen eingetragen.

Statistiktabelle In der Statistiktabelle eingetragene Tag-Arten
Statistiken zu TopN-Abfragen Anfrage-Tags
Statistiken zu TopN-Lesevorgängen Anfrage-Tags
Statistiken zur TopN-Transaktionen Transaktions-Tags
Statistiken zu TopN-Sperren Transaktions-Tags

Anfrage-Tags

Sie können einer Abfrage oder einer Leseanfrage ein optionales Anfrage-Tag hinzufügen. Spanner gruppiert Statistiken nach Anfrage-Tag, das im Feld REQUEST_TAG der Tabellen Abfragestatistik und Lesestatistiken sichtbar ist.

Wann werden Anfrage-Tags verwendet?

Im Folgenden sind einige Szenarien aufgeführt, die sich für die Verwendung von Anfrage-Tags eignen.

  • Quelle einer problematischen Abfrage oder eines problematischen Lesevorgangs finden: Spanner erfasst Statistiken für Lesevorgänge und Abfragen in integrierten Statistiktabellen. Wenn Sie in der Statistiktabelle langsame Abfragen oder Lesevorgänge mit hoher CPU-Auslastung sehen, denen Sie bereits Tags zugewiesen haben, können Sie auf Basis der Informationen im Tag die Quelle (Anwendung/Mikrodienst) identifizieren, die diese Vorgänge aufruft.
  • Lesevorgänge oder Abfragen in Statistiktabellen identifizieren: Durch Zuweisen von Anfrage-Tags können Zeilen in der Statistiktabelle anhand der für Sie interessanten Tags gefiltert werden.
  • Ermitteln, ob Abfragen von einer bestimmten Anwendung oder einem bestimmten Mikrodienst langsam sind: Anfrage-Tags können dabei helfen, zu ermitteln, ob Abfragen einer bestimmten Anwendung oder eines bestimmten Mikrodienstes höhere Latenzen haben.
  • Statistiken für mehrere Lesevorgänge oder Abfragen gruppieren: Sie können Anfrage-Tags verwenden, um die Leistung für eine Reihe ähnlicher Lese- oder Abfragevorgänge zu verfolgen, zu vergleichen und zu melden. Wenn beispielsweise mehrere Abfragen auf eine Tabelle oder einen Tabellensatz mit demselben Zugriffsmuster zugreifen, können Sie allen diese Abfragen dasselbe Tag hinzufügen, um sie gemeinsam zu verfolgen.

So weisen Sie Anfrage-Tags zu

Das folgende Beispiel zeigt, wie Anfrage-Tags mithilfe der Spanner-Clientbibliotheken festgelegt werden.

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

So rufen Sie Anfrage-Tags in der Statistiktabelle auf

Die folgende Abfrage gibt die Abfragestatistiken in Intervallen von zehn Minuten zurück.

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;

Nehmen wir als Beispiel die folgenden Ergebnisse zu unserer Abfrage:

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
* aus den Bestellungen auswählen; app=catalogsearch,env=dev,action=list 55 0,02 16 33,35
SELECT SingerId, FirstName, LastName FROM Singers; [Leerer String] 154 0,048 42 486,33

In dieser Ergebnistabelle können Sie sehen, dass eine Abfrage, der Sie einen REQUEST_TAG zugewiesen haben, in der Statistiktabelle erfasst wird. Wenn kein Anfrage-Tag zugewiesen ist, wird sie als leerer String angezeigt.

Für die getaggten Abfragen werden die Statistiken pro Tag aggregiert. Beispiel: Das Anfrage-Tag app=concert,env=dev,action=select hat eine durchschnittliche Latenz von 0,025 Sekunden. Wenn kein Tag zugewiesen ist, werden die Statistiken pro Abfrage aggregiert. Beispiel: Die Abfrage in der dritten Zeile hat eine durchschnittliche Latenz von 0,048 Sekunden.

Transaktions-Tags

Ein optionales Transaktions-Tag kann einzelnen Transaktionen hinzugefügt werden. Spanner gruppiert Statistiken nach Transaktions-Tag. Dieses wird im Feld TRANSACTION_TAG von Tabellen mit Transaktionsstatistiken angezeigt.

Wann werden Transaktions-Tags verwendet?

Im Folgenden sind einige Szenarien aufgeführt, die sich für die Verwendung von Transaktions-Tags eignen.

  • Quelle einer problematischen Transaktion finden: Spanner erfasst Statistiken für Lese-/Schreibtransaktionen in der Transaktionsstatistiktabelle. Wenn Sie in der Tabelle mit den Transaktionsstatistiken langsame Transaktionen sehen, denen Sie bereits Tags zugewiesen haben, können Sie auf Basis der Informationen im Tag die Quelle (Anwendung/Mikrodienst) ermitteln, die diese Transaktionen aufruft.
  • Transaktionen in Statistiktabellen identifizieren: Durch Zuweisen von Transaktions-Tags können Zeilen in der Tabelle mit den Transaktionsstatistiken anhand der für Sie interessanten Tags gefiltert werden. Ohne Transaktions-Tags kann es mühsam sein, zu ermitteln, für welchen Vorgang eine Statistik steht. Für Transaktionsstatistiken müssten Sie beispielsweise die entsprechenden Tabellen und Spalten überprüfen, um die nicht getaggte Transaktion zu identifizieren.
  • Ermitteln, ob Transaktionen von einer bestimmten Anwendung oder einem bestimmten Mikrodienst langsam sind: Mit Transaktions-Tags können Sie feststellen, ob Transaktionen einer bestimmten Anwendung oder eines bestimmten Mikrodienstes höhere Latenzen haben.
  • Statistiken für mehrere Transaktionen gruppieren: Sie können Transaktions-Tags verwenden, um die Leistung für eine Reihe ähnlicher Transaktionen zu verfolgen, zu vergleichen und zu melden.
  • Ermitteln, welche Transaktionen auf die Spalten zugreifen, die am Sperrkonflikt beteiligt sind: Durch Transaktions-Tags können in den Tabelle mit den Sperrstatistiken präzise einzelne Transaktionen angezeigt werden, die Sperrkonflikte verursachen.
  • Streaming von Nutzeränderungsdaten aus Spanner mithilfe von Änderungsstreams: Änderungsstreams enthalten Transaktions-Tags für die Transaktionen, die die Nutzerdaten geändert haben. Dadurch kann der Leser eines Änderungsstreams Änderungen anhand von Tags mit dem Transaktionstyp verknüpfen.

So weisen Sie Transaktions-Tags zu

Das folgende Beispiel zeigt, wie Sie Transaktions-Tags mithilfe der Spanner-Clientbibliotheken festlegen. Wenn Sie eine Clientbibliothek verwenden, können Sie zu Beginn des Transaktionsaufrufs ein Transaktions-Tag festlegen, das auf alle einzelnen Vorgänge innerhalb dieser Transaktion angewendet wird.

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

Transaktions-Tags in der Tabelle mit den Transaktionsstatistiken anzeigen

Die folgende Abfrage gibt die Transaktionsstatistiken in Intervallen von 10 Minuten zurück.

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;

Nehmen wir als Beispiel die folgenden Ergebnisse zu unserer Abfrage:

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 [Leerer String] [Singers.FirstName, Singers.LastName, Singers._exists] 5357 0,048

In dieser Ergebnistabelle sehen Sie, dass eine Transaktion, der Sie einen TRANSACTION_TAG zugewiesen haben, in der Tabelle mit den Transaktionsstatistiken erfasst wird. Wenn kein Transaktions-Tag zugewiesen ist, wird sie als leerer String angezeigt.

Für die getaggten Transaktionen werden die Statistiken pro Transaktions-Tag zusammengefasst. Beispiel: Das Transaktions-Tag app=concert,env=dev hat eine durchschnittliche Latenz von 0,3508 Sekunden. Wenn kein Tag zugewiesen ist, werden die Statistiken pro FPRINT aggregiert. Beispiel: 77848338483 in der dritten Zeile hat eine durchschnittliche Latenz von 0,048 Sekunden.

Transaktions-Tags in der Tabelle mit den Sperrstatistiken anzeigen

Die folgende Abfrage gibt die Sperrstatistiken über Intervalle von 10 Minuten zurück.

Die Funktion CAST() konvertiert das BYTES-Feld row_range_start_key in einen 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;

Nehmen wir als Beispiel die folgenden Ergebnisse zu unserer Abfrage:

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
Alben(2,1+) 0,48 LOCK_MODE: ReaderShared
COLUMN: users._exists1
TRANSACTION_TAG: [empty string]

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

In dieser Ergebnistabelle sehen Sie, dass eine Transaktion, der Sie einen TRANSACTION_TAG zugewiesen haben, in der Tabelle mit den Sperrstatistiken erfasst wird. Wenn kein Transaktions-Tag zugewiesen ist, wird sie als leerer String angezeigt.

Zuordnung zwischen API-Methoden und Anfrage-/Transaktions-Tag

Anfrage- und Transaktions-Tags gelten für bestimmte API-Methoden, je nachdem, ob der Transaktionsmodus eine schreibgeschützte oder eine Lese-Schreib-Transaktion ist. Im Allgemeinen sind Transaktions-Tags auf Lese-Schreib-Transaktionen anwendbar, Anfrage-Tags hingegen auf schreibgeschützte Transaktionen. Die folgende Tabelle zeigt die Zuordnung von API-Methoden zu anwendbaren Tag-Typen.

API-Methoden Transaktionsmodi Anfrage-Tag Transaktions-Tag
Read,
StreamingRead
Schreibgeschützte Transaktion Ja Nein
Lese-/Schreibtransaktion Ja Ja
ExecuteSql,
ExecuteStreamingSql1
Schreibgeschützte Transaktion1 Ja1 Nein
Lese-/Schreibtransaktion Ja Ja
ExecuteBatchDml Lese-/Schreibtransaktion Ja Ja
BeginTransaction Lese-/Schreibtransaktion Nein Ja
Commit Lese-/Schreibtransaktion Nein Ja

1 Bei Änderungsstreamabfragen, die mit dem Apache Beam SpannerIO-Dataflow-Connector ausgeführt werden, enthält der REQUEST_TAG einen Dataflow-Jobnamen.

Beschränkungen

Beachten Sie beim Hinzufügen von Tags zu Ihren Lesevorgängen, Abfragen und Transaktionen die folgenden Einschränkungen:

  • Die Länge eines Tag-Strings ist auf 50 Zeichen begrenzt. Strings, die dieses Limit überschreiten, werden abgeschnitten.
  • In einem Tag sind nur ASCII-Zeichen (32–126) zulässig. Beliebige Unicode-Zeichen werden durch Unterstriche ersetzt.
  • Alle vorangestellten Unterstriche (_) werden aus dem String entfernt.
  • Bei Tags wird zwischen Groß- und Kleinschreibung unterschieden. Wenn Sie beispielsweise einem Satz von Abfragen das Anfrage-Tag APP=cart,ENV=dev und einem anderen Satz von Abfragen app=cart,env=dev hinzufügen, fasst Spanner Statistiken für jedes Tag separat zusammen.
  • In den Statistiktabellen können Tags unter folgenden Umständen fehlen:

    • Wenn Spanner keine Statistiken für alle getaggten Vorgänge, die während des Intervalls ausgeführt werden, in Tabellen speichern kann, priorisiert das System Vorgänge, die im angegebenen Intervall die höchsten Ressourcen verbrauchen.

Tag-Benennung

Wenn Sie Ihren Datenbankvorgängen Tags zuweisen, müssen Sie berücksichtigen, welche Informationen in jedem Tag-String übertragen werden sollen. Die von Ihnen gewählte Konvention bzw. das gewählte Muster macht Ihre Tags effektiver. Beispielsweise erleichtert die richtige Tag-Benennung die Korrelation von Statistiken mit dem Anwendungscode.

Sie können ein beliebiges Tag innerhalb der angegebenen Einschränkungen auswählen. Wir empfehlen jedoch, einen Tag-String als eine Reihe von durch Kommas getrennten Schlüssel/Wert-Paaren zu erstellen.

Angenommen, Sie verwenden eine Spanner-Datenbank für einen E-Commerce-Anwendungsfall. Vielleicht möchten Sie Informationen über die Anwendung, die Entwicklungsumgebung und die durch die Abfrage ausgeführte Aktion in das Anfrage-Tag aufnehmen, das Sie einer bestimmten Abfrage zuweisen. Sie können den Tag-String im Schlüssel/Wert-Format als app=cart,env=dev,action=update zuweisen.Das bedeutet, dass die Abfrage von der Einkaufswagenanwendung in der Entwicklungsumgebung aufgerufen und zum Aktualisieren des Einkaufswagens verwendet wird.

Angenommen, Sie haben eine weitere Abfrage aus einer Katalog-Suchanwendung und weisen den Tag-String als app=catalogsearch,env=dev,action=list zu. Wenn eine dieser Abfragen in der Abfragestatistiktabelle als Abfragen mit hoher Latenz angezeigt wird, können Sie die Quelle einfach durch das Tag identifizieren.

Im Folgenden finden Sie einige Beispiele dafür, wie ein Tagging-Muster zum Organisieren Ihrer Vorgangsstatistiken verwendet werden kann. Die Beispiele erheben keinen Anspruch auf Vollständigkeit. Sie können sie im Tag-String auch mit einem Trennzeichen wie einem Komma kombinieren.

Tag-Schlüssel Beispiele für Tag-Wert-Paare Beschreibung
Anwendung app=cart
app=frontend
app=catalogsearch
Hilft beim Identifizieren der Anwendung, die den Vorgang aufruft.
Umgebung env=prod
env=dev
env=test
env=staging
Hilft beim Identifizieren der Umgebung, die mit dem Vorgang verknüpft ist.
Framework framework=spring
framework=django
framework=jetty
Hilft beim Identifizieren des Frameworks, das mit dem Vorgang verknüpft ist.
Aktion action=list
action=retrieve
action=update
Hilft beim Identifizieren der vom Vorgang ausgeführten Aktion.
Dienst service=payment
service=shipping
Hilft beim Identifizieren des Mikrodiensts, der den Vorgang aufruft.

Weitere Hinweise

  • Wenn Sie eine REQUEST_TAG zuweisen, werden Statistiken für mehrere Abfragen mit demselben Tag-String in einer einzelnen Zeile in der Tabelle Abfragestatistik gruppiert. Im Feld TEXT wird nur der Text einer dieser Abfragen angezeigt.
  • Wenn Sie ein REQUEST_TAG zuweisen, werden Statistiken für mehrere Lesevorgänge mit demselben Tag-String in einer einzigen Zeile in der Lesestatistiktabelle gruppiert. Die gelesenen Spalten werden dem Feld READ_COLUMNS hinzugefügt.
  • Wenn Sie ein TRANSACTION_TAG zuweisen, werden Statistiken für Transaktionen mit demselben Tag-String in einer einzigen Zeile in der Tabelle mit den Transaktionsstatistiken gruppiert. Alle Spalten, die von den Transaktionen geschrieben werden, werden dem Feld WRITE_CONSTRUCTIVE_COLUMNS und die gelesenen Spalten dem Feld READ_COLUMNS hinzugefügt.

Fehlerbehebungsszenarien mit Tags

Quelle einer problematischen Transaktion ermitteln

Die folgende Abfrage gibt die Rohdaten für die Top-Transaktionen im ausgewählten Zeitraum zurück.

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;

Die folgende Tabelle enthält Beispieldaten, die von unserer Abfrage zurückgegeben wurden, wobei wir drei Anwendungen haben, nämlich Warenkorb, Produkt und Frontend, die dieselbe Datenbank besitzen oder abfragen.

Wenn Sie die Transaktionen mit hoher Latenz ermittelt haben, können Sie die zugehörigen Tags verwenden, um den relevanten Teil Ihres Anwendungscodes zu identifizieren und Fehler mithilfe von Transaktionsstatistiken weiter zu beheben.

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 [Leerer String] 0,031 0,015 14 0

Ebenso kann das Anfrage-Tag verwendet werden, um die Quelle einer problematischen Abfrage aus der Abfragestatistiktabelle und die Quelle eines problematischen Lesevorgangs aus der Lesestatistiktabelle zu ermitteln.

Latenz und andere Statistiken für Transaktionen aus einer bestimmten Anwendung oder einem bestimmten Mikrodienst ermitteln

Wenn Sie den Namen der Anwendung oder des Mikrodienstes im Tag-String verwendet haben, erleichtert das die Filterung der Tabelle mit den Transaktionsstatistiken nach Tags, die diesen Anwendungsnamen oder Mikrodienstnamen enthalten.

Angenommen, Sie haben der Zahlungsanwendung neue Transaktionen hinzugefügt und möchten sich Latenzen und andere Statistiken dieser neuen Transaktionen ansehen. Wenn Sie den Namen der Zahlungsanwendung im Tag verwendet haben, können Sie die Transaktionsstatistiktabelle nur nach den Tags filtern, die app=payment enthalten.

Die folgende Abfrage gibt die Transaktionsstatistiken für die Zahlungsanwendung über 10-Minuten-Intervalle zurück.

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;

Hier einige Ausgabebeispiele:

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

Ebenso können Sie Abfragen oder Lesevorgänge aus einer bestimmten Anwendung in der Abfragestatistiktabelle oder in der Lesestatistiktabelle mithilfe von Anfrage-Tags ermitteln.

Transaktionen im Zusammenhang mit Sperrenkonflikten ermitteln

Um festzustellen, für welche Transaktionen und Zeilenschlüssel die hohen Wartezeiten für Sperren aufgetreten sind, fragen wir die Tabelle LOCK_STAT_TOP_10MINUTE ab, in der die Zeilenschlüssel, Spalten und entsprechenden Transaktionen aufgeführt sind, die am Sperrkonflikt beteiligt sind.

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;

Hier einige Ausgabebeispiele aus unserer Abfrage:

row_range_start_key total_lock_wait_seconds lock_wait_seconds frac_of_total sample_lock_requests
Sänger(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

In dieser Ergebnistabelle sehen Sie, dass der Konflikt in der Tabelle Singers unter dem Schlüssel SingerId=32 aufgetreten ist. Singers.SingerInfo ist die Spalte, in der der Sperrkonflikt zwischen ReaderShared und WriterShared aufgetreten ist. Sie können auch die entsprechenden Transaktionen (app=cart,service=order und app=cart,service=redis) identifizieren, bei denen der Konflikt auftritt.

Sobald die Transaktionen identifiziert wurden, die die Sperrkonflikte verursacht haben, können Sie sich mithilfe von Transaktionsstatistiken auf diese Transaktionen konzentrieren, um besser zu verstehen, was die Transaktionen tun und ob Sie Konflikte vermeiden können oder die Zeit verkürzen können, für die die Sperren aufrechterhalten werden. Weitere Informationen finden Sie unter Best Practices zur Reduzierung von Sperrkonflikten.

Nächste Schritte