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

Neste tópico, descrevemos como examinar um componente do Spanner para encontrar a fonte da latência e visualizar essa latência usando o OpenTelemetry. Para ter 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 kit de ferramentas e framework de observabilidade de código aberto que permite criar e gerenciar dados de telemetria, como traces, métricas e registros. Esse é 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 em Identificar o ponto de latência para encontrar os componentes ou componentes que estão 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. Configure o SDK do OpenTelemetry com as opções apropriadas para exportar seus dados de telemetria. Há várias opções de exportador do OpenTelemetry disponíveis. Recomendamos usar o exportador do protocolo OpenTelemetry (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, poderá usar 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 as seguintes 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.23.1
go.opentelemetry.io/otel/sdk/metric v1.23.1

Injetar o objeto do OpenTelemetry

Em seguida, crie um objeto do OpenTelemetry com o exportador do 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 tempo (em milissegundos) entre o primeiro byte da solicitação da API Spanner que o cliente envia ao banco de dados (por meio do GFE e do front-end da API Spanner) e o último byte de 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 não é compatível com o OpenTelemetry. É possível instrumentar a métrica usando o OpenCensus com uma 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.

Veja um exemplo de gráfico que ilustra a latência do 5o percentil da métrica de latência de ida e volta do cliente. Para alterar a latência do percentil para o 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 ela é 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 o momento em que 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 opções a seguir 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.

Veja um exemplo de gráfico que ilustra a agregação de distribuição da 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 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 de solicitação da API Spanner é o tempo (em segundos) entre o primeiro byte da solicitação que o front-end da API Spanner recebe e o último byte de resposta que ele 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

É possível usar a ferramenta de gráficos do Metrics Explorer para visualizar o gráfico da métrica spanner.googleapis.com/api/request_latencies no Cloud Monitoring.

Veja um exemplo de gráfico que ilustra a latência do 5o percentil da métrica de latência de solicitações da API Spanner. Se quiser mudar a latência do percentil para o 50o ou 99o percentil, use o menu Agregador.

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

Capturar e visualizar a latência da consulta

A latência da consulta é o tempo (em milissegundos) necessário para executar consultas SQL 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.

Veja um exemplo de gráfico que ilustra a agregação de distribuição da 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 de consulta do Cloud Monitoring.

A seguir