Risolvere i problemi relativi a tag di richiesta e tag di transazione

Spanner fornisce un set di tabelle di statistiche integrate per aiutarti a ottenere insight su query, letture e transazioni. Per correlare le statistiche al codice dell'applicazione e migliorare la risoluzione dei problemi, puoi aggiungere un tag (una stringa in formato libero) alle operazioni di lettura, query e transazioni di Spanner nel codice dell'applicazione. Questi tag vengono compilati nelle tabelle delle statistiche per aiutarti a correlare e cercare in base ai tag.

Spanner supporta due tipi di tag: tag di richiesta e tag di transazione. Come suggerisce il nome, puoi aggiungere tag di transazione alle transazioni e richiedere tag a singole query e leggere le API. Puoi impostare un tag transazione nell'ambito della transazione e impostare singoli tag di richiesta per ogni richiesta API applicabile all'interno della transazione. I tag di richiesta e di transazione impostati nel codice dell'applicazione vengono compilati nelle colonne delle seguenti tabelle delle statistiche.

Tabella delle statistiche Tipo di tag compilati nella tabella delle statistiche
Statistiche query TopN Tag di richiesta
Statistiche di lettura TopN Tag di richiesta
Statistiche sulle transazioni di TopN Tag transazioni
Statistiche blocco TopN Tag transazioni

Tag di richiesta

Puoi aggiungere un tag di richiesta facoltativo a una query o a una richiesta di lettura. Spanner raggruppa le statistiche per tag di richiesta, visibile nel campo REQUEST_TAG delle tabelle delle statistiche delle query e delle statistiche di lettura.

Quando utilizzare i tag di richiesta

Di seguito sono riportati alcuni degli scenari che traggono vantaggio dall'utilizzo dei tag di richiesta.

  • Individuazione dell'origine di una query o lettura problematica: Spanner raccoglie statistiche per le letture e le query nelle tabelle delle statistiche integrate. Se nella tabella delle statistiche trovi query lente o letture che consumano un'alta CPU, se hai già assegnato tag a queste ultime, puoi identificare l'origine (applicazione/microservizio) che chiama queste operazioni in base alle informazioni contenute nel tag.
  • Identificazione di letture o query nelle tabelle delle statistiche: l'assegnazione di tag di richiesta consente di filtrare le righe nella tabella delle statistiche in base ai tag che ti interessano.
  • Individuazione se le query di una determinata applicazione o di un determinato microservizio sono lente: i tag di richiesta consentono di identificare se le query di una determinata applicazione o di un determinato microservizio hanno latenze più elevate.
  • Raggruppamento di statistiche per un insieme di letture o query: puoi utilizzare i tag di richiesta per monitorare, confrontare e generare report sulle prestazioni in un insieme di letture o query simili. Ad esempio, se più query accedono a una tabella/un insieme di tabelle con lo stesso modello di accesso, puoi valutare l'aggiunta dello stesso tag a tutte quelle query per monitorarle insieme.

Come assegnare tag di richiesta

Il seguente esempio mostra come impostare i tag di richiesta utilizzando le librerie client 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

Come visualizzare i tag di richiesta nella tabella delle statistiche

La seguente query restituisce le statistiche della query a intervalli di 10 minuti.

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;

Prendiamo i seguenti dati come esempio dei risultati che otteniamo dalla nostra query.

sms request_tag execution_count avg_latency_seconds avg_rows avg_bytes
SELECT ID cantante, ID album, titolo album dai album app=concert,env=dev,action=select 212 0,025 21 2365
select * dagli ordini; app=catalogsearch,env=dev,action=list 55 0,02 16 33,35
SELECT SingerId, FirstName, LastName FROM Singers; [stringa vuota] 154 0,048 42 486,33

Da questa tabella dei risultati puoi vedere che se hai assegnato un valore REQUEST_TAG a una query, questa viene compilata nella tabella delle statistiche. Se non viene assegnato alcun tag di richiesta, viene visualizzata una stringa vuota.

Per le query codificate, le statistiche vengono aggregate per tag (ad es. il tag di richiesta app=concert,env=dev,action=select ha una latenza media di 0,025 secondi). Se non viene assegnato alcun tag, le statistiche vengono aggregate per query (ad es. la query nella terza riga ha una latenza media di 0,048 secondi).

Tag transazioni

È possibile aggiungere un tag transazione facoltativo alle singole transazioni. Spanner raggruppa le statistiche per tag transazione, visibile nel campo TRANSACTION_TAG delle tabelle delle statistiche delle transazioni.

Quando utilizzare i tag transazioni

Di seguito sono riportati alcuni degli scenari che traggono vantaggio dall'utilizzo dei tag di transazione.

  • Individuazione dell'origine di una transazione problematica: Spanner raccoglie le statistiche per le transazioni di lettura/scrittura nella tabella delle statistiche delle transazioni. Quando trovi transazioni lente nella tabella delle statistiche delle transazioni, se hai già assegnato tag, puoi identificare l'origine (applicazione/microservizio) che chiama queste transazioni in base alle informazioni nel tag.
  • Identificazione delle transazioni nelle tabelle delle statistiche: l'assegnazione di tag di transazione consente di filtrare le righe nella tabella delle statistiche delle transazioni in base ai tag di tuo interesse. Senza i tag transazione, scoprire quali operazioni sono rappresentate da una statistica può essere un processo complesso. Ad esempio, per le statistiche sulle transazioni, devi esaminare le tabelle e le colonne interessate per identificare la transazione senza tag.
  • Individuazione dell'eventuale lentezza delle transazioni da una determinata applicazione o a un determinato microservizio: i tag Transaction consentono di identificare se le transazioni di un'applicazione o di un microservizio specifici hanno latenze più elevate.
  • Raggruppamento di statistiche per un insieme di transazioni: puoi utilizzare i tag transazione per monitorare, confrontare e generare report sul rendimento di un insieme di transazioni simili.
  • Individuazione delle transazioni che accedono alle colonne coinvolte nel conflitto di blocco: i tag transazione consentono di individuare le singole transazioni che causano conflitti di blocco nelle tabelle Statistiche blocco.
  • Streaming dei dati delle modifiche utente da Spanner utilizzando stream di modifiche: i record di dati dei flussi di modifiche contengono tag di transazione per le transazioni che hanno modificato i dati utente. In questo modo, il lettore di una modifica in tempo reale può associare le modifiche al tipo di transazione in base ai tag.

Come assegnare i tag transazioni

Il seguente esempio mostra come impostare i tag di transazione utilizzando le librerie client Spanner. Quando utilizzi una libreria client, puoi impostare un tag transazione all'inizio della chiamata della transazione che viene applicato a tutte le singole operazioni all'interno della transazione.

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

Come visualizzare i tag transazioni nella tabella delle statistiche delle transazioni

La seguente query restituisce le statistiche delle transazioni a intervalli di 10 minuti.

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;

Prendiamo i seguenti dati come esempio dei risultati che otteniamo dalla nostra query.

Fprint transaction_tag read_columns commit_attempt_count avg_total_latency_seconds
40015598317 app=concert,env=dev [Sedi._exists,
Sede.VenueId,
Sedi.NomeVenue,
Sedi.Capacità]
278802 0,3508
20524969030 app=product,service=payment [InfoSingers.Singer] 129012 0,0142
77848338483 [stringa vuota] [Singers.FirstName, Singers.LastName, Singers._exists] 5357 0,048

Da questa tabella dei risultati, possiamo vedere che se hai assegnato un valore TRANSACTION_TAG a una transazione, questa viene compilata nella tabella delle statistiche delle transazioni. Se non viene assegnato alcun tag transazioni, viene visualizzato come una stringa vuota.

Per le transazioni con tag, le statistiche vengono aggregate in base al tag di transazione (ad esempio, il tag di transazione app=concert,env=dev a ha una latenza media di 0,3508 secondi). Se non viene assegnato alcun tag, le statistiche vengono aggregate per FPRINT (ad es. 77848338483 nella terza riga ha una latenza media di 0,048 secondi).

Come visualizzare i tag transazioni nella tabella delle statistiche di blocco

La seguente query restituisce le statistiche di blocco a intervalli di 10 minuti.

La funzione CAST() converte il campo row_range_start_key BYTES in 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;

Prendiamo i seguenti dati come esempio dei risultati che otteniamo dalla nostra query.

row_range_start_key lock_wait_seconds sample_lock_requests
Brani(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
album(2,1+) 0,48 LOCK_MODE: ReaderShared
COLUMN: users._exists1
TRANSACTION_TAG: [stringa vuota]

LOCK_MODE: WriterShared
COLUMN: users._exists
TRANSACTION_TAG: [stringa vuota]

Da questa tabella dei risultati, possiamo vedere che se hai assegnato un valore TRANSACTION_TAG a una transazione, questa viene compilata nella tabella delle statistiche di blocco. Se non viene assegnato alcun tag transazione, viene visualizzata una stringa vuota.

Mappatura tra metodi API e tag di richiesta/transazione

I tag di richiesta e i tag di transazione sono applicabili a metodi API specifici a seconda che la modalità di transazione sia una transazione di sola lettura o una transazione di lettura-scrittura. In generale, i tag transazione si applicano alle transazioni di lettura-scrittura, mentre i tag di richiesta si applicano alle transazioni di sola lettura. La seguente tabella mostra la mappatura dai metodi API ai tipi di tag applicabili.

Metodi API Modalità di transazione Richiedi tag Tag transazioni
Lettura,
StreamingRead
Transazione di sola lettura No
Transazione di lettura/scrittura
ExecuteSql,
ExecuteStreamingSql1
Transazione di sola lettura1 1 No
Transazione di lettura/scrittura
ExecuteBatchDml Transazione di lettura/scrittura
BeginTransaction Transazione di lettura/scrittura No
Esegui il commit Transazione di lettura/scrittura No

1 Per le query di modifiche in tempo reale eseguite utilizzando il connettore Dataflow SpannerIO di Apache Beam, REQUEST_TAG contiene il nome di un job Dataflow.

Limitazioni

Quando aggiungi tag a letture, query e transazioni, considera le seguenti limitazioni:

  • La lunghezza di una stringa tag è limitata a 50 caratteri. Le stringhe che superano questo limite vengono troncate.
  • In un tag sono ammessi solo caratteri ASCII (32-126). I caratteri Unicode arbitrari sono sostituiti da trattini bassi.
  • Il trattino basso iniziale (_) viene rimosso dalla stringa.
  • I tag sono sensibili alle maiuscole. Ad esempio, se aggiungi il tag di richiesta APP=cart,ENV=dev a un set di query e aggiungi app=cart,env=dev a un altro insieme di query, Spanner aggrega le statistiche separatamente per ogni tag.
  • Potrebbero mancare tag nelle tabelle delle statistiche nei seguenti casi:

    • Se Spanner non è in grado di archiviare le statistiche per tutte le operazioni con tag eseguite durante l'intervallo nelle tabelle, il sistema dà la priorità alle operazioni con il consumo più elevato di risorse durante l'intervallo specificato.

Denominazione dei tag

Quando assegni tag alle operazioni del tuo database, è importante considerare quali informazioni vuoi trasmettere in ogni stringa di tag. La convenzione o il pattern che scegli rende i tag più efficaci. Ad esempio, una denominazione corretta dei tag semplifica la correlazione tra statistiche e codice dell'applicazione.

Puoi scegliere qualsiasi tag entro le limitazioni indicate. Tuttavia, ti consigliamo di creare una stringa tag come un insieme di coppie chiave-valore separate da virgole.

Ad esempio, supponiamo di utilizzare un database Spanner per un caso d'uso di e-commerce. Potresti voler includere informazioni sull'applicazione, sull'ambiente di sviluppo e sull'azione intrapresa dalla query nel tag di richiesta da assegnare a una query specifica. Puoi valutare l'assegnazione della stringa tag nel formato chiave-valore come app=cart,env=dev,action=update.Ciò significa che la query viene richiamata dall'applicazione carrello nell'ambiente di sviluppo e utilizzata per aggiornare il carrello.

Supponi di avere un'altra query di un'applicazione di ricerca di cataloghi e di assegnare la stringa tag come app=catalogsearch,env=dev,action=list. Ora se una di queste query compare nella tabella delle statistiche delle query come query ad alta latenza, puoi identificare facilmente l'origine utilizzando il tag.

Ecco alcuni esempi di come un pattern di tagging può essere utilizzato per organizzare le statistiche operative. Questi esempi non sono esaustivi; puoi anche combinarli nella stringa del tag utilizzando un delimitatore, ad esempio una virgola.

Chiavi tag Esempi di coppia tag-valore Description
Applicazione app=cart
app=frontend
app=catalogsearch
Consente di identificare l'applicazione che chiama l'operazione.
Ambiente env=prod
env=dev
env=test
env=staging
Consente di identificare l'ambiente associato all'operazione.
Framework framework=molla
framework=django
framework=molo
Consente di identificare il framework associato all'operazione.
Azione action=list
action=retrieve
action=update
Consente di identificare l'azione intrapresa dall'operazione.
Servizio servizio=pagamento
servizio=spedizione
Consente di identificare il microservizio che chiama l'operazione.

Da tenere presente

  • Quando assegni un REQUEST_TAG, le statistiche per più query che hanno la stessa stringa tag vengono raggruppate in una singola riga nella tabella delle statistiche delle query. Nel campo TEXT viene visualizzato solo il testo di una di queste query.
  • Quando assegni un valore REQUEST_TAG, le statistiche per più letture che hanno la stessa stringa tag vengono raggruppate in una singola riga nella tabella delle statistiche di lettura. L'insieme di tutte le colonne lette viene aggiunto al campo READ_COLUMNS.
  • Quando assegni un TRANSACTION_TAG, le statistiche per le transazioni che hanno la stessa stringa tag vengono raggruppate in una singola riga nella tabella delle statistiche sulle transazioni. L'insieme di tutte le colonne scritte dalle transazioni viene aggiunto al campo WRITE_CONSTRUCTIVE_COLUMNS, mentre l'insieme di tutte le colonne lette viene aggiunto al campo READ_COLUMNS.

Scenari di risoluzione dei problemi relativi all'utilizzo dei tag

Individuazione dell'origine di una transazione problematica

La seguente query restituisce i dati non elaborati per le transazioni principali nel periodo di tempo selezionato.

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;

La seguente tabella elenca dati di esempio restituiti dalla nostra query, in cui abbiamo tre applicazioni, cart, product e frontend, che possiedono o eseguono query sullo stesso database.

Una volta identificate le transazioni soggette a un'alta latenza, puoi utilizzare i tag associati per identificare la parte pertinente del codice dell'applicazione e risolvere ulteriormente i problemi utilizzando le statistiche sulle transazioni.

Fprint transaction_tag avg_total_latency_sec avg_commit_latency_sec commit_attempt_count commit_abort_count
7129109266372596045 app=carrello,servizio=ordine 0,3508 0,0139 278802 142205
9353100217060788102 app=cart,servizio=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,servizio=annunci 0,0093 0,0045 164 0
11079878968512225881 [stringa vuota] 0,031 0,015 14 0

Analogamente, il tag di richiesta può essere utilizzato per trovare l'origine di una query problematica dalla tabella delle statistiche delle query e l'origine della lettura problematica dalla tabella delle statistiche di lettura.

Trovare la latenza e altre statistiche per le transazioni da una particolare applicazione o microservizio

Se hai utilizzato il nome dell'applicazione o del microservizio nella stringa del tag, è utile filtrare la tabella delle statistiche delle transazioni in base ai tag contenenti quel nome di applicazione o quel nome.

Supponiamo che tu abbia aggiunto nuove transazioni all'app di pagamento e voglia controllare le latenze e altre statistiche relative a queste nuove transazioni. Se hai utilizzato il nome dell'applicazione di pagamento all'interno del tag, puoi filtrare la tabella delle statistiche delle transazioni solo per i tag che contengono app=payment.

La seguente query restituisce le statistiche sulle transazioni per l'app per pagamenti a intervalli di oltre 10 minuti.

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;

Ecco un esempio di output:

transaction_tag avg_total_latency_sec avg_commit_latency_sec commit_attempt_count commit_abort_count
app=pagamento,azione=aggiornamento 0,3508 0,0139 278802 142205
app=pagamento,azione=trasferimento 0,1633 0,0142 129012 27177
app=pagamento, azione=recupero 0,1423 0,0133 5357 636

Allo stesso modo, puoi trovare query o letture da un'applicazione specifica nella tabella delle statistiche sulle query o nella tabella delle statistiche di lettura utilizzando i tag di richiesta.

Individuazione delle transazioni coinvolte nel conflitto di blocco

Per scoprire quali transazioni e chiavi di riga hanno riscontrato gli elevati tempi di attesa per il blocco, eseguiamo una query nella tabella LOCK_STAT_TOP_10MINUTE, che elenca le chiavi di riga, le colonne e le transazioni corrispondenti coinvolte nel conflitto di blocco.

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;

Ecco alcuni esempi di output della nostra query:

row_range_start_key total_lock_wait_seconds lock_wait_seconds frac_of_total sample_lock_requests
Cantanti(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

Da questa tabella dei risultati, puoi vedere che il conflitto si è verificato nella tabella Singers nella chiave SingerId=32. Singers.SingerInfo è la colonna in cui si è verificato il conflitto di blocco tra ReaderShared e WriterShared. Puoi anche identificare le transazioni corrispondenti (app=cart,service=order e app=cart,service=redis) che presentano il conflitto.

Una volta identificate le transazioni che causano i conflitti di blocco, puoi concentrarti su queste transazioni utilizzando le statistiche sulle transazioni per comprendere meglio lo stato delle transazioni e se puoi evitare un conflitto o ridurre il tempo per cui vengono conservati i blocchi. Per saperne di più, consulta le best practice per ridurre i conflitti dei blocchi.

Passaggi successivi