Analisar a latência em um componente do Spanner com o OpenTelemetry

Este tópico descreve como examinar um componente do Spanner para encontrar a origem da latência e visualizar essa latência usando o OpenTelemetry. Para uma visão geral de alto nível dos componentes neste tópico, consulte Pontos de latência em uma solicitação do Spanner.

O OpenTelemetry é um framework e um kit de ferramentas de observabilidade de código aberto que permite criar e gerenciar dados de telemetria, como traces, métricas e registros. Ele é o resultado de uma fusão entre o OpenTracing e o OpenCensus. Para mais informações, consulte O que é o OpenTelemetry?

As bibliotecas de cliente do Spanner fornecem métricas e traces com o uso do framework de observabilidade do OpenTelemetry. Siga o procedimento Identifique o ponto de latência para encontrar as ou componentes que estejam mostrando latência no Spanner.

Antes de começar

Antes de começar a capturar métricas de latência, familiarize-se com a instrumentação manual com o OpenTelemetry. É necessário configurar o SDK do OpenTelemetry com as opções adequadas para exportar seus dados de telemetria. Há várias opções de exportador do OpenTelemetry disponíveis. Recomendamos usar o OpenTelemetry Exportador de protocolo (OTLP). Outras opções incluem o uso de um coletor OTel com o Google Cloud Exporter ou o Google Managed Service para Prometheus Exporter.

Se você estiver executando seu aplicativo no Compute Engine, use o Agente de operações para coletar métricas e traces do protocolo do OpenTelemetry. Para mais informações, consulte Coletar métricas e traces do OTLP.

Adicionar dependências

Para configurar o SDK do OpenTelemetry e o exportador do OTLP, adicione o seguinte: dependências ao aplicativo.

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

Injetar o objeto do OpenTelemetry

Em seguida, crie um objeto do OpenTelemetry com o exportador OTLP e injete o objeto do OpenTelemetry usando 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"),
		))
}

Capturar e visualizar a latência de ida e volta do cliente

A latência de ida e volta do cliente é o período (em milissegundos) entre o primeiro byte da solicitação da API Spanner que o cliente envia para o banco de dados (pelo GFE e pelo front-end da API Spanner) e o último byte da resposta que o cliente recebe do banco de dados.

Capturar a latência de ida e volta do cliente

A métrica de latência de ida e volta do cliente do Spanner está indisponível usando o OpenTelemetry. É possível instrumentar a métrica usando o OpenCensus com um ponte e migrar os dados para o OpenTelemetry.

Visualizar a latência de ida e volta do cliente

Depois de recuperar as métricas, é possível visualizar a latência de ida e volta do cliente no Cloud Monitoring.

Aqui está um exemplo de gráfico que ilustra a latência do 5o percentil para o métrica de latência de ida e volta do cliente. Para alterar a latência do percentil para no 50o ou 99o percentil, use o menu Agregador.

O programa cria uma visualização chamada roundtrip_latency. Essa string se torna parte do nome da métrica quando é exportada para o Cloud Monitoring.

Latência de ida e volta do cliente do Cloud Monitoring.

Capturar e visualizar a latência do GFE

A latência do Google Front End (GFE) é o período (em milissegundos) entre quando a rede do Google recebe uma chamada de procedimento remoto do cliente e quando o GFE recebe o primeiro byte da resposta.

Capturar a latência do GFE

É possível capturar métricas de latência do GFE ativando as seguintes opções usando a biblioteca de cliente do 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
		}
	}
}

Visualizar a latência do GFE

Depois de recuperar as métricas, é possível visualizar a latência do GFE no Cloud Monitoring:

Confira um exemplo de um gráfico que ilustra a agregação de distribuição da métrica de latência do GFE. Para mudar o percentil de latência para o quinto, 50o, 95o ou 99o percentil, use o menu Agregador.

O programa cria uma visualização chamada spanner/gfe_latency. Essa string se torna parte do nome da métrica quando é exportada para o Cloud Monitoring.

Latência do GFE do Cloud Monitoring.

Capture e visualize a latência da solicitação da API Spanner

A latência da solicitação da API Spanner é o período (em segundos) entre o primeiro byte da solicitação que o front-end da API Spanner recebe e o último byte da resposta que o front-end da API Spanner envia.

Capturar a latência de solicitação da API Spanner

Por padrão, essa latência está disponível como parte das métricas do Cloud Monitoring. Você não precisa fazer nada para capturar e exportar essas informações.

Conferir a latência da solicitação da API Spanner

Use o Metrics Explorer ferramenta de gráficos para visualizar o gráfico spanner.googleapis.com/api/request_latencies no Cloud Monitoring.

Aqui está um exemplo de gráfico que ilustra a latência do 5o percentil para o Métrica de latência de solicitação da API Spanner. Para mudar a latência do percentil para o 50o ou o 99o percentil, use o menu Agregador.

Latência de solicitação da API Cloud Monitoring.

Capturar e visualizar a latência da consulta

Latência de consulta é o tempo (em milissegundos) necessário para executar um SQL consultas no banco de dados do Spanner.

Capturar latência da consulta

É possível capturar a latência da consulta nos seguintes idiomas:

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

Visualizar a latência da consulta

Depois de recuperar as métricas, é possível visualizar a latência da consulta no Cloud Monitoring.

Este é um exemplo de gráfico que ilustra a agregação de distribuição a métrica de latência do GFE. Para mudar a latência do percentil para o 5o, 50o, 95o ou 99o percentil, use o menu Agregador.

O programa cria uma visualização do OpenCensus chamada query_stats_elapsed. Essa string se torna parte do nome da métrica quando é exportada para o Cloud Monitoring.

Latência da consulta do Cloud Monitoring.

A seguir