Resolver problemas com tags de solicitação e de transação

O Spanner oferece um conjunto de tabelas de estatísticas integradas para ajudar você a ter insights consultas, leituras e transações. Para correlacionar estatísticas com o código do aplicativo e melhorar a solução de problemas, é possível adicionar uma tag (uma string de formato livre) às operações de leitura, consulta e transação do Spanner no código do aplicativo. Essas tags são preenchidas em tabelas de estatísticas que ajudam você a correlacionar e pesquisar com base em tags.

O Spanner oferece suporte a dois tipos de tags: request e transaction. Como os nomes sugerem, é possível adicionar tags de transação a transações e solicitar tags para consultas individuais e leituras de APIs. É possível definir uma tag de transação no escopo da transação e definir tags de solicitação individuais para cada solicitação de API aplicável na transação. As tags de solicitação e de transação definidas no código do aplicativo são preenchidas nas colunas das tabelas de estatísticas a seguir.

Tabela de estatísticas Tipo de tags preenchidas na tabela de estatísticas
Estatísticas de consulta do TopN Tags de solicitação
Estatísticas de leitura do TopN Tags de solicitação
Estatísticas de transações do TopN Tags de transação
Estatísticas de bloqueio do TopN Tags de transação

Tags de solicitação

É possível adicionar uma tag de solicitação opcional a uma consulta ou uma solicitação de leitura. Spanner. agrupa estatísticas por tag de solicitação, que é visível no campo REQUEST_TAG do os dois estatísticas de consulta e estatísticas de leitura tabelas.

Quando usar tags de solicitação

Veja abaixo alguns dos cenários que se beneficiam do uso de tags de solicitação.

  • Como encontrar a origem de uma consulta ou leitura problemática: Spanner coleta estatísticas de leituras e consultas em tabelas de estatísticas integradas. Quando você encontrar consultas lentas ou leituras de alto consumo de CPU na tabela de estatísticas, se já tiver atribuído tags a elas, poderá identificar a origem (aplicativo/microsserviço) que está chamando essas operações com base nas informações da tag.
  • Identificação de leituras ou consultas em tabelas de estatísticas: a atribuição de tags de solicitação ajuda a filtrar linhas na tabela de estatísticas com base nas tags do seu interesse.
  • Como descobrir se as consultas de um aplicativo ou microsserviço específico são lentas: as tags de solicitação ajudam a identificar se as consultas de um determinado aplicativo ou microsserviço têm latências mais altas.
  • Agrupar as estatísticas de um conjunto de leituras ou consultas: use tags de solicitação para rastrear, comparar e gerar relatórios de desempenho em um conjunto de leituras ou consultas semelhantes. Por exemplo, se várias consultas estiverem acessando uma tabela/conjunto de tabelas com o mesmo padrão de acesso, considere adicionar a mesma tag a todas essas consultas para acompanhá-las.

Como atribuir tags de solicitação

O exemplo a seguir mostra como definir tags de solicitação usando as bibliotecas de cliente do 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

Como visualizar tags de solicitação na tabela de estatísticas

A consulta a seguir retorna as estatísticas de consulta em intervalos de 10 minutos.

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;

Vamos analisar os dados a seguir como um exemplo do resultado que recebemos da nossa consulta.

texto request_tag execution_count average_Latency_seconds avg_rows avg_bytes
SELECIONE SingerId, AlbumId, AlbumTitle DE álbuns app=concert,env=dev,action=select 212 0.025 21 2365
selecione * de pedidos, app=catalogsearch,env=dev,action=list 55 0.02 16 33.35
SELECIONE SingerId, FirstName, LastName DE cantores, [string vazia] 154 0.048 42 486.33

Nessa tabela de resultados, podemos ver que, se você atribuiu um REQUEST_TAG a uma consulta, ele será preenchido na tabela de estatísticas. Se não houver uma tag de solicitação atribuída, ela será exibida como uma string vazia.

Para as consultas marcadas, as estatísticas são agregadas por tag (por exemplo, a tag de solicitação app=concert,env=dev,action=select tem uma latência média de 0,025 segundo). Se não houver uma tag atribuída, as estatísticas serão agregadas por consulta. Por exemplo, a consulta na terceira linha tem uma latência média de 0,048 segundo.

Tags de transação

Uma tag de transação opcional pode ser adicionada a transações individuais. O Spanner agrupa as estatísticas por tag de transação, que é visível no campo TRANSACTION_TAG das tabelas de estatísticas de transações.

Quando usar tags de transação

Veja a seguir alguns dos cenários que se beneficiam do uso de tags de transação.

  • Como encontrar a origem de uma transação com problemas: o Spanner coleta estatísticas para transações de leitura e gravação na tabela de estatísticas da transação. Quando encontrar transações lentas na tabela de estatísticas da transação, se você já tiver atribuído tags a elas, poderá identificar a origem (aplicativo/microsserviço) que está chamando essas transações com base nas informações na tag.
  • Identificação de transações em tabelas de estatísticas: a atribuição de tags de transação ajuda a filtrar linhas na tabela de estatísticas de transação com base nas tags de seu interesse. Sem as tags de transação, descobrir quais operações são representadas por uma estatística pode ser um processo complicado. Por exemplo, para estatísticas de transação, é necessário examinar as tabelas e colunas envolvidas para identificar a transação não marcada.
  • Como descobrir se as transações de um aplicativo ou microsserviço específico são lentas: as tags de transação podem ajudar a identificar se as transações de um determinado aplicativo ou microsserviço têm latências mais altas.
  • Estatísticas de agrupamento de um conjunto de transações: você pode usar tags de transação para acompanhar, comparar e gerar relatórios de desempenho para um conjunto de transações semelhantes.
  • Encontrar quais transações estão acessando as colunas envolvidas no conflito de bloqueio: as tags de transação podem ajudar a identificar transações individuais que causam conflitos de bloqueio nas tabelas Estatísticas de bloqueio.
  • Fluxo de dados de alterações do usuário do Spanner usando fluxos de alterações: os registros de dados dos fluxos de alterações contêm tags de transação para as transações que modificaram os dados do usuário. Isso permite que o leitor de um fluxo de alterações associar alterações ao tipo de transação com base em tags.

Como atribuir tags de transação

O exemplo a seguir mostra como definir tags de transação usando o Spanner bibliotecas de cliente. Quando você usa uma biblioteca de cliente, é possível definir uma tag de transação no início da chamada de transação que é aplicada a todas as operações individuais dentro dessa transação.

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

Como ver as tags de transação na tabela "Estatísticas de transação"

A consulta a seguir retorna as estatísticas de transação em intervalos de 10 minutos.

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;

Vamos analisar os dados a seguir como um exemplo do resultado que recebemos da nossa consulta.

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 vazia] [Singers.FirstName, Singers.LastName, Singers._exists] 5357 0.048

Nessa tabela de resultados, podemos ver que, se você atribuiu um TRANSACTION_TAG a uma transação, ela será preenchida na tabela de estatísticas da transação. Caso não haja uma tag de transação atribuída, ela será exibida como uma string vazia.

Para as transações marcadas, as estatísticas são agregadas por tag de transação (por exemplo, a tag de transação app=concert,env=dev tem uma latência média de 0,3508 segundos). Se não houver uma tag atribuída, as estatísticas serão agregadas por FPRINT (por exemplo, 77848338483 na terceira linha tem uma latência média de 0,048 segundos).

Como ver tags de transação na tabela "Estatísticas de bloqueio"

A consulta a seguir retorna as estatísticas de bloqueio em intervalos de 10 minutos.

A função CAST() converte o campo row_range_start_key BYTES em uma 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;

Vamos analisar os dados a seguir como um exemplo do resultado que recebemos da nossa consulta.

row_range_start_key lock_wait_seconds sample_lock_requests
Músicas(2,1,1) 0,61 LOCK_MODE: ReaderShared
COLUNA: Singers.SingerInfo
TRANSACTION_TAG: app=product,service=shipping

LOCK_MODE: WriterShared
COLUNA: Singers.SingerInfo
TRANSACTION_TAG: app=product,service=payment
álbuns(2,1+) 0,48 LOCK_MODE: ReaderShared
COLUNA: users._exists1
TRANSACTION_TAG: [string vazia]

LOCK_MODE: WriterShared
COLUNA: users._exists
TRANSACTION_TAG: [string vazia]

Nessa tabela de resultados, podemos ver que, se você atribuiu um TRANSACTION_TAG a uma transação, ela será preenchida na tabela de estatísticas de bloqueio. Caso não haja uma tag de transação atribuída, ela é exibida como uma string vazia.

Como mapear entre métodos de API e tags de solicitação/transação

As tags de solicitação e de transação são aplicáveis a métodos de API específicos com base no tipo de transação: somente leitura ou de leitura e gravação. Geralmente, as tags de transação são aplicáveis a transações de leitura e gravação, enquanto as tags de solicitação são aplicáveis a transações somente leitura. Veja na tabela a seguir o mapeamento dos métodos de API para os tipos de tags aplicáveis.

Métodos de API Modos de transação Tag de solicitação Tag da transação
Ler,
StreamingRead
Transação somente leitura. Sim Não
Transação de leitura/gravação Sim Sim
ExecuteSql,
ExecuteStreamingSql1
Transação somente de leitura1 Sim1 Não
Transação de leitura/gravação Sim Sim
ExecuteBatchDml Transação de leitura/gravação Sim Sim
BeginTransaction Transação de leitura/gravação Não Sim
Confirmação Transação de leitura/gravação Não Sim

1 Para consultas de fluxo de alterações executadas usando o conector do Dataflow do Apache Beam SpannerIO, o REQUEST_TAG contém um nome de job do Dataflow.

Limitações

Ao adicionar tags às leituras, consultas e transações, considere as seguintes limitações:

  • O comprimento de uma string de tag é limitado a 50 caracteres. As strings que excedem esse limite são truncadas.
  • Somente caracteres ASCII (32-126) são permitidos em uma tag. Caracteres Unicode arbitrários são substituídos por sublinhados.
  • Todos os caracteres sublinhado (_) iniciais são removidos da string.
  • As tags diferenciam maiúsculas de minúsculas. Por exemplo, se você adicionar a tag de solicitação APP=cart,ENV=dev a um conjunto de consultas e adicionar app=cart,env=dev a outro conjunto de consultas, o Spanner vai agregar estatísticas separadamente para cada tag.
  • As tags podem estar ausentes nas tabelas de estatísticas nas seguintes circunstâncias:

    • Se o Spanner não puder armazenar estatísticas para todas as operações com tags executadas durante o intervalo nas tabelas, o sistema vai priorizar as operações com os recursos com maior consumo durante o intervalo especificado.

Nomeação de tags

Ao atribuir tags às operações do seu banco de dados, é importante considerar quais informações você quer transmitir em cada string da tag. A convenção ou o padrão que você escolher torna suas tags mais eficazes. Por exemplo, um nome de tag adequado facilita a correlação de estatísticas com o código do aplicativo.

Você pode escolher qualquer tag conforme as limitações declaradas. No entanto, recomendamos construir uma string de tag como um conjunto de pares de chave-valor separados por vírgulas.

Por exemplo, suponha que você esteja usando um banco de dados do Spanner para um caso de uso de e-commerce. É possível incluir informações sobre o aplicativo, o ambiente de desenvolvimento e a ação realizada pela consulta na tag de solicitação que você atribuirá a uma consulta específica. Considere atribuir a string da tag no formato de chave-valor como app=cart,env=dev,action=update. Isso significa que a consulta é chamada do aplicativo de carrinho no ambiente de desenvolvimento e é usada para atualizar o carrinho.

Suponha que você tenha outra consulta de um aplicativo de pesquisa de catálogo e atribua a string da tag como app=catalogsearch,env=dev,action=list. Agora, se alguma dessas consultas aparecer na tabela de estatísticas de consulta como consultas de alta latência, você poderá identificar facilmente a origem usando a tag.

Veja alguns exemplos de como um padrão de inclusão de tag pode ser usado para organizar suas estatísticas de operações. Estes exemplos não são completos. Também é possível combiná-los na string da tag usando um delimitador, como uma vírgula.

Chaves de tag Exemplos de par de tag e valor Descrição
Aplicativo app=cart
app=frontend
app=catalogsearch
Ajuda a identificar o aplicativo que está chamando a operação.
Ambiente env=prod
env=dev
env=test
env=staging
Ajuda a identificar o ambiente associado à operação.
Framework framework=spring
framework=django
framework=jetty
Ajuda a identificar o framework associado à operação.
Ação action=list
action=retrieve
action=update
Ajuda a identificar a ação realizada pela operação.
Serviço service=payment
service=shipping
Ajuda a identificar o microsserviço que está chamando a operação.

Observe o seguinte

  • Quando você atribui um REQUEST_TAG, as estatísticas de várias consultas que têm a mesma string de tag são agrupadas em uma única linha na tabela estatísticas de consulta. Somente o texto de uma dessas consultas é mostrado no campo TEXT.
  • Quando você atribui um REQUEST_TAG, as estatísticas de várias leituras com a mesma string de tags são agrupadas em uma única linha na tabela estatísticas de leitura. O conjunto de todas as colunas lidas é adicionado ao campo READ_COLUMNS.
  • Quando você atribui um TRANSACTION_TAG, as estatísticas das transações que têm a mesma string de tags são agrupadas em uma única linha na tabela estatísticas de transações. O conjunto de todas as colunas gravadas pelas transações é adicionado ao campo WRITE_CONSTRUCTIVE_COLUMNS, e o conjunto de todas as colunas lidas é adicionado ao campo READ_COLUMNS.

Como solucionar problemas usando tags

Como encontrar a origem de uma transação com problemas

A consulta a seguir retorna os dados brutos das principais transações no período selecionado.

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;

A tabela a seguir lista os dados de exemplo retornados da nossa consulta, em que temos três aplicativos, cart, product e frontend, que são proprietários. ou consultam o mesmo banco de dados.

Depois de identificar as transações com alta latência, use as tags associadas para identificar a parte relevante do código do aplicativo e resolva problemas adicionais usando as estatísticas de transações.

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

Da mesma forma, a tag de solicitação pode ser usada para encontrar a origem de uma consulta problemática na tabela de estatísticas de consulta e a fonte de leitura problemática da tabela estatísticas de leitura.

Como encontrar a latência e outras estatísticas para transações de um aplicativo ou microsserviço específico

Se você usou o nome do aplicativo ou do microsserviço na string da tag, ele ajuda a filtrar a tabela de estatísticas de transação por tags que contenham o nome desse aplicativo ou o nome do microsserviço.

Suponha que você adicionou novas transações ao aplicativo payment e quer ver as latências e outras estatísticas dessas novas transações. Se você usou o nome do aplicativo de pagamento na tag, filtre a tabela de estatísticas de transação apenas para as tags que contêm app=payment.

A consulta a seguir retorna as estatísticas de transação do app de pagamento em intervalos de 10 minutos.

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;

Veja um exemplo de saída:

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

Da mesma maneira, é possível encontrar consultas ou leituras de um aplicativo específico na tabela de estatísticas de consulta ou estatísticas de leitura, usando tags de solicitação.

Como descobrir transações envolvidas em conflito de bloqueios

Para descobrir quais transações e chaves de linha tiveram os tempos de espera de bloqueio alto, consultamos a tabela LOCK_STAT_TOP_10MINUTE, que lista as chaves de linha, as colunas e as transações correspondentes envolvidas no conflito de bloqueio.

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;

Veja um exemplo de saída da nossa consulta:

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

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

Nesta tabela de resultados, podemos ver que o conflito aconteceu na tabela Singers da chave SingerId=32. O Singers.SingerInfo é a coluna em que ocorreu o conflito de bloqueio entre ReaderShared e WriterShared. Também é possível identificar transações correspondentes (app=cart,service=order e app=cart,service=redis) que estão enfrentando o conflito.

Depois que as transações que causam os conflitos de bloqueio forem identificadas, será possível se concentrar nessas transações usando as estatísticas de transações para entender melhor o que as transações estão fazendo e se é possível evitar um conflito ou reduzir o tempo em que os bloqueios são mantidos. Para mais informações, consulte Práticas recomendadas para reduzir a contenção de bloqueio.

A seguir