Memeriksa latensi di komponen Spanner dengan OpenTelemetry

Topik ini menjelaskan cara memeriksa komponen Spanner untuk menemukan sumber latensi dan memvisualisasikan latensi tersebut menggunakan OpenTelemetry. Untuk ringkasan umum terkait komponen dalam topik ini, lihat Titik latensi dalam permintaan Spanner.

OpenTelemetry adalah toolkit dan framework kemampuan observasi open source yang memungkinkan Anda membuat dan mengelola data telemetri seperti trace, metrik, dan log. Ini adalah hasil dari penggabungan antara OpenTracing dan OpenCensus. Untuk mengetahui informasi selengkapnya, lihat Apa itu OpenTelemetry?

Library klien Spanner menyediakan metrik dan trace dengan menggunakan framework kemampuan observasi OpenTelemetry. Ikuti prosedur di Mengidentifikasi titik latensi untuk menemukan komponen atau komponen yang menampilkan latensi di Spanner.

Sebelum memulai

Sebelum mulai merekam metrik latensi, pahami instrumentasi manual dengan OpenTelemetry. Anda harus mengonfigurasi OpenTelemetry SDK dengan opsi yang sesuai untuk mengekspor data telemetri. Ada beberapa opsi pengekspor OpenTelemetry yang tersedia. Sebaiknya gunakan pengekspor OpenTelemetry Protocol (OTLP). Opsi lainnya termasuk menggunakan OTel Collector dengan Google Cloud Pengekspor atau Google Managed Service for Prometheus Exporter.

Jika menjalankan aplikasi di Compute Engine, Anda dapat menggunakan Agen Operasional untuk mengumpulkan metrik dan trace OpenTelemetry Protocol. Untuk mengetahui informasi selengkapnya, lihat artikel Mengumpulkan metrik dan trace OTLP.

Menambahkan dependensi

Untuk mengonfigurasi OpenTelemetry SDK dan pengekspor OTLP, tambahkan dependensi berikut ke aplikasi Anda.

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

Memasukkan objek OpenTelemetry

Kemudian, buat objek OpenTelemetry dengan pengekspor OTLP dan masukkan objek OpenTelemetry menggunakan 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"),
		))
}

Menangkap dan memvisualisasikan latensi bolak-balik klien

Latensi bolak-balik klien adalah durasi waktu (dalam milidetik) antara byte pertama permintaan Spanner API yang dikirim klien ke database (melalui frontend GFE dan Spanner API), dan byte respons terakhir yang diterima klien dari database.

Merekam latensi bolak-balik klien

Metrik latensi bolak-balik klien Spanner tidak didukung menggunakan OpenTelemetry. Anda dapat menginstrumentasikan metrik menggunakan OpenCensus dengan jembatan dan memigrasikan data ke OpenTelemetry.

Memvisualisasikan latensi bolak-balik klien

Setelah mengambil metrik, Anda dapat memvisualisasikan latensi bolak-balik klien di Cloud Monitoring.

Berikut ini contoh grafik yang menggambarkan latensi persentil ke-5 untuk metrik latensi bolak-balik klien. Untuk mengubah latensi persentil ke persentil ke-50 atau ke-99, gunakan menu Aggregator.

Program ini membuat tampilan yang disebut roundtrip_latency. String ini menjadi bagian dari nama metrik saat diekspor ke Cloud Monitoring.

Latensi bolak-balik klien Cloud Monitoring.

Mengambil dan memvisualisasikan latensi GFE

Latensi Google Front End (GFE) adalah durasi waktu (dalam milidetik) antara saat jaringan Google menerima remoteproc call dari klien dan saat GFE menerima byte pertama respons.

Mengambil latensi GFE

Anda dapat mencatat metrik latensi GFE dengan mengaktifkan opsi berikut menggunakan library klien 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
		}
	}
}

Memvisualisasikan latensi GFE

Setelah mengambil metrik, Anda dapat memvisualisasikan latensi GFE di Cloud Monitoring.

Berikut ini contoh grafik yang menggambarkan agregasi distribusi untuk metrik latensi GFE. Untuk mengubah latensi persentil ke persentil ke-5, ke-50, ke-95, atau ke-99, gunakan menu Agregator.

Program ini membuat tampilan yang disebut spanner/gfe_latency. String ini menjadi bagian dari nama metrik saat diekspor ke Cloud Monitoring.

Latensi GFE Cloud Monitoring.

Mengambil dan memvisualisasikan latensi permintaan Spanner API

Latensi permintaan Spanner API adalah durasi (dalam detik) antara byte permintaan pertama yang diterima frontend Spanner API dan byte respons terakhir yang dikirim oleh frontend Spanner API.

Mengambil latensi permintaan Spanner API

Secara default, latensi ini tersedia sebagai bagian dari metrik Cloud Monitoring. Anda tidak perlu melakukan apa pun untuk menangkap dan mengekspornya.

Memvisualisasikan latensi permintaan Spanner API

Anda dapat menggunakan alat diagram Metrics Explorer untuk memvisualisasikan grafik untuk metrik spanner.googleapis.com/api/request_latencies di Cloud Monitoring.

Berikut ini contoh grafik yang menggambarkan latensi persentil ke-5 untuk metrik latensi permintaan Spanner API. Untuk mengubah latensi persentil ke persentil ke-50 atau ke-99, gunakan menu Aggregator.

Latensi permintaan Cloud Monitoring API.

Menangkap dan memvisualisasikan latensi kueri

Latensi kueri adalah durasi waktu (dalam milidetik) yang diperlukan untuk menjalankan kueri SQL di database Spanner.

Menangkap latensi kueri

Anda dapat menangkap latensi kueri untuk bahasa berikut:

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

Memvisualisasikan latensi kueri

Setelah mengambil metrik, Anda dapat memvisualisasikan latensi kueri di Cloud Monitoring.

Berikut ini contoh grafik yang menggambarkan agregasi distribusi untuk metrik latensi GFE. Untuk mengubah latensi persentil ke persentil ke-5, ke-50, ke-95, atau ke-99, gunakan menu Agregator.

Program ini membuat tampilan OpenCensus yang disebut query_stats_elapsed. String ini menjadi bagian dari nama metrik saat diekspor ke Cloud Monitoring.

Latensi kueri Cloud Monitoring.

Langkah selanjutnya