Resolva problemas com etiquetas de pedidos e etiquetas de transações

O Spanner oferece um conjunto de tabelas de estatísticas incorporadas para ajudar a obter estatísticas sobre as suas consultas, leituras e transações. Para correlacionar estatísticas com o código da aplicação e melhorar a resolução de problemas, pode adicionar uma etiqueta (uma string de forma livre) às operações de leitura, consulta e transação do Spanner no código da aplicação. Estas etiquetas são preenchidas em tabelas de estatísticas, o que ajuda a correlacionar e pesquisar com base nas etiquetas.

O Spanner suporta dois tipos de etiquetas: etiquetas de pedido e etiquetas de transação. Como os nomes sugerem, pode adicionar etiquetas de transações a transações e etiquetas de pedidos a consultas individuais e APIs de leitura. Pode definir uma etiqueta de transação ao nível da transação e definir etiquetas de pedidos individuais para cada pedido de API aplicável na transação. As etiquetas de pedidos e as etiquetas de transações definidas no código da aplicação são preenchidas nas colunas das seguintes tabelas de estatísticas.

Tabela de estatísticas Tipo de etiquetas preenchidas na tabela de estatísticas
Estatísticas de consultas TopN Etiquetas de pedidos
Estatísticas de leitura TopN Etiquetas de pedidos
Estatísticas de transações TopN Etiquetas de transação
Estatísticas de bloqueio TopN Etiquetas de transação

Etiquetas de pedidos

Pode adicionar uma etiqueta de pedido opcional a uma consulta ou a um pedido de leitura. O Spanner agrupa as estatísticas por etiqueta de pedido, que é visível no campo REQUEST_TAG das tabelas de estatísticas de consultas e estatísticas de leitura.

Quando usar etiquetas de pedidos

Seguem-se alguns dos cenários que beneficiam da utilização de etiquetas de pedidos.

  • Encontrar a origem de uma consulta ou leitura problemática: o Spanner recolhe estatísticas para leituras e consultas em tabelas de estatísticas incorporadas. Quando encontrar as consultas lentas ou as leituras que consomem muita CPU na tabela de estatísticas, se já tiver atribuído etiquetas a essas consultas, pode identificar a origem (aplicação/microsserviço) que está a chamar estas operações com base nas informações na etiqueta.
  • Identificar leituras ou consultas em tabelas de estatísticas: a atribuição de etiquetas de pedidos ajuda a filtrar linhas na tabela de estatísticas com base nas etiquetas que lhe interessam.
  • Saber se as consultas de uma determinada aplicação ou microsserviço são lentas: as etiquetas de pedidos podem ajudar a identificar se as consultas de uma determinada aplicação ou microsserviço têm latências mais elevadas.
  • Agrupar estatísticas para um conjunto de leituras ou consultas: pode usar etiquetas de pedidos para monitorizar, comparar e comunicar o desempenho num conjunto de leituras ou consultas semelhantes. Por exemplo, se várias consultas estiverem a aceder a uma tabela/conjunto de tabelas com o mesmo padrão de acesso, pode considerar adicionar a mesma etiqueta a todas essas consultas para as acompanhar em conjunto.

Como atribuir etiquetas de pedidos

O exemplo seguinte mostra como definir etiquetas de pedidos através das bibliotecas 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 ver etiquetas de pedidos na tabela de estatísticas

A seguinte consulta devolve as estatísticas de consultas 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 usar os seguintes dados como exemplo dos resultados que recebemos da nossa consulta.

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

A partir desta tabela de resultados, podemos ver que, se tiver atribuído um REQUEST_TAG a uma consulta, este é preenchido na tabela de estatísticas. Se não existir nenhuma etiqueta de pedido atribuída, é apresentada como uma string vazia.

Para as consultas etiquetadas, as estatísticas são agregadas por etiqueta (por exemplo, a etiqueta de pedido app=concert,env=dev,action=select tem uma latência média de 0,025 segundos). Se não for atribuída nenhuma etiqueta, as estatísticas são agregadas por consulta (por exemplo, a consulta na terceira linha tem uma latência média de 0,048 segundos).

Etiquetas de transação

Pode adicionar uma etiqueta de transação opcional a transações individuais. O Spanner agrupa as estatísticas por etiqueta de transação, que é visível no campo TRANSACTION_TAG das tabelas de estatísticas de transações.

Quando usar etiquetas de transações

Seguem-se alguns dos cenários que beneficiam da utilização de etiquetas de transação.

  • Encontrar a origem de uma transação problemática: o Spanner recolhe estatísticas para transações de leitura/escrita na tabela de estatísticas de transações. Quando encontra transações lentas na tabela de estatísticas de transações, se já lhes tiver atribuído etiquetas, pode identificar a origem (aplicação/microsserviço) que está a chamar estas transações com base nas informações na etiqueta.
  • Identificar transações em tabelas de estatísticas: a atribuição de etiquetas de transação ajuda a filtrar linhas na tabela de estatísticas de transações com base nas etiquetas que lhe interessam. Sem etiquetas de transações, descobrir que operações são representadas por uma estatística pode ser um processo complicado. Por exemplo, para as estatísticas de transações, teria de examinar as tabelas e as colunas envolvidas para identificar a transação não etiquetada.
  • Saber se as transações de uma determinada aplicação ou microsserviço são lentas: as etiquetas de transações podem ajudar a identificar se as transações de uma determinada aplicação ou microsserviço têm latências mais elevadas.
  • Agrupar estatísticas para um conjunto de transações: pode usar etiquetas de transações para monitorizar, comparar e comunicar o desempenho de um conjunto de transações semelhantes.
  • Determinar que transações estão a aceder às colunas envolvidas no conflito de bloqueio: as etiquetas de transações podem ajudar a identificar transações individuais que causam conflitos de bloqueio nas tabelas de estatísticas de bloqueio.
  • Transmitir dados de alterações de utilizadores do Spanner através de streams de alterações: os registos de dados de streams de alterações contêm etiquetas de transações para as transações que modificaram os dados do utilizador. Isto permite que o leitor de um fluxo de alterações associe as alterações ao tipo de transação com base em etiquetas.

Como atribuir etiquetas de transações

O exemplo seguinte mostra como definir etiquetas de transações através das bibliotecas de cliente do Spanner. Quando usa uma biblioteca de cliente, pode definir uma etiqueta de transação no início da chamada de transação, que é aplicada a todas as operações individuais nessa 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.TransactionOptions.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 etiquetas de transações na tabela de estatísticas de transações

A seguinte consulta devolve as estatísticas de transações 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 usar os seguintes dados como exemplo dos resultados 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

A partir desta tabela de resultados, podemos ver que, se tiver atribuído um TRANSACTION_TAG a uma transação, este é preenchido na tabela de estatísticas de transações. Se não existir nenhuma etiqueta de transação atribuída, é apresentado como uma string vazia.

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

Como ver etiquetas de transações na tabela de estatísticas de bloqueios

A seguinte consulta devolve as estatísticas de bloqueio em intervalos de 10 minutos.

A função CAST() converte o campo BYTES num STRING.row_range_start_key

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 usar os seguintes dados como exemplo dos resultados 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
COLUMN: Singers.SingerInfo
TRANSACTION_TAG: app=product,service=shipping

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

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

A partir desta tabela de resultados, podemos ver que, se tiver atribuído um TRANSACTION_TAG a uma transação, este é preenchido na tabela de estatísticas de bloqueio. Se não estiver atribuída nenhuma etiqueta de transação, é apresentada como uma string vazia.

Mapeamento entre métodos da API e etiqueta de pedido/transação

As etiquetas de pedidos e as etiquetas de transações são aplicáveis a métodos de API específicos com base no facto de o modo de transação ser uma transação só de leitura ou uma transação de leitura/escrita. Geralmente, as etiquetas de transação são aplicáveis a transações de leitura/escrita, enquanto as etiquetas de pedido são aplicáveis a transações só de leitura. A tabela seguinte mostra o mapeamento dos métodos da API para os tipos de etiquetas aplicáveis.

Métodos da API Modos de transação Etiqueta de pedido Etiqueta de transação
Ler,
StreamingRead
Transação só de leitura Sim Não
Transação de leitura/escrita Sim Sim
ExecuteSql,
ExecuteStreamingSql1
Transação só de leitura1 Sim1 Não
Transação de leitura/escrita Sim Sim
ExecuteBatchDml Transação de leitura/escrita Sim Sim
BeginTransaction Transação de leitura/escrita Não Sim
Consolidação Transação de leitura/escrita Não Sim

1 Para consultas de streams de alterações executadas através do conetor Dataflow do SpannerIO, o REQUEST_TAG contém um nome de tarefa do Dataflow.

Limitações

Quando adicionar etiquetas às suas leituras, consultas e transações, tenha em atenção as seguintes limitações:

  • O comprimento de uma string de etiqueta está limitado a 50 carateres. As strings que excedam este limite são truncadas.
  • Apenas são permitidos carateres ASCII (32-126) numa etiqueta. Os carateres Unicode arbitrários são substituídos por sublinhados.
  • Todos os carateres de sublinhado (_) iniciais são removidos da string.
  • As etiquetas são sensíveis a maiúsculas e minúsculas. Por exemplo, se adicionar a etiqueta de pedido APP=cart,ENV=dev a um conjunto de consultas e adicionar app=cart,env=dev a outro conjunto de consultas, o Spanner agrega estatísticas separadamente para cada etiqueta.
  • As etiquetas podem estar em falta nas tabelas de estatísticas na seguinte circunstância:

    • Se o Spanner não conseguir armazenar estatísticas para todas as operações etiquetadas executadas durante o intervalo nas tabelas, o sistema dá prioridade às operações com os recursos de consumo mais elevados durante o intervalo especificado.

Nomenclatura das etiquetas

Quando atribui etiquetas às operações da base de dados, é importante considerar que informações quer transmitir em cada string de etiqueta. A convenção ou o padrão que escolher torna as suas etiquetas mais eficazes. Por exemplo, a atribuição de nomes adequados às etiquetas facilita a correlação das estatísticas com o código da aplicação.

Pode escolher qualquer etiqueta que quiser dentro das limitações indicadas. No entanto, recomendamos que crie uma string de etiqueta como um conjunto de pares chave-valor separados por vírgulas.

Por exemplo, suponha que está a usar uma base de dados do Spanner para um exemplo de utilização de comércio eletrónico. Recomendamos que inclua informações sobre a aplicação, o ambiente de desenvolvimento e a ação realizada pela consulta na etiqueta de pedido que vai atribuir a uma consulta específica. Pode considerar atribuir a string da etiqueta no formato de chave-valor como app=cart,env=dev,action=update.Isto significa que a consulta é chamada a partir da aplicação de carrinho no ambiente de desenvolvimento e é usada para atualizar o carrinho.

Suponhamos que tem outra consulta de uma aplicação de pesquisa de catálogo e atribui a string de etiquetas como app=catalogsearch,env=dev,action=list. Agora, se alguma destas consultas aparecer na tabela de estatísticas de consultas como consultas de latência elevada, pode identificar facilmente a origem através da etiqueta.

Seguem-se alguns exemplos de como um padrão de etiquetagem pode ser usado para organizar as estatísticas de operações. Estes exemplos não se destinam a ser exaustivos. Também pode combiná-los na string da etiqueta usando um delimitador, como uma vírgula.

Chaves de etiquetas Exemplos de par de etiqueta-valor Descrição
Aplicação app=cart
app=frontend
app=catalogsearch
Ajuda a identificar a aplicação que está a chamar 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 a estrutura associada à 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á a chamar a operação.

Aspetos a ter em conta

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

Cenários de resolução de problemas com etiquetas

Encontrar a origem de uma transação problemática

A seguinte consulta devolve os dados não processados 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 seguinte apresenta exemplos de dados devolvidos pela nossa consulta, onde temos três aplicações, nomeadamente carrinho, produto e frontend, que são proprietárias ou consultam a mesma base de dados.

Depois de identificar as transações com latência elevada, pode usar as etiquetas associadas para identificar a parte relevante do código da aplicação e resolver problemas adicionais com 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 etiqueta de pedido pode ser usada para encontrar a origem de uma consulta problemática na tabela de estatísticas de consultas e a origem de uma leitura problemática na tabela de estatísticas de leitura.

Encontrar a latência e outras estatísticas para transações de uma aplicação ou um microsserviço específico

Se usou o nome da aplicação ou o nome do microsserviço na string da etiqueta, ajuda a filtrar a tabela de estatísticas de transações por etiquetas que contenham esse nome da aplicação ou nome do microsserviço.

Suponhamos que adicionou novas transações à app de pagamento e quer ver as latências e outras estatísticas dessas novas transações. Se tiver usado o nome da aplicação de pagamentos na etiqueta, pode filtrar a tabela de estatísticas de transações apenas para as etiquetas que contenham app=payment.

A seguinte consulta devolve as estatísticas de transações para a app de pagamentos 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;

Segue-se um exemplo de resultado:

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 forma, pode encontrar consultas ou leituras de uma aplicação específica na tabela de estatísticas de consultas ou estatísticas de leituras através de etiquetas de pedidos.

Descobrir as transações envolvidas no conflito de bloqueio

Para saber que transações e chaves de linhas registaram os tempos de espera de bloqueio elevados, consultamos a tabela LOCK_STAT_TOP_10MINUTE, que lista as chaves de linhas, 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;

Seguem-se alguns exemplos de saída da nossa consulta:

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

A partir desta tabela de resultados, podemos ver que o conflito ocorreu na tabela Singers na chave SingerId=32. Singers.SingerInfo é a coluna onde ocorreu o conflito de bloqueio entre ReaderShared e WriterShared. Também pode identificar as transações correspondentes (app=cart,service=order e app=cart,service=redis) que estão a ter o conflito.

Depois de identificar as transações que causam os conflitos de bloqueio, pode concentrar-se nestas transações usando as estatísticas de transações para compreender melhor o que as transações estão a fazer e se pode evitar um conflito ou reduzir o tempo durante o qual os bloqueios são mantidos. Para mais informações, consulte o artigo Práticas recomendadas para reduzir a contenção de bloqueios.

O que se segue?