Esaminare la latenza in un componente Spanner con OpenTelemetry

Questo argomento descrive come esaminare un componente Spanner per trovare l'origine della latenza e visualizzare la latenza utilizzando OpenTelemetry. Per una panoramica generale dei componenti in questo argomento, consulta Punti di latenza in una richiesta Spanner.

OpenTelemetry è un framework e toolkit di osservabilità open source che consente di creare e gestire dati di telemetria come tracce, metriche e log. È il risultato di una fusione tra OpenTraccia e OpenCensus. Per ulteriori informazioni, vedi Che cos'è OpenTelemetry?

Le librerie client di Spanner forniscono metriche e tracce con l'uso del framework di osservabilità OpenTelemetry. Segui la procedura descritta in Identificare il punto di latenza per trovare i componenti o i componenti che mostrano la latenza in Spanner.

Prima di iniziare

Prima di iniziare ad acquisire le metriche di latenza, acquisisci familiarità con la strumentazione manuale con OpenTelemetry. Devi configurare l'SDK OpenTelemetry con le opzioni appropriate per esportare i dati di telemetria. Sono disponibili diverse opzioni di esportazione di OpenTelemetry. Ti consigliamo di utilizzare l'esportatore OpenTelemetry Protocol (OTLP). Altre opzioni includono l'utilizzo di OTel Collector con Google Cloud Exporter o Google Managed Service per Prometheus Exporter.

Se esegui la tua applicazione su Compute Engine, puoi utilizzare Ops Agent per raccogliere metriche e tracce del protocollo OpenTelemetry. Per maggiori informazioni, consulta Raccogliere metriche e tracce OTLP.

Aggiungi dipendenze

Per configurare l'SDK OpenTelemetry e l'esportazione OTLP, aggiungi le seguenti dipendenze all'applicazione.

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.24.0
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.24.0
go.opentelemetry.io/otel/sdk v1.23.1
go.opentelemetry.io/otel/sdk/metric v1.23.1

Inserisci l'oggetto OpenTelemetry

Quindi, crea un oggetto OpenTelemetry con l'esportatore OTLP e inserisci l'oggetto OpenTelemetry utilizzando 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"),
		))
}

Acquisisci e visualizza la latenza di round trip del client

La latenza di round trip del client è il tempo (in millisecondi) che intercorre tra il primo byte della richiesta dell'API Spanner che il client invia al database (tramite sia il GFE che il frontend dell'API Spanner) e l'ultimo byte di risposta che il client riceve dal database.

Acquisisci la latenza di round trip del client

La metrica di latenza andata e ritorno del client Spanner non è supportata utilizzando OpenTelemetry. Puoi instrumentare la metrica utilizzando OpenCensus con un ponte ed eseguire la migrazione dei dati in OpenTelemetry.

Visualizza la latenza di round trip del client

Dopo aver recuperato le metriche, puoi visualizzare la latenza di andata e ritorno del client in Cloud Monitoring.

Di seguito è riportato un esempio di grafico che illustra la latenza del 5° percentile per la metrica latenza di andata e ritorno del client. Per modificare la latenza percentile al 50° o al 99° percentile, utilizza il menu Aggregatore.

Il programma crea una vista denominata roundtrip_latency. Questa stringa diventa parte del nome della metrica quando viene esportata in Cloud Monitoring.

latenza di round trip del client Cloud Monitoring.

Acquisizione e visualizzazione della latenza GFE

La latenza di Google Front End (GFE) è il periodo di tempo (in millisecondi) che intercorre tra il momento in cui la rete Google riceve una chiamata di procedura remota dal client e il momento in cui il GFE riceve il primo byte della risposta.

Acquisizione della latenza GFE

Puoi acquisire le metriche di latenza dei GFE abilitando le seguenti opzioni mediante la libreria client 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
		}
	}
}

Visualizzare la latenza dei GFE

Dopo aver recuperato le metriche, puoi visualizzare la latenza dei GFE in Cloud Monitoring.

Ecco un esempio di grafico che illustra l'aggregazione della distribuzione per la metrica di latenza GFE. Per modificare la latenza percentile al 5°, 50°, 95° o 99° percentile, utilizza il menu Aggregatore.

Il programma crea una vista denominata spanner/gfe_latency. Questa stringa diventa parte del nome della metrica quando viene esportata in Cloud Monitoring.

Latenza GFE di Cloud Monitoring.

Acquisisci e visualizza la latenza delle richieste API Spanner

La latenza delle richieste API Spanner è il periodo di tempo (in secondi) tra il primo byte di richiesta ricevuto dal frontend dell'API Spanner e l'ultimo byte di risposta inviato dal frontend dell'API Spanner.

Acquisizione della latenza delle richieste API Spanner

Per impostazione predefinita, questa latenza è disponibile nelle metriche di Cloud Monitoring. Non devi fare nulla per acquisirla ed esportarla.

Visualizza la latenza delle richieste API Spanner

Puoi utilizzare lo strumento di creazione dei grafici Metrics Explorer per visualizzare il grafico della metrica spanner.googleapis.com/api/request_latencies in Cloud Monitoring.

Ecco un esempio di grafico che illustra la latenza del 5° percentile per la metrica di latenza delle richieste dell'API Spanner. Per modificare la latenza percentile al 50° o al 99° percentile, utilizza il menu Aggregatore.

Latenza delle richieste API Cloud Monitoring.

Acquisisci e visualizza la latenza delle query

La latenza delle query è il tempo (in millisecondi) necessario per eseguire query SQL nel database Spanner.

Acquisisci latenza delle query

Puoi acquisire la latenza delle query nelle seguenti lingue:

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
		}
	}
}

Visualizza la latenza delle query

Dopo aver recuperato le metriche, puoi visualizzare la latenza delle query in Cloud Monitoring.

Ecco un esempio di grafico che illustra l'aggregazione della distribuzione per la metrica di latenza GFE. Per modificare la latenza percentile al 5°, 50°, 95° o 99° percentile, utilizza il menu Aggregatore.

Il programma crea una vista OpenCensus chiamata query_stats_elapsed. Questa stringa diventa parte del nome della metrica quando viene esportata in Cloud Monitoring.

Latenza delle query di Cloud Monitoring.

Passaggi successivi