Examina la latencia en un componente de Spanner con OpenCensus
Organiza tus páginas con colecciones
Guarda y categoriza el contenido según tus preferencias.
En este tema, se describe cómo examinar un componente de Spanner.
Para encontrar la fuente de la latencia y visualizarla con OpenCensus.
Para obtener una descripción general de los componentes de este tema, consulta
Puntos de latencia en una solicitud de Spanner.
Las bibliotecas cliente de Spanner proporcionan estadísticas y seguimientos con el
el uso del framework de observabilidad de OpenCensus. Este framework ofrece estadísticas
en los componentes internos del cliente y ayuda a solucionar problemas de extremo a extremo
(ida y vuelta). De forma predeterminada, el framework está inhabilitado.
Antes de comenzar
Usa el procedimiento que se describe en Identifica el punto de latencia.
para encontrar los componentes
que muestran latencia.
Captura y visualiza la latencia de ida y vuelta de los clientes
La latencia de ida y vuelta del cliente es el tiempo (en milisegundos) entre las
primer byte de la solicitud a la API de Spanner que el cliente envía al
a la base de datos (a través de Google Front End, GFE) y
frontend de la API de Spanner), y el último byte de respuesta que
cliente recibe de la base de datos.
Captura la latencia de ida y vuelta de los clientes
Puedes capturar la latencia de ida y vuelta del cliente para los siguientes lenguajes:
Java
static void captureGrpcMetric(DatabaseClient dbClient) {
// Add io.grpc:grpc-census and io.opencensus:opencensus-exporter-stats-stackdriver
// dependencies to enable gRPC metrics.
// Register basic gRPC views.
RpcViews.registerClientGrpcBasicViews();
// Enable OpenCensus exporters to export metrics to Stackdriver Monitoring.
// Exporters use Application Default Credentials to authenticate.
// See https://developers.google.com/identity/protocols/application-default-credentials
// for more details.
try {
StackdriverStatsExporter.createAndRegister();
} catch (IOException | IllegalStateException e) {
System.out.println("Error during StackdriverStatsExporter");
}
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));
}
}
}
Comienza a usarlo
import (
"context"
"fmt"
"io"
"regexp"
"cloud.google.com/go/spanner"
"google.golang.org/api/iterator"
"contrib.go.opencensus.io/exporter/stackdriver"
"go.opencensus.io/plugin/ocgrpc"
"go.opencensus.io/stats/view"
)
var validDatabasePattern = regexp.MustCompile("^projects/(?P<project>[^/]+)/instances/(?P<instance>[^/]+)/databases/(?P<database>[^/]+)$")
func queryWithGRPCMetric(w io.Writer, db string) error {
projectID, _, _, err := parseDatabaseName(db)
if err != nil {
return err
}
ctx := context.Background()
client, err := spanner.NewClient(ctx, db)
if err != nil {
return err
}
defer client.Close()
// Register OpenCensus views.
if err := view.Register(ocgrpc.DefaultClientViews...); err != nil {
return err
}
// Create OpenCensus Stackdriver exporter.
sd, err := stackdriver.NewExporter(stackdriver.Options{
ProjectID: projectID,
})
if err != nil {
return err
}
// It is imperative to invoke flush before your main function exits
defer sd.Flush()
// Start the metrics exporter
sd.StartMetricsExporter()
defer sd.StopMetricsExporter()
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
}
fmt.Fprintf(w, "%d %d %s\n", singerID, albumID, albumTitle)
}
}
func parseDatabaseName(databaseUri string) (project, instance, database string, err error) {
matches := validDatabasePattern.FindStringSubmatch(databaseUri)
if len(matches) == 0 {
return "", "", "", fmt.Errorf("failed to parse database name from %q according to pattern %q",
databaseUri, validDatabasePattern.String())
}
return matches[1], matches[2], matches[3], nil
}
Visualiza la latencia de ida y vuelta del cliente
Después de recuperar las métricas, puedes visualizar la latencia de ida y vuelta del cliente en
Cloud Monitoring
Este es un ejemplo de un gráfico en el que se ilustra la latencia del percentil 5 de la
métrica de latencia de ida y vuelta del cliente. Para cambiar la latencia percentil a
en el percentil 50 o 99, usa el menú Agregador.
El programa crea una vista de OpenCensus llamada roundtrip_latency.
Esta cadena pasa a formar parte del nombre de la métrica cuando se exporta a Cloud Monitoring.
Captura y visualiza la latencia de GFE
La latencia de Google Front End (GFE) es la cantidad de tiempo (en milisegundos) entre
cuando la red de Google recibe una llamada de procedimiento remoto del cliente y
cuando GFE recibe el primer byte de la respuesta.
Cómo capturar la latencia de GFE
Puedes capturar la latencia de GFE para los siguientes idiomas:
Java
static void captureGfeMetric(DatabaseClient dbClient) {
// Capture GFE Latency.
SpannerRpcViews.registerGfeLatencyView();
// Capture GFE Latency and GFE Header missing count.
// SpannerRpcViews.registerGfeLatencyAndHeaderMissingCountViews();
// Capture only GFE Header missing count.
// SpannerRpcViews.registerGfeHeaderMissingCountView();
// Enable OpenCensus exporters to export metrics to Stackdriver Monitoring.
// Exporters use Application Default Credentials to authenticate.
// See https://developers.google.com/identity/protocols/application-default-credentials
// for more details.
try {
StackdriverStatsExporter.createAndRegister();
} catch (IOException | IllegalStateException e) {
System.out.println("Error during StackdriverStatsExporter");
}
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
// We are in the process of adding support in the Cloud Spanner Go Client Library
// to capture the gfe_latency metric.
import (
"context"
"fmt"
"io"
"strconv"
"strings"
spanner "cloud.google.com/go/spanner/apiv1"
gax "github.com/googleapis/gax-go/v2"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"contrib.go.opencensus.io/exporter/stackdriver"
"go.opencensus.io/stats"
"go.opencensus.io/stats/view"
"go.opencensus.io/tag"
)
// OpenCensus Tag, Measure and View.
var (
KeyMethod = tag.MustNewKey("grpc_client_method")
GFELatencyMs = stats.Int64("cloud.google.com/go/spanner/gfe_latency",
"Latency between Google's network receives an RPC and reads back the first byte of the response", "ms")
GFELatencyView = view.View{
Name: "cloud.google.com/go/spanner/gfe_latency",
Measure: GFELatencyMs,
Description: "Latency between Google's network receives an RPC and reads back the first byte of the response",
Aggregation: view.Distribution(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),
TagKeys: []tag.Key{KeyMethod}}
)
func queryWithGFELatency(w io.Writer, db string) error {
projectID, _, _, err := parseDatabaseName(db)
if err != nil {
return err
}
ctx := context.Background()
client, err := spanner.NewClient(ctx)
if err != nil {
return err
}
defer client.Close()
// Register OpenCensus views.
err = view.Register(&GFELatencyView)
if err != nil {
return err
}
// Create OpenCensus Stackdriver exporter.
sd, err := stackdriver.NewExporter(stackdriver.Options{
ProjectID: projectID,
})
if err != nil {
return err
}
// It is imperative to invoke flush before your main function exits
defer sd.Flush()
// Start the metrics exporter
sd.StartMetricsExporter()
defer sd.StopMetricsExporter()
// Create a session.
req := &sppb.CreateSessionRequest{Database: db}
session, err := client.CreateSession(ctx, req)
if err != nil {
return err
}
// Execute a SQL query and retrieve the GFE server-timing header in gRPC metadata.
req2 := &sppb.ExecuteSqlRequest{
Session: session.Name,
Sql: `SELECT SingerId, AlbumId, AlbumTitle FROM Albums`,
}
var md metadata.MD
resultSet, err := client.ExecuteSql(ctx, req2, gax.WithGRPCOptions(grpc.Header(&md)))
if err != nil {
return err
}
for _, row := range resultSet.GetRows() {
for _, value := range row.GetValues() {
fmt.Fprintf(w, "%s ", value.GetStringValue())
}
fmt.Fprintf(w, "\n")
}
// The format is: "server-timing: gfet4t7; dur=[GFE latency in ms]"
srvTiming := md.Get("server-timing")[0]
gfeLtcy, err := strconv.Atoi(strings.TrimPrefix(srvTiming, "gfet4t7; dur="))
if err != nil {
return err
}
// Record GFE t4t7 latency with OpenCensus.
ctx, err = tag.New(ctx, tag.Insert(KeyMethod, "ExecuteSql"))
if err != nil {
return err
}
stats.Record(ctx, GFELatencyMs.M(int64(gfeLtcy)))
return nil
}
Visualiza la latencia de GFE
Después de recuperar las métricas, puedes visualizar la latencia de GFE en
Cloud Monitoring
Este es un ejemplo de un gráfico en el que se ilustra la latencia del percentil 5 de la
Métrica de latencia de GFE. Para cambiar la latencia del percentil al 50
Percentil 99, usa el menú Agregador.
El programa crea una vista de OpenCensus llamada gfe_latency. Esta
string pasa a formar parte del nombre de la métrica cuando se exporta a
Cloud Monitoring
Captura y visualiza la latencia de las solicitudes a la API de Spanner
La latencia de solicitud a la API de Spanner es la cantidad de tiempo (en segundos)
entre el primer byte de una solicitud que el frontend de la API de Spanner
recibe y el último byte de una respuesta que la API de Spanner
que envía el frontend.
Captura la latencia de solicitud a la API de Spanner
De forma predeterminada, esta latencia está disponible
Métricas de Cloud Monitoring.
No tienes que hacer nada para capturarlo y exportarlo.
Visualiza la latencia de la solicitud a la API de Spanner
Puedes usar el Explorador de métricas
una herramienta de gráficos para visualizar el gráfico del spanner.googleapis.com/api/request_latencies
en Cloud Monitoring.
Este es un ejemplo de un gráfico en el que se ilustra la latencia del percentil 5 de la
Métrica de latencia de solicitud a la API de Spanner. Para cambiar el percentil
hasta el percentil 50 o 99, usa el agregador.
.
Captura y visualiza la latencia de las consultas
La latencia de la consulta es el tiempo (en milisegundos) que se tarda en ejecutar SQL
en la base de datos de Spanner.
Captura la latencia de las consultas
Puedes capturar la latencia de las consultas en los siguientes lenguajes:
Java
private static final String MILLISECOND = "ms";
static final List<Double> RPC_MILLIS_BUCKET_BOUNDARIES =
Collections.unmodifiableList(
Arrays.asList(
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));
static final Aggregation AGGREGATION_WITH_MILLIS_HISTOGRAM =
Distribution.create(BucketBoundaries.create(RPC_MILLIS_BUCKET_BOUNDARIES));
static MeasureDouble QUERY_STATS_ELAPSED =
MeasureDouble.create(
"cloud.google.com/java/spanner/query_stats_elapsed",
"The execution of the query",
MILLISECOND);
// Register the view. It is imperative that this step exists,
// otherwise recorded metrics will be dropped and never exported.
static View QUERY_STATS_LATENCY_VIEW = View
.create(Name.create("cloud.google.com/java/spanner/query_stats_elapsed"),
"The execution of the query",
QUERY_STATS_ELAPSED,
AGGREGATION_WITH_MILLIS_HISTOGRAM,
Collections.emptyList());
static ViewManager manager = Stats.getViewManager();
private static final StatsRecorder STATS_RECORDER = Stats.getStatsRecorder();
static void captureQueryStatsMetric(DatabaseClient dbClient) {
manager.registerView(QUERY_STATS_LATENCY_VIEW);
// Enable OpenCensus exporters to export metrics to Cloud Monitoring.
// Exporters use Application Default Credentials to authenticate.
// See https://developers.google.com/identity/protocols/application-default-credentials
// for more details.
try {
StackdriverStatsExporter.createAndRegister();
} catch (IOException | IllegalStateException e) {
System.out.println("Error during StackdriverStatsExporter");
}
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));
}
Value value = resultSet.getStats().getQueryStats()
.getFieldsOrDefault("elapsed_time", Value.newBuilder().setStringValue("0 msecs").build());
double elapasedTime = Double.parseDouble(value.getStringValue().replaceAll(" msecs", ""));
STATS_RECORDER.newMeasureMap()
.put(QUERY_STATS_ELAPSED, elapasedTime)
.record();
}
}
Comienza a usarlo
import (
"context"
"fmt"
"io"
"strconv"
"strings"
"cloud.google.com/go/spanner"
"google.golang.org/api/iterator"
"contrib.go.opencensus.io/exporter/stackdriver"
"go.opencensus.io/stats"
"go.opencensus.io/stats/view"
"go.opencensus.io/tag"
)
// OpenCensus Tag, Measure and View.
var (
QueryStatsElapsed = stats.Float64("cloud.google.com/go/spanner/query_stats_elapsed",
"The execution of the query", "ms")
QueryStatsLatencyView = view.View{
Name: "cloud.google.com/go/spanner/query_stats_elapsed",
Measure: QueryStatsElapsed,
Description: "The execution of the query",
Aggregation: view.Distribution(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),
TagKeys: []tag.Key{}}
)
func queryWithQueryStats(w io.Writer, db string) error {
projectID, _, _, err := parseDatabaseName(db)
if err != nil {
return err
}
ctx := context.Background()
client, err := spanner.NewClient(ctx, db)
if err != nil {
return err
}
defer client.Close()
// Register OpenCensus views.
err = view.Register(&QueryStatsLatencyView)
if err != nil {
return err
}
// Create OpenCensus Stackdriver exporter.
sd, err := stackdriver.NewExporter(stackdriver.Options{
ProjectID: projectID,
})
if err != nil {
return err
}
// It is imperative to invoke flush before your main function exits
defer sd.Flush()
// Start the metrics exporter
sd.StartMetricsExporter()
defer sd.StopMetricsExporter()
// Execute a SQL query and get the query stats.
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 OpenCensus.
elapasedTime := iter.QueryStats["elapsed_time"].(string)
elapasedTimeMs, err := strconv.ParseFloat(strings.TrimSuffix(elapasedTime, " msecs"), 64)
if err != nil {
return err
}
stats.Record(ctx, QueryStatsElapsed.M(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
}
fmt.Fprintf(w, "%d %d %s\n", singerID, albumID, albumTitle)
}
}
Visualiza la latencia de las consultas
Después de recuperar las métricas, puedes visualizar la latencia de la consulta en
Cloud Monitoring
Este es un ejemplo de un gráfico en el que se ilustra la latencia del percentil 5 de la
de latencia de la consulta. Para cambiar la latencia del percentil al 50
en el percentil 99, usa el menú Agregador.
El programa crea una vista de OpenCensus llamada query_stats_elapsed.
Esta cadena pasa a formar parte del nombre de la métrica cuando se exporta a
Cloud Monitoring