Latenz in einer Spanner-Komponente mit OpenTelemetry untersuchen

In diesem Thema wird beschrieben, wie Sie eine Spanner-Komponente untersuchen, um die Ursache der Latenz zu finden und diese Latenz mithilfe von OpenTelemetry zu visualisieren. Eine allgemeine Übersicht über die Komponenten in diesem Thema finden Sie unter Latenzpunkte in einer Spanner-Anfrage.

OpenTelemetry ist ein Open-Source-Framework und -Toolkit für die Beobachtbarkeit, mit dem Sie Telemetriedaten wie Traces, Messwerte und Logs erstellen und verwalten können. Dies ist das Ergebnis einer Zusammenführung zwischen OpenTracing und OpenCensus. Weitere Informationen finden Sie unter Was ist OpenTelemetry?

Spanner-Clientbibliotheken stellen Messwerte und Traces mithilfe des OpenTelemetry-Beobachtbarkeits-Frameworks bereit. Folgen Sie der Anleitung unter Latenzpunkt ermitteln, um die Komponenten oder Komponenten zu finden, bei denen in Spanner Latenz angezeigt wird.

Hinweise

Machen Sie sich vor dem Erfassen von Latenzmesswerten mit der manuellen Instrumentierung mit OpenTelemetry vertraut. Sie müssen das OpenTelemetry SDK mit geeigneten Optionen für den Export Ihrer Telemetriedaten konfigurieren. Es stehen mehrere OpenTelemetry-Exporter-Optionen zur Verfügung. Wir empfehlen die Verwendung des OTLP-Exporters (OpenTelemetry Protocol). Alternativ können Sie einen OTel Collector mit Google Cloud Exporter oder Google Managed Service for Prometheus Exporter verwenden.

Wenn Sie Ihre Anwendung in Compute Engine ausführen, können Sie mit dem Ops-Agent Messwerte und Traces des OpenTelemetry-Protokolls erfassen. Weitere Informationen finden Sie unter OTLP-Messwerte und -Traces erfassen.

Abhängigkeiten hinzufügen

Fügen Sie Ihrer Anwendung die folgenden Abhängigkeiten hinzu, um das OpenTelemetry SDK und den OTLP-Exporter zu konfigurieren.

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>

Einfach loslegen (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

OpenTelemetry-Objekt einschleusen

Erstellen Sie dann ein OpenTelemetry-Objekt mit dem OTLP-Exporter und injizieren Sie das OpenTelemetry-Objekt mit 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();

Einfach loslegen (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"),
		))
}

Umlauflatenzen des Clients erfassen und visualisieren

Die Umlauflatenz des Clients ist die Zeit (in Millisekunden) zwischen dem ersten Byte der Spanner API-Anfrage, die der Client an die Datenbank (sowohl über das GFE als auch das Spanner API-Frontend) sendet, und dem letzten Byte der Antwort, die der Client von der Datenbank erhält.

Umlauflatenz des Clients erfassen

Der Messwert für die Umlauflatenz des Spanner-Clients wird bei OpenTelemetry nicht unterstützt. Sie können den Messwert mithilfe von OpenCensus mit einer Bridge instrumentieren und die Daten zu OpenTelemetry migrieren.

Client-Umlauflatenz visualisieren

Nach dem Abrufen der Messwerte können Sie die Umlauflatenz des Clients in Cloud Monitoring visualisieren.

Das folgende Beispieldiagramm veranschaulicht die Latenz des 5. Perzentils für den Messwert für die Client-Umlauflatenz. Verwenden Sie das Menü Aggregator, um die Perzentillatenz entweder auf das 50. oder 99. Perzentil zu ändern.

Das Programm erstellt eine Ansicht mit dem Namen roundtrip_latency. Dieser String wird beim Export nach Cloud Monitoring Teil des Messwertnamens.

Umlauflatenz des Cloud Monitoring-Clients

GFE-Latenz erfassen und visualisieren

Die GFE-Latenz (Google Front End) ist die Zeit (in Millisekunden) zwischen dem Empfang eines Remote-Prozeduraufrufs vom Client im Google-Netzwerk und dem Empfang des ersten Byte der Antwort durch das GFE.

GFE-Latenz erfassen

Sie können GFE-Latenzmesswerte erfassen, indem Sie die folgenden Optionen mithilfe der Spanner-Clientbibliothek aktivieren.

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

Einfach loslegen (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
		}
	}
}

GFE-Latenz visualisieren

Nach dem Abrufen der Messwerte können Sie die GFE-Latenz in Cloud Monitoring visualisieren.

Das folgende Beispiel zeigt ein Diagramm, in dem die Verteilungsaggregation für den GFE-Latenzmesswert veranschaulicht wird. Verwenden Sie das Menü Aggregator, um die Perzentillatenz auf das 5., 50., 95. oder 99. Perzentil zu ändern.

Das Programm erstellt eine Ansicht mit dem Namen spanner/gfe_latency. Dieser String wird beim Export in Cloud Monitoring Teil des Messwertnamens.

Cloud Monitoring-GFE-Latenz.

Latenz von Spanner API-Anfragen erfassen und visualisieren

Die Anfragelatenz der Spanner API ist die Zeitspanne (in Sekunden) zwischen dem ersten Byte der Anfrage, das das Spanner API-Frontend empfängt, und dem letzten Byte der Antwort, das das Spanner API-Frontend sendet.

Latenz von Spanner API-Anfragen erfassen

Standardmäßig ist diese Latenz als Teil der Cloud Monitoring-Messwerte verfügbar. Sie müssen nichts unternehmen, um sie zu erfassen und zu exportieren.

Spanner API-Anfragelatenz visualisieren

Mit dem Diagrammtool Metrics Explorer können Sie das Diagramm für den Messwert spanner.googleapis.com/api/request_latencies in Cloud Monitoring visualisieren.

Das folgende Beispieldiagramm veranschaulicht die Latenz des 5. Perzentils für den Messwert zur Anfragelatenz der Spanner API. Verwenden Sie das Menü Aggregator, um die Perzentillatenz auf das 50. oder 99. Perzentil zu ändern.

Cloud Monitoring API-Anfragelatenz

Abfragelatenz erfassen und visualisieren

Die Abfragelatenz ist die Zeit (in Millisekunden), die zum Ausführen von SQL-Abfragen in der Spanner-Datenbank benötigt wird.

Abfragelatenz erfassen

Sie können die Abfragelatenz für die folgenden Sprachen erfassen:

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

Einfach loslegen (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
		}
	}
}

Abfragelatenz visualisieren

Nach dem Abrufen der Messwerte können Sie die Abfragelatenz in Cloud Monitoring visualisieren.

Das folgende Beispiel zeigt ein Diagramm, in dem die Verteilungsaggregation für den GFE-Latenzmesswert veranschaulicht wird. Verwenden Sie das Menü Aggregator, um die Perzentillatenz auf das 5., 50., 95. oder 99. Perzentil zu ändern.

Das Programm erstellt eine OpenCensus-Ansicht mit dem Namen query_stats_elapsed. Dieser String wird beim Export nach Cloud Monitoring Teil des Messwertnamens.

Cloud Monitoring-Abfragelatenz

Nächste Schritte