Examiner la latence dans un composant Spanner avec OpenTelemetry

Cet article explique comment examiner un composant Spanner pour trouver la source de la latence et visualiser cette latence à l'aide d'OpenTelemetry. Pour une présentation générale des composants de cet article, consultez la section Points de latence dans une requête Spanner.

OpenTelemetry est un kit d'outils et un framework d'observabilité Open Source qui vous permet de créer et de gérer des données de télémétrie, telles que des traces, des métriques et des journaux. Il est le résultat d'une fusion entre OpenTracing et OpenCensus. Pour en savoir plus, consultez Qu'est-ce qu'OpenTelemetry ?

Les bibliothèques clientes Spanner fournissent des métriques et des traces à l'aide du framework d'observabilité OpenTelemetry. Suivez la procédure décrite dans la section Identifier le point de latence pour rechercher les composants ou les composants qui présentent une latence dans Spanner.

Avant de commencer

Avant de commencer à capturer des métriques de latence, familiarisez-vous avec l'instrumentation manuelle avec OpenTelemetry. Vous devez configurer le SDK OpenTelemetry avec les options appropriées pour exporter vos données de télémétrie. Plusieurs options d'exportateur OpenTelemetry sont disponibles. Nous vous recommandons d'utiliser l'exportateur du protocole OpenTelemetry (OTLP). D'autres options incluent l'utilisation d'un collecteur OTel avec Google Cloud Exporter ou Google Managed Service pour Prometheus Exporter.

Si vous exécutez votre application sur Compute Engine, vous pouvez utiliser l'agent Ops pour collecter les métriques et les traces du protocole OpenTelemetry. Pour en savoir plus, consultez Collecter les métriques et les traces OTLP.

Ajouter des dépendances

Pour configurer le SDK OpenTelemetry et l'exportateur OTLP, ajoutez les dépendances suivantes à votre application.

Java

<dependency>
  <groupId>com.google.cloud</groupId>
  <artifactId>google-cloud-spanner</artifactId>
</dependency>
<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-sdk</artifactId>
</dependency>
<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-sdk-metrics</artifactId>
</dependency>
<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-sdk-trace</artifactId>
</dependency>
<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>

Go

go.opentelemetry.io/otel v1.23.1
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.23.1
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.1
go.opentelemetry.io/otel/metric v1.23.1
go.opentelemetry.io/otel/sdk v1.23.1
go.opentelemetry.io/otel/sdk/metric v1.23.1

Injecter l'objet OpenTelemetry

Ensuite, créez un objet OpenTelemetry avec l'exportateur OTLP et injectez l'objet OpenTelemetry à l'aide de SpannerOptions.

Java

// Enable OpenTelemetry metrics and traces before Injecting OpenTelemetry
SpannerOptions.enableOpenTelemetryMetrics();
SpannerOptions.enableOpenTelemetryTraces();

// Create a new meter provider
SdkMeterProvider sdkMeterProvider = SdkMeterProvider.builder()
    // Use Otlp exporter or any other exporter of your choice.
    .registerMetricReader(
        PeriodicMetricReader.builder(OtlpGrpcMetricExporter.builder().build()).build())
    .build();

// Create a new tracer provider
SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
    // Use Otlp exporter or any other exporter of your choice.
    .addSpanProcessor(SimpleSpanProcessor.builder(OtlpGrpcSpanExporter
        .builder().build()).build())
        .build();

// Configure OpenTelemetry object using Meter Provider and Tracer Provider
OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
    .setMeterProvider(sdkMeterProvider)
    .setTracerProvider(sdkTracerProvider)
    .build();

// Inject OpenTelemetry object via Spanner options or register as GlobalOpenTelemetry.
SpannerOptions options = SpannerOptions.newBuilder()
    .setOpenTelemetry(openTelemetry)
    .build();
Spanner spanner = options.getService();

Go

// Ensure that your Go runtime version is supported by the OpenTelemetry-Go compatibility policy before enabling OpenTelemetry instrumentation.
// Refer to compatibility here https://github.com/googleapis/google-cloud-go/blob/main/debug.md#opentelemetry

import (
	"context"
	"fmt"
	"io"
	"log"
	"strconv"
	"strings"

	"cloud.google.com/go/spanner"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
	"go.opentelemetry.io/otel/metric"
	sdkmetric "go.opentelemetry.io/otel/sdk/metric"
	"go.opentelemetry.io/otel/sdk/resource"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
	semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
	"google.golang.org/api/iterator"
)

func enableOpenTelemetryMetricsAndTraces(w io.Writer, db string) error {
	// db = `projects/<project>/instances/<instance-id>/database/<database-id>`
	ctx := context.Background()

	// Create a new resource to uniquely identify the application
	res, err := newResource()
	if err != nil {
		log.Fatal(err)
	}

	// Enable OpenTelemetry traces by setting environment variable GOOGLE_API_GO_EXPERIMENTAL_TELEMETRY_PLATFORM_TRACING to the case-insensitive value "opentelemetry" before loading the client library.
	// Enable OpenTelemetry metrics before injecting meter provider.
	spanner.EnableOpenTelemetryMetrics()

	// Create a new tracer provider
	tracerProvider, err := getOtlpTracerProvider(ctx, res)
	defer tracerProvider.ForceFlush(ctx)
	if err != nil {
		log.Fatal(err)
	}
	// Register tracer provider as global
	otel.SetTracerProvider(tracerProvider)

	// Create a new meter provider
	meterProvider := getOtlpMeterProvider(ctx, res)
	defer meterProvider.ForceFlush(ctx)

	// Inject meter provider locally via ClientConfig when creating a spanner client or set globally via setMeterProvider.
	client, err := spanner.NewClientWithConfig(ctx, db, spanner.ClientConfig{OpenTelemetryMeterProvider: meterProvider})
	if err != nil {
		return err
	}
	defer client.Close()
	return nil
}

func getOtlpMeterProvider(ctx context.Context, res *resource.Resource) *sdkmetric.MeterProvider {
	otlpExporter, err := otlpmetricgrpc.New(ctx)
	if err != nil {
		log.Fatal(err)
	}
	meterProvider := sdkmetric.NewMeterProvider(
		sdkmetric.WithResource(res),
		sdkmetric.WithReader(sdkmetric.NewPeriodicReader(otlpExporter)),
	)
	return meterProvider
}

func getOtlpTracerProvider(ctx context.Context, res *resource.Resource) (*sdktrace.TracerProvider, error) {
	traceExporter, err := otlptracegrpc.New(ctx)
	if err != nil {
		return nil, err
	}

	tracerProvider := sdktrace.NewTracerProvider(
		sdktrace.WithResource(res),
		sdktrace.WithBatcher(traceExporter),
		sdktrace.WithSampler(sdktrace.AlwaysSample()),
	)

	return tracerProvider, nil
}

func newResource() (*resource.Resource, error) {
	return resource.Merge(resource.Default(),
		resource.NewWithAttributes(semconv.SchemaURL,
			semconv.ServiceName("otlp-service"),
			semconv.ServiceVersion("0.1.0"),
		))
}

Capturer et visualiser la latence aller-retour du client

La latence aller-retour du client correspond à la durée (en millisecondes) entre le premier octet de la requête API Spanner que le client envoie à la base de données (via le GFE et l'interface de l'API Spanner) et le dernier octet de réponse que le client reçoit de la base de données.

Capturer la latence aller-retour du client

La métrique de latence aller-retour du client Spanner n'est pas compatible avec OpenTelemetry. Vous pouvez instrumenter la métrique à l'aide d'OpenCensus avec un pont et migrer les données vers OpenTelemetry.

Visualiser la latence aller-retour du client

Après avoir récupéré les métriques, vous pouvez visualiser la latence aller-retour du client dans Cloud Monitoring.

Voici un exemple de graphique illustrant la latence du 5e centile de la métrique de latence aller-retour du client. Pour définir la latence du centile sur le 50e ou le 99e centile, utilisez le menu Aggregator (Agrégateur).

Le programme crée une vue appelée roundtrip_latency. Cette chaîne devient une partie du nom de la métrique lorsqu'elle est exportée vers Cloud Monitoring.

Latence aller-retour du client Cloud Monitoring.

Capturez et visualisez la latence du GFE

La latence du GFE (Google Front End) correspond au délai (en millisecondes) entre le moment où le réseau Google reçoit un appel de procédure à distance du client et le moment où le GFE reçoit le premier octet de la réponse.

Capturer la latence du GFE

Vous pouvez capturer les métriques de latence des GFE en activant les options suivantes à l'aide de la bibliothèque cliente Spanner.

Java

static void captureGfeMetric(DatabaseClient dbClient) {
  // GFE_latency and other Spanner metrics are automatically collected
  // when OpenTelemetry metrics are enabled.

  try (ResultSet resultSet =
      dbClient
          .singleUse() // Execute a single read or query against Cloud Spanner.
          .executeQuery(Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"))) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %d %s", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2));
    }
  }
}

Go

// GFE_Latency and other Spanner metrics are automatically collected
// when OpenTelemetry metrics are enabled.
func captureGFELatencyMetric(ctx context.Context, client spanner.Client) error {
	stmt := spanner.Statement{SQL: `SELECT SingerId, AlbumId, AlbumTitle FROM Albums`}
	iter := client.Single().Query(ctx, stmt)
	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
		}
	}
}

Visualiser la latence du GFE

Après avoir récupéré les métriques, vous pouvez visualiser la latence des GFE dans Cloud Monitoring.

Voici un exemple de graphique illustrant l'agrégation de distribution pour la métrique de latence du GFE. Pour définir la latence du centile sur les 5e, 50e, 95e ou 99e centile, utilisez le menu Agrégateur.

Le programme crée une vue appelée spanner/gfe_latency. Cette chaîne devient une partie du nom de la métrique lorsqu'elle est exportée vers Cloud Monitoring.

Latence du GFE de Cloud Monitoring.

Capturer et visualiser la latence des requêtes API Spanner

La latence des requêtes API Spanner correspond à la durée (en secondes) entre le premier octet de la requête reçu par l'interface de l'API Spanner et le dernier octet de réponse envoyé par l'interface de l'API Spanner.

Capturer la latence des requêtes API Spanner

Par défaut, cette latence est disponible dans les métriques Cloud Monitoring. Vous n'avez rien à faire pour la capturer et l'exporter.

Visualiser la latence des requêtes API Spanner

Vous pouvez utiliser l'outil de graphique de l'explorateur de métriques pour visualiser le graphique de la métrique spanner.googleapis.com/api/request_latencies dans Cloud Monitoring.

Voici un exemple de graphe illustrant la latence au 5e centile de la métrique de latence des requêtes de l'API Spanner. Pour définir la latence du centile sur le 50e ou le 99e centile, utilisez le menu Aggregator (Agrégateur).

Latence des requêtes API Cloud Monitoring.

Capturez et visualisez la latence des requêtes

La latence des requêtes correspond à la durée (en millisecondes) nécessaire pour exécuter des requêtes SQL dans la base de données Spanner.

Capturer la latence des requêtes

Vous pouvez capturer la latence des requêtes pour les langages suivants :

Java

static void captureQueryStatsMetric(OpenTelemetry openTelemetry, DatabaseClient dbClient) {
  // Register query stats metric.
  // This should be done once before start recording the data.
  Meter meter = openTelemetry.getMeter("cloud.google.com/java");
  DoubleHistogram queryStatsMetricLatencies =
      meter
          .histogramBuilder("spanner/query_stats_elapsed")
          .setDescription("The execution of the query")
          .setUnit("ms")
          .build();

  // Capture query stats metric data.
  try (ResultSet resultSet = dbClient.singleUse()
      .analyzeQuery(Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"),
          QueryAnalyzeMode.PROFILE)) {

    while (resultSet.next()) {
      System.out.printf(
          "%d %d %s", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2));
    }

    String value = resultSet.getStats().getQueryStats()
        .getFieldsOrDefault("elapsed_time", Value.newBuilder().setStringValue("0 msecs").build())
        .getStringValue();
    double elapsedTime = value.contains("msecs")
        ? Double.parseDouble(value.replaceAll(" msecs", ""))
        : Double.parseDouble(value.replaceAll(" secs", "")) * 1000;
    queryStatsMetricLatencies.record(elapsedTime);
  }
}

Go

func captureQueryStatsMetric(ctx context.Context, mp metric.MeterProvider, client spanner.Client) error {
	meter := mp.Meter(spanner.OtInstrumentationScope)
	// Register query stats metric with OpenTelemetry to record the data.
	// This should be done once before start recording the data.
	queryStats, err := meter.Float64Histogram(
		"spanner/query_stats_elapsed",
		metric.WithDescription("The execution of the query"),
		metric.WithUnit("ms"),
		metric.WithExplicitBucketBoundaries(0.0, 0.01, 0.05, 0.1, 0.3, 0.6, 0.8, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 8.0, 10.0, 13.0,
			16.0, 20.0, 25.0, 30.0, 40.0, 50.0, 65.0, 80.0, 100.0, 130.0, 160.0, 200.0, 250.0,
			300.0, 400.0, 500.0, 650.0, 800.0, 1000.0, 2000.0, 5000.0, 10000.0, 20000.0, 50000.0,
			100000.0),
	)
	if err != nil {
		fmt.Print(err)
	}

	stmt := spanner.Statement{SQL: `SELECT SingerId, AlbumId, AlbumTitle FROM Albums`}
	iter := client.Single().QueryWithStats(ctx, stmt)
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			// Record query execution time with OpenTelemetry.
			elapasedTime := iter.QueryStats["elapsed_time"].(string)
			elapasedTimeMs, err := strconv.ParseFloat(strings.TrimSuffix(elapasedTime, " msecs"), 64)
			if err != nil {
				return err
			}
			queryStats.Record(ctx, elapasedTimeMs)
			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
		}
	}
}

Visualiser la latence des requêtes

Après avoir récupéré les métriques, vous pouvez visualiser la latence des requêtes dans Cloud Monitoring.

Voici un exemple de graphique illustrant l'agrégation de distribution pour la métrique de latence du GFE. Pour définir la latence du centile sur les 5e, 50e, 95e ou 99e centile, utilisez le menu Agrégateur.

Le programme crée une vue OpenCensus appelée query_stats_elapsed. Cette chaîne devient une partie du nom de la métrique lorsqu'elle est exportée vers Cloud Monitoring.

Latence des requêtes Cloud Monitoring.

Étapes suivantes