Résoudre les problèmes liés aux tags de demande et aux tags de transaction

Spanner fournit un ensemble de tables de statistiques intégrées pour vous aider à mieux comprendre vos requêtes, lectures et transactions. Pour mettre en corrélation les statistiques avec le code de votre application et améliorer le dépannage, vous pouvez ajouter un tag (une chaîne de forme libre) aux opérations de lecture, de requête et de transaction Spanner dans le code de votre application. Ces tags sont renseignés dans les tableaux de statistiques, pour vous aider à mettre en corrélation et à rechercher en fonction de tags.

Spanner accepte deux types de balises : les balises request et les balises transaction. Comme leur nom l'indique, vous pouvez ajouter des tags aux transactions et demander des tags à des requêtes individuelles et à des API de lecture. Vous pouvez définir un tag de transaction au niveau de la transaction et définir des tags de requête individuels pour chaque requête API applicable au sein de la transaction. Les tags de requête et les tags de transaction définis dans le code de l'application sont renseignés dans les colonnes des tableaux de statistiques suivants.

Tableau de statistiques Type de tags renseignés dans la table de statistiques
Statistiques des requêtes TopN Demander des tags
Statistiques de lecture TopN Demander des tags
Statistiques des transactions TopN Tags de transaction
Statistiques de verrous TopN Tags de transaction

Demander des tags

Vous pouvez ajouter un tag de requête facultatif à une requête ou à une requête de lecture. Spanner regroupe les statistiques par tag de requête, qui est visible dans le champ REQUEST_TAG des tables des statistiques sur la requête et des statistiques de lecture.

Quand utiliser des tags de requête

Voici quelques-uns des scénarios qui tirent parti des tags de requête.

  • Rechercher la source d'une requête ou d'une lecture problématique:Spanner collecte des statistiques sur les lectures et les requêtes dans les tables de statistiques intégrées. Lorsque vous trouvez les requêtes lentes ou les lectures consommant beaucoup de processeurs dans la table de statistiques, si vous leur avez déjà attribué des tags, vous pouvez identifier la source (application/microservice) qui appelle ces opérations en fonction des informations dans le tag.
  • Identifier les lectures ou les requêtes dans les tables de statistiques : l'attribution de tags de requête permet de filtrer les lignes de la table de statistiques en fonction des tags qui vous intéressent.
  • Rechercher si les requêtes d'une application ou d'un microservice particulier sont lentes : les tags de requête peuvent aider à déterminer si les requêtes d'une application ou d'un microservice particulier présentent des latences plus élevées.
  • Regroupement des statistiques pour un ensemble de lectures ou de requêtes : vous pouvez utiliser des tags de requête pour suivre, comparer et signaler les performances d'un ensemble de lectures ou de requêtes similaires. Par exemple, si plusieurs requêtes accèdent à une table/un ensemble de tables ayant le même modèle d'accès, vous pouvez envisager d'ajouter le même tag à toutes ces requêtes pour les suivre en même temps.

Attribuer des tags de requête

L'exemple suivant montre comment définir des tags de requête à l'aide des bibliothèques clientes 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

Afficher les tags de requête dans un tableau de statistiques

La requête suivante renvoie les statistiques de requête sur des intervalles de 10 minutes.

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;

Prenons les données suivantes en guise d'exemple de résultats générés par notre requête.

texte 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; [chaîne vide] 154 0,048 42 486,33

À partir de ce tableau de résultats, nous constatons que si vous avez attribué un REQUEST_TAG à une requête, il est renseigné dans le tableau de statistiques. Si aucun tag de requête n'est attribué, il s'affiche sous forme de chaîne vide.

Pour les requêtes taguées, les statistiques sont agrégées par tag (par exemple, le tag de requête app=concert,env=dev,action=select a une latence moyenne de 0,025 seconde). Si aucun tag n'est attribué, les statistiques sont agrégées par requête (par exemple, la requête de la troisième ligne présente une latence moyenne de 0,048 seconde).

Tags de transaction

Vous pouvez ajouter un tag de transaction facultatif à chaque transaction. Spanner regroupe les statistiques par tag de transaction, ce qui est visible dans le champ TRANSACTION_TAG des tables de statistiques sur les transactions.

Quand utiliser les tags de transaction ?

Voici quelques-uns des scénarios qui tirent parti des tags de transaction.

  • Rechercher la source d'une transaction problématique:Spanner collecte les statistiques des transactions en lecture-écriture dans le tableau des statistiques de transaction. Lorsque vous trouvez des transactions lentes dans le tableau de statistiques des transactions, si vous leur avez déjà attribué des tags, vous pouvez identifier la source (application/microservice) qui appelle ces transactions en fonction des informations le tag.
  • Identifier des transactions dans des tableaux de statistiques : l'attribution de tags de transaction permet de filtrer les lignes de table de statistiques en fonction des tags qui vous intéressent. Sans tags de transaction, la découverte des opérations représentées par une statistique peut s'avérer fastidieuse. Par exemple, pour les statistiques de transaction, vous devez examiner les tables et les colonnes impliquées afin d'identifier la transaction sans tag.
  • Rechercher si les transactions d'une application ou d'un microservice sont lentes : les tags de transaction peuvent aider à déterminer si les transactions d'une application ou d'un microservice particulier présentent des latences plus élevées.
  • Regroupement des statistiques pour un ensemble de transactions : vous pouvez utiliser des tags de transaction pour suivre, comparer et signaler les performances d'un ensemble de transactions similaires.
  • Rechercher les transactions qui accèdent aux colonnes impliquées dans le conflit de verrouillage : les tags de transaction peuvent aider à identifier les transactions individuelles à l'origine des conflits de verrouillage dans les tableaux de statistiques de verrouillage.
  • Transférer des données de modification des utilisateurs en flux continu depuis Spanner à l'aide de flux de modifications:les enregistrements de données de flux de modifications contiennent des tags de transaction pour les transactions qui ont modifié les données utilisateur. Cela permet au lecteur d'un flux de modifications d'associer des modifications au type de transaction en fonction de tags.

Attribuer des tags de transaction

L'exemple suivant montre comment définir des tags de transaction à l'aide des bibliothèques clientes Spanner. Lorsque vous utilisez une bibliothèque cliente, vous pouvez définir un tag de transaction au début de l'appel de transaction qui sera appliqué à toutes les opérations individuelles de cette transaction.

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";
            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";
            return await insertCommand.ExecuteNonQueryAsync();
        });
    }
}

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

Afficher les tags de transaction dans le tableau de statistiques des transactions

La requête suivante renvoie les statistiques de transaction sur des intervalles de 10 minutes.

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;

Prenons les données suivantes en guise d'exemple de résultats générés par notre requête.

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 [chaîne vide] [Singers.FirstName, Singers.LastName, Singers._exists] 5357 0,048

Ce tableau de résultats indique que si vous avez attribué une valeur TRANSACTION_TAG à une transaction, elle est alors renseignée dans la table de statistiques sur les transactions. Si aucun tag de transaction n'est attribué, il s'affiche sous forme de chaîne vide.

Pour les transactions taguées, les statistiques sont agrégées par tag de transaction (par exemple, le tag de transaction app=concert,env=dev a une latence moyenne de 0,3508 seconde). Si aucun tag n'est attribué, les statistiques sont agrégées par FPRINT (par exemple, 77848338483 à la troisième ligne présente une latence moyenne de 0,048 seconde).

Afficher les tags de transaction dans le tableau des statistiques de verrouillage

La requête suivante renvoie les statistiques de verrouillage sur des intervalles de 10 minutes.

La fonction CAST() convertit le champ BYTES row_range_start_key en 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;

Prenons les données suivantes en guise d'exemple de résultats générés par notre requête.

row_range_start_key lock_wait_seconds sample_lock_requests
Songs(2,1,1) 0.61 LOCK_MODE: ReaderShared
COLUMN: Singers.SingerInfo
TRANSACTION_TAG: app=product,service=shipping

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

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

À partir de ce tableau de résultats, nous constatons que si vous avez attribué une TRANSACTION_TAG à une transaction, elle est alors renseignée dans la table de statistiques de verrouillage. Si aucun tag de transaction n'est attribué, il s'affiche sous forme de chaîne vide.

Mapper entre les méthodes API et le tag requête/transaction

Les tags de requête et les tags de transaction s'appliquent à des méthodes d'API spécifiques, selon que le mode transactionnel est une transaction en lecture seule ou une transaction en lecture-écriture. En règle générale, les tags de transaction sont applicables aux transactions en lecture-écriture, tandis que les tags de requête sont applicables aux transactions en lecture seule. Le tableau suivant montre la correspondance entre les méthodes d'API et les types de tags applicables.

Méthodes d'API Modes de transaction Demander un tag Tag de transaction
Lecture,
StreamingRead
Transaction en lecture seule Oui Non
Transaction en lecture-écriture Oui Oui
ExecuteSql,
ExecuteStreamingSql1
Transaction en lecture seule1 Oui1 Non
Transaction en lecture-écriture Oui Oui
ExecuteBatchDml Transaction en lecture-écriture Oui Oui
BeginTransaction Transaction en lecture-écriture Non Oui
Commit Transaction en lecture-écriture Non Oui

1 Pour les requêtes de flux de modifications exécutées à l'aide du connecteur Dataflow SpannerIO Apache Beam, REQUEST_TAG contient un nom de job Dataflow.

Limites

Lorsque vous ajoutez des tags à vos lectures, requêtes et transactions, tenez compte des limitations suivantes :

  • La longueur d'une chaîne de tag est limitée à 50 caractères. Les chaînes qui dépassent cette limite sont tronquées.
  • Seuls les caractères ASCII (32-126) sont autorisés dans un tag. Les caractères Unicode arbitraires sont remplacés par des traits de soulignement.
  • Tous les traits de soulignement (_) au début de la chaîne sont supprimés.
  • Les tags sont sensibles à la casse. Par exemple, si vous ajoutez le tag de requête APP=cart,ENV=dev à un ensemble de requêtes et que vous ajoutez app=cart,env=dev à un autre ensemble de requêtes, Spanner agrège les statistiques séparément pour chaque tag.
  • Les tags peuvent ne pas être présents dans les tableaux de statistiques dans les cas suivants :

    • Si Spanner ne peut pas stocker les statistiques de toutes les opérations taguées exécutées pendant l'intervalle dans des tables, le système donne la priorité aux opérations qui consomment le plus de ressources pendant l'intervalle spécifié.

Dénomination des tags

Lorsque vous attribuez des tags à vos opérations de base de données, il est important de prendre en compte les informations que vous souhaitez transmettre dans chaque chaîne de tag. La convention ou le modèle que vous choisissez rend vos tags plus efficaces. Par exemple, une dénomination de tag appropriée facilite la mise en corrélation des statistiques avec le code de l'application.

Vous pouvez choisir n'importe quel tag dans les limites définies. Cependant, nous vous recommandons de créer une chaîne de tag sous la forme d'un ensemble de paires valeur/clé séparées par des virgules.

Par exemple, supposons que vous utilisiez une base de données Spanner pour un cas d'utilisation d'e-commerce. Vous pouvez inclure des informations sur l'application, l'environnement de développement et l'action effectuée par la requête dans le tag de requête que vous allez attribuer à une requête particulière. Vous pouvez envisager d'attribuer la chaîne de tag au format clé-valeur en tant que app=cart,env=dev,action=update. Cela signifie que la requête est appelée à partir de l'application de panier dans l'environnement de développement et permet de mettre à jour le panier.

Supposons que vous ayez une autre requête provenant d'une application de recherche de catalogue et que vous attribuiez la chaîne de tag comme app=catalogsearch,env=dev,action=list. Si l'une de ces requêtes apparaît dans la table de statistiques comme étant des requêtes à latence élevée, vous pouvez facilement identifier la source à l'aide de ce tag.

Voici quelques exemples d'utilisation d'un modèle d'ajout de tags pour organiser vos statistiques d'opération. Ces exemples ne sont pas exhaustifs. Vous pouvez également les combiner dans la chaîne de tag à l'aide d'un délimiteur tel qu'une virgule.

Clés de tag Exemples de paires tag/valeur Description
Application app=cart
app=frontend
app=catalogsearch
Permet d'identifier l'application qui appelle l'opération.
Environnement env=prod
env=dev
env=test
env=staging
Aide à identifier l'environnement associé à l'opération.
Framework framework=spring
framework=django
framework=jetty
Aide à identifier le framework associé à l'opération.
Action action=list
action=retrieve
action=update
Permet d'identifier l'action entreprise par l'opération.
Service service=payment
service=shipping
Permet d'identifier le microservice qui appelle l'opération.

Éléments à noter

  • Lorsque vous attribuez un élément REQUEST_TAG, les statistiques de plusieurs requêtes ayant la même chaîne de tag sont regroupées sur une seule ligne du tableau Statistiques sur les requêtes. Seul le texte de l'une de ces requêtes est affiché dans le champ TEXT.
  • Lorsque vous attribuez un REQUEST_TAG, les statistiques de plusieurs lectures ayant la même chaîne de tag sont regroupées sur une seule ligne de la table Statistiques de lecture. L'ensemble de toutes les colonnes lues est ajouté au champ READ_COLUMNS.
  • Lorsque vous attribuez un élément TRANSACTION_TAG, les statistiques des transactions ayant la même chaîne de tag sont regroupées sur une seule ligne du tableau Statistiques sur les transactions. L'ensemble de toutes les colonnes écrites par les transactions est ajouté au champ WRITE_CONSTRUCTIVE_COLUMNS, et l'ensemble de toutes les colonnes lues est ajouté au champ READ_COLUMNS.

Scénarios de dépannage à l'aide de tags

Déterminer la source d'une transaction problématique

La requête suivante renvoie les données brutes correspondant aux principales transactions exécutées au cours de la période sélectionnée.

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;

Le tableau suivant répertorie les exemples de données renvoyées par notre requête, dans lesquels nous avons trois applications, à savoir cart, product et frontend, qui possèdent les valeurs suivantes : ou interroger la même base de données.

Une fois que vous avez identifié les transactions présentant une latence élevée, vous pouvez utiliser les tags associés pour identifier la partie pertinente de votre code d'application, puis résoudre les problèmes à l'aide des statistiques de transaction.

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 [chaîne vide] 0.031 0,015 14 0

De même, le tag de requête peut être utilisé pour trouver la source d'une requête problématique à partir de la table statistiques de requête et la source de lecture problématique à partir de la table statistiques de lecture.

Déterminer la latence et d'autres statistiques pour les transactions d'une application ou d'un microservice spécifique

Si vous avez utilisé le nom de l'application ou le microservice dans la chaîne de tag, il est utile de filtrer le tableau de statistiques des transactions par tags contenant ce nom d'application ou de microservice.

Supposons que vous ayez ajouté de nouvelles transactions à l'application de paiement et que vous souhaitez examiner les latences et d'autres statistiques de ces nouvelles transactions. Si vous avez utilisé le nom de l'application de paiement dans le tag, vous pouvez filtrer le tableau de statistiques des transactions pour n'afficher que les tags contenant app=payment.

La requête suivante renvoie les statistiques de transaction de l'application de paiement sur des intervalles de 10 minutes.

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;

Voici un exemple de résultat :

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

De même, vous pouvez trouver des requêtes ou des lectures d'une application spécifique dans la table statistiques de requête ou statistiques de lecture à l'aide de tags de requête.

Découvrir les transactions impliquées dans un conflit de verrouillage

Pour savoir quelles transactions et clés de ligne ont subi des temps d'attente élevés, nous interrogeons la table LOCK_STAT_TOP_10MINUTE, qui répertorie les clés de ligne, les colonnes et les transactions correspondantes impliquées dans le conflit de verrouillage.

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;

Voici un exemple de résultat de notre requête :

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
COLUMN: Singers.SingerInfo
TRANSACTION_TAG:
app=cart,service=order

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

À partir de cette table de résultats, nous pouvons constater le conflit dans la table Singers à la clé SingerId=32. Singers.SingerInfo est la colonne où le conflit de verrouillage s'est produit entre ReaderShared et WriterShared. Vous pouvez également identifier les transactions correspondantes (app=cart,service=order et app=cart,service=redis) qui rencontrent le conflit.

Une fois les transactions à l'origine des conflits de verrouillage identifiées, vous pouvez vous concentrer sur celles-ci en utilisant les statistiques de transaction pour mieux comprendre le fonctionnement des transactions et éviter un conflit ou réduire la durée pendant laquelle les verrous sont maintenus. Pour plus d'informations, consultez les bonnes pratiques pour réduire les conflits de verrouillage.

Étapes suivantes