Solución de problemas con el uso del SDK de Azure Cosmos DB para Java v4 con cuentas de la API para NoSQL

SE APLICA A: NoSQL

Importante

Este artículo trata solo sobre la solución de problemas con el SDK de Azure Cosmos DB para Java v4. Consulte las notas de la versión del SDK de Azure Cosmos DB para Java v4, el repositorio de Maven y las sugerencias de rendimiento para más información. Si en la actualidad usa una versión anterior a la v4, vea la guía Migración a la versión 4 del SDK de Java de Azure Cosmos DB para obtener ayuda para actualizar a v4.

En este artículo se tratan problemas comunes, soluciones alternativas, pasos de diagnóstico y herramientas al usar el SDK de Azure Cosmos DB para Java v4 con cuentas de Azure Cosmos DB for NoSQL. El SDK de Azure Cosmos DB para Java v4 proporciona la representación lógica del lado cliente para acceder a Azure Cosmos DB for NoSQL. En este artículo se describen herramientas y enfoques para ayudarle si surge algún problema.

Comience con esta lista:

  • Eche un vistazo a la sección Problemas comunes y soluciones alternativas de este artículo.
  • Consulte el SDK para Java en el repositorio central de Azure Cosmos DB, que está disponible como código abierto en GitHub. Tiene una sección de problemas que se supervisa activamente. Compruebe si encuentra algún problema similar con una solución alternativa ya registrada. Una sugerencia útil es filtrar los problemas por la etiqueta *cosmos:v4-item*.
  • Revise las sugerencias de rendimiento del SDK de Azure Cosmos DB para Java v4 y siga los procedimientos sugeridos.
  • Lea el resto de este artículo y, si no encuentra una solución, registre un problema de GitHub. Si hay una opción para agregar etiquetas al problema de GitHub, agregue la etiqueta *cosmos:v4-item*.

Captura de los diagnósticos

Las respuestas de base de datos, contenedor, elemento y consulta del SDK de Java V4 tienen una propiedad Diagnostics. Esta propiedad registra toda la información relacionada con la solicitud única, incluidos los reintentos o errores transitorios.

Los diagnósticos se devuelven como una cadena. La cadena cambia con cada versión a medida que se mejora para ofrecer mejores soluciones a los problemas de los distintos escenarios. Con cada versión del SDK, la cadena podría interrumpir su formato. No analice la cadena para evitar cambios importantes.

En el siguiente ejemplo de código se muestra cómo leer los registros de diagnóstico mediante el SDK de Java V4:

Importante

Se recomienda validar la versión mínima recomendada del SDK de Java V4 y asegurarse de que se usa esta versión o superior. Puede comprobar la versión recomendada aquí.

Operaciones de base de datos

CosmosDatabaseResponse databaseResponse = client.createDatabaseIfNotExists(databaseName);
CosmosDiagnostics diagnostics = databaseResponse.getDiagnostics();
logger.info("Create database diagnostics : {}", diagnostics); 

Operaciones de contenedor

CosmosContainerResponse containerResponse = database.createContainerIfNotExists(containerProperties,
                  throughputProperties);
CosmosDiagnostics diagnostics = containerResponse.getDiagnostics();
logger.info("Create container diagnostics : {}", diagnostics);

Operaciones de elemento

// Write Item
CosmosItemResponse<Family> item = container.createItem(family, new PartitionKey(family.getLastName()),
                    new CosmosItemRequestOptions());
        
CosmosDiagnostics diagnostics = item.getDiagnostics();
logger.info("Create item diagnostics : {}", diagnostics);
        
// Read Item
CosmosItemResponse<Family> familyCosmosItemResponse = container.readItem(documentId,
                    new PartitionKey(documentLastName), Family.class);
        
CosmosDiagnostics diagnostics = familyCosmosItemResponse.getDiagnostics();
logger.info("Read item diagnostics : {}", diagnostics);

Operaciones de consulta

String sql = "SELECT * FROM c WHERE c.lastName = 'Witherspoon'";
        
CosmosPagedIterable<Family> filteredFamilies = container.queryItems(sql, new CosmosQueryRequestOptions(),
                    Family.class);
        
//  Add handler to capture diagnostics
filteredFamilies = filteredFamilies.handle(familyFeedResponse -> {
    logger.info("Query Item diagnostics through handle : {}", 
    familyFeedResponse.getCosmosDiagnostics());
});
        
//  Or capture diagnostics through iterableByPage() APIs.
filteredFamilies.iterableByPage().forEach(familyFeedResponse -> {
    logger.info("Query item diagnostics through iterableByPage : {}",
    familyFeedResponse.getCosmosDiagnostics());
});

Excepciones de Azure Cosmos DB

try {
  CosmosItemResponse<Family> familyCosmosItemResponse = container.readItem(documentId,
                    new PartitionKey(documentLastName), Family.class);
} catch (CosmosException ex) {
  CosmosDiagnostics diagnostics = ex.getDiagnostics();
  logger.error("Read item failure diagnostics : {}", diagnostics);
}

Registro de diagnósticos

Las versiones del SDK de Java V4 para v4.43.0 y versiones posteriores admiten el registro automático de diagnósticos de Cosmos para todas las solicitudes o errores si cumplen determinados criterios. Los desarrolladores de aplicaciones pueden definir umbrales para la latencia. Se puede hacer para las operaciones que son de punto (creación, lectura, reemplazo, actualizar/insertar (upsert), revisión) y las que no son de punto (consulta, fuente de cambios, masiva y por lotes), así como la carga de solicitudes y el tamaño de carga. Si las solicitudes superan estos umbrales definidos, los diagnósticos de Cosmos para esas solicitudes se emitirán automáticamente.

De forma predeterminada, el SDK de Java v4 registra estos diagnósticos automáticamente en un formato específico. Sin embargo, esto se puede cambiar si se implementa la interfaz CosmosDiagnosticsHandler y proporciona su propio controlador de diagnóstico personalizado.

Entonces, se pueden usar CosmosDiagnosticsThresholds y CosmosDiagnosticsHandler en un objeto CosmosClientTelemetryConfig, que se debe pasar a CosmosClientBuilder, mientras se crea un cliente sincrónico o asincrónico.

NOTA: Estos umbrales de diagnóstico se aplican en distintos tipos de diagnósticos, incluidos el registro, el seguimiento y la telemetría del cliente.

Los ejemplos de código siguientes muestran cómo definir umbrales de diagnóstico y un registrador de diagnóstico personalizado, y cómo usarlos a través de la configuración de telemetría de cliente:

Definición de umbrales de diagnóstico personalizados

//  Create diagnostics threshold
CosmosDiagnosticsThresholds cosmosDiagnosticsThresholds = new CosmosDiagnosticsThresholds();
//  These thresholds are for demo purposes
//  NOTE: Do not use the same thresholds for production
cosmosDiagnosticsThresholds.setPayloadSizeThreshold(100_00);
cosmosDiagnosticsThresholds.setPointOperationLatencyThreshold(Duration.ofSeconds(1));
cosmosDiagnosticsThresholds.setNonPointOperationLatencyThreshold(Duration.ofSeconds(5));
cosmosDiagnosticsThresholds.setRequestChargeThreshold(100f);

Definición del controlador de diagnóstico personalizado

//  By default, DEFAULT_LOGGING_HANDLER can be used
CosmosDiagnosticsHandler cosmosDiagnosticsHandler = CosmosDiagnosticsHandler.DEFAULT_LOGGING_HANDLER;

//  App developers can also define their own diagnostics handler
cosmosDiagnosticsHandler = new CosmosDiagnosticsHandler() {
    @Override
    public void handleDiagnostics(CosmosDiagnosticsContext diagnosticsContext, Context traceContext) {
        logger.info("This is custom diagnostics handler: {}", diagnosticsContext.toJson());
    }
};

Definición de CosmosClientTelemetryConfig

//  Create Client Telemetry Config
CosmosClientTelemetryConfig cosmosClientTelemetryConfig =
    new CosmosClientTelemetryConfig();
cosmosClientTelemetryConfig.diagnosticsHandler(cosmosDiagnosticsHandler);
cosmosClientTelemetryConfig.diagnosticsThresholds(cosmosDiagnosticsThresholds);

//  Create sync client
CosmosClient client = new CosmosClientBuilder()
    .endpoint(AccountSettings.HOST)
    .key(AccountSettings.MASTER_KEY)
    .clientTelemetryConfig(cosmosClientTelemetryConfig)
    .buildClient();

Diseño de reintentos

Consulte nuestra guía para diseñar aplicaciones resistentes con los SDK de Azure Cosmos DB para instrucciones sobre cómo diseñar aplicaciones resistentes y saber cuál es la semántica de reintentos del SDK.

Problemas comunes y soluciones alternativas

Comprobación de las métricas del portal

La comprobación de las métricas del portal le ayudarán a determinar si hay un problema por parte del cliente o si hay un problema con el servicio. Por ejemplo, si las métricas contienen una alta tasa de solicitudes de velocidad limitada (código de estado HTTP 429), lo que significa que la solicitud se ha limitado, consulte la sección Tasa de solicitudes demasiado grande.

Problemas de red, error de tiempo de espera de lectura de Netty, rendimiento bajo, latencia alta

Sugerencias generales

Para obtener el mejor rendimiento:

  • Asegúrese de que la aplicación se está ejecutando en la misma región que la cuenta de Azure Cosmos DB.
  • Compruebe el uso de la CPU en el host donde se ejecuta la aplicación. Si el uso de CPU es de un 50 por ciento o más, ejecute la aplicación en un host con una configuración mayor. O bien, distribuya la carga en más máquinas.

Limitación de la conexión

La limitación de la conexión puede deberse a un Límite de conexiones en una máquina host o al agotamiento de puertos SNAT (PAT) de Azure.

Límite de conexiones en una máquina host

Algunos sistemas Linux, como Red Hat, tienen un límite máximo para el número total de archivos abiertos. En Linux, los sockets se implementan como archivos, por lo que este número también limita el número total de conexiones. Ejecute el siguiente comando:

ulimit -a

El número máximo permitido de archivos abiertos, que se identifican como "nofile", debe al menos doblar el tamaño del grupo de conexiones. Para más información, consulte Sugerencias de rendimiento del SDK de Azure Cosmos DB para Java v4.

Agotamiento de puertos SNAT (PAT) de Azure

Si la aplicación está implementada en Azure Virtual Machines sin una dirección IP pública, los puertos SNAT de Azure se usan de manera predeterminada para establecer conexiones con cualquier punto de conexión fuera de la máquina virtual. El número de conexiones permitidas desde la máquina virtual hasta el punto de conexión de Azure Cosmos DB está limitado por la configuración de Azure SNAT.

Los puertos SNAT de Azure se usan solo cuando la máquina virtual tiene una dirección IP privada y un proceso de la máquina virtual intenta conectarse con una dirección IP pública. Hay dos soluciones alternativas para evitar la limitación de Azure SNAT:

  • Agregue el punto de conexión de servicio de Azure Cosmos DB a la subred de la red virtual de Azure Virtual Machines. Para obtener más información, consulte puntos de conexión de servicio de red virtual de Azure.

    Cuando se habilita el punto de conexión de servicio, las solicitudes ya no se envían desde una dirección IP pública a Azure Cosmos DB. En su lugar, se envían la red virtual y la identidad de la subred. Este cambio puede producir caídas de firewall si solo se permiten direcciones IP públicas. Si usa un firewall, cuando se habilite el punto de conexión de servicio, agregue una subred al firewall mediante las ACL de Virtual Network.

  • Asignar una dirección IP pública a la máquina virtual de Azure.

No se puede conectar con el servicio: firewall

ConnectTimeoutException indica que el SDK no puede conectar con el servicio. Es posible que obtenga un error similar al siguiente cuando use el modo directo:

GoneException{error=null, resourceAddress='https://cdb-ms-prod-westus-fd4.documents.azure.com:14940/apps/e41242a5-2d71-5acb-2e00-5e5f744b12de/services/d8aa21a5-340b-21d4-b1a2-4a5333e7ed8a/partitions/ed028254-b613-4c2a-bf3c-14bd5eb64500/replicas/131298754052060051p//', statusCode=410, message=Message: The requested resource is no longer available at the server., getCauseInfo=[class: class io.netty.channel.ConnectTimeoutException, message: connection timed out: cdb-ms-prod-westus-fd4.documents.azure.com/101.13.12.5:14940]

Si tiene un firewall que se ejecuta en el equipo de la aplicación, abra el intervalo de puertos de 10 000 a 20 000, que son los que usa el modo directo. Consulte también las indicaciones de Límite de conexiones en una máquina host.

UnknownHostException

UnknownHostException significa que el marco de Java no puede resolver la entrada DNS para el punto de conexión de Azure Cosmos DB en la máquina afectada. Debe comprobar que la máquina pueda resolver la entrada DNS o, si tiene algún software de resolución DNS personalizado (como VPN o Proxy, o una solución personalizada), asegúrese de que contiene la configuración correcta para el punto de conexión DNS que el error reclama que no se puede resolver. Si el error es constante, puede comprobar la resolución DNS de la máquina mediante un comando curl al punto de conexión descrito en el error.

Proxy HTTP

Si usa un Proxy HTTP, asegúrese de que pueda admitir el número de conexiones configuradas en el SDK de ConnectionPolicy. En caso contrario, se encontrará con problemas de conexión.

Patrón de codificación no válido: bloqueo del subproceso de E/S de Netty

El SDK usa la biblioteca de E/S de Netty para comunicarse con Azure Cosmos DB. El SDK tiene una API asincrónica y usa las API de E/S de Netty sin bloqueo. El trabajo de E/S del SDK se realiza en subprocesos de E/S de Netty. El número de subprocesos de E/S de Netty está configurado para ser el mismo que el número de núcleos de CPU de la máquina de la aplicación.

Los subprocesos de E/S de Netty solo están diseñados para usarse con trabajos de E/S de Netty sin bloqueos. El SDK devuelve el resultado de la invocación de la API en uno de los subprocesos de E/S de Netty al código de la aplicación. Si la aplicación realiza una operación de larga duración después de recibir los resultados en el subproceso de Netty, puede que el SDK no tenga suficientes subprocesos de E/S para realizar su trabajo de E/S interno. Este tipo de codificación de aplicación puede producir un rendimiento bajo, alta latencia y errores de io.netty.handler.timeout.ReadTimeoutException. La solución consiste en cambiar el subproceso cuando se sabe que la operación tarda tiempo.

Por ejemplo, eche un vistazo al siguiente fragmento de código, que agrega elementos a un contenedor (mire aquí para obtener instrucciones sobre cómo configurar la base de datos y el contenedor). Puede realizar un trabajo de larga duración que tarda más de unos milisegundos en el subproceso de Netty. En ese caso, podrá entrar en un estado donde ningún subproceso de E/S de Netty está presente para procesar el trabajo de E/S. Como resultado, recibirá un error ReadTimeoutException.

API asincrónica del SDK para Java V4 (Maven com.azure::azure-cosmos)


//Bad code with read timeout exception

int requestTimeoutInSeconds = 10;

/* ... */

AtomicInteger failureCount = new AtomicInteger();
// Max number of concurrent item inserts is # CPU cores + 1
Flux<Family> familyPub =
        Flux.just(Families.getAndersenFamilyItem(), Families.getAndersenFamilyItem(), Families.getJohnsonFamilyItem());
familyPub.flatMap(family -> {
    return container.createItem(family);
}).flatMap(r -> {
    try {
        // Time-consuming work is, for example,
        // writing to a file, computationally heavy work, or just sleep.
        // Basically, it's anything that takes more than a few milliseconds.
        // Doing such operations on the IO Netty thread
        // without a proper scheduler will cause problems.
        // The subscriber will get a ReadTimeoutException failure.
        TimeUnit.SECONDS.sleep(2 * requestTimeoutInSeconds);
    } catch (Exception e) {
    }
    return Mono.empty();
}).doOnError(Exception.class, exception -> {
    failureCount.incrementAndGet();
}).blockLast();
assert(failureCount.get() > 0);

La solución consiste en cambiar el subproceso en el que realiza el trabajo que lleva tiempo. Defina una instancia singleton del programador para la aplicación.

API asincrónica del SDK para Java V4 (Maven com.azure::azure-cosmos)

// Have a singleton instance of an executor and a scheduler.
ExecutorService ex  = Executors.newFixedThreadPool(30);
Scheduler customScheduler = Schedulers.fromExecutor(ex);

Es posible que necesite realizar trabajo que lleva tiempo, por ejemplo, trabajo computacionalmente intensivo o E/S de bloqueo. En este caso, cambie el subproceso a un trabajador proporcionado por el objeto customScheduler con la API .publishOn(customScheduler).

API asincrónica del SDK para Java V4 (Maven com.azure::azure-cosmos)

container.createItem(family)
        .publishOn(customScheduler) // Switches the thread.
        .subscribe(
                // ...
        );

Mediante el uso de publishOn(customScheduler), se libera el subproceso de E/S de Netty y se cambia a su propio subproceso personalizado proporcionado por el programador personalizado. Esta modificación resuelve el problema. No volverá a recibir un error io.netty.handler.timeout.ReadTimeoutException.

Tasa de solicitudes demasiado grande

Se trata de un error de servidor. Indica que consumió el rendimiento aprovisionado. Vuelva a intentarlo más tarde. Si recibe este error con frecuencia, considere la posibilidad de aumentar el rendimiento de la colección.

  • Implementación del retroceso según intervalos de getRetryAfterInMilliseconds

    Durante las pruebas de rendimiento, debe aumentar la carga hasta que se limite una tasa de solicitudes pequeña. Si se limita, la aplicación cliente debe retroceder de acuerdo con el intervalo de reintento que el servidor especificó. Respetar el retroceso garantiza que dedica una cantidad de tiempo mínima de espera entre reintentos.

Control de errores desde la cadena reactiva del SDK para Java

El control de errores desde el SDK para Java de Azure Cosmos DB es importante en lo que respecta a la lógica de aplicación del cliente. Hay diferentes mecanismos de control de errores proporcionados por el marco de trabajo de núcleo de Reactor, que se pueden usar en distintos escenarios. Se recomienda a los clientes que conozcan en detalle estos operadores de control de errores y usen los que mejor se ajusten a sus escenarios de lógica de reintento.

Importante

No se recomienda usar el operador onErrorContinue(), ya que no se admite en todos los escenarios. Tenga presente que onErrorContinue() es un operador especializado que puede hacer que el comportamiento de su cadena reactiva sea poco claro. Funciona en operadores ascendentes, no descendentes, requiere compatibilidad específica con el operador para funcionar, y el ámbito puede propagarse fácilmente en sentido ascendente en código de biblioteca que no lo haya anticipado (lo que da lugar a un comportamiento no deseado). Consulte la documentación de onErrorContinue() para obtener más información sobre este operador especial.

Error al conectarse al emulador de Azure Cosmos DB

El certificado HTTPS del emulador de Azure Cosmos DB es un certificado autofirmado. Para que SDK funcione con el emulador, importe el certificado del emulador a Java TrustStore. Para más información, consulte Exportación de los certificados del emulador de Azure Cosmos DB.

Problemas de conflictos de dependencias

El SDK de Azure Cosmos DB para Java extrae varias dependencias; en general, si el árbol de dependencias del proyecto incluye una versión anterior de un artefacto del que depende el SDK de Azure Cosmos DB para Java, esto puede provocar errores inesperados al ejecutar la aplicación. Si va a depurar el motivo por el que la aplicación inicia inesperadamente una excepción, es una buena idea comprobar que el árbol de dependencias no extrae accidentalmente una versión anterior de una o varias de las dependencias del SDK de Azure Cosmos DB para Java.

La solución alternativa para este problema es identificar qué dependencias del proyecto vienen de la versión anterior, excluir la dependencia transitiva de esa versión anterior y permitir que el SDK de Azure Cosmos DB para Java incorpore la versión más reciente.

Para identificar qué dependencias del proyecto vienen de una versión anterior de algo de lo que depende el SDK de Azure Cosmos DB para Java, ejecute el siguiente comando en el archivo pom.xml del proyecto:

mvn dependency:tree

Para obtener más información, vea la guía sobre el árbol de dependencia de Maven.

Una vez que sepa qué dependencia del proyecto depende de una versión anterior, puede modificar la dependencia en esa biblioteca en el archivo pom y excluir la dependencia transitiva, según el ejemplo siguiente (que presupone que reactor-core es la dependencia obsoleta):

<dependency>
  <groupId>${groupid-of-lib-which-brings-in-reactor}</groupId>
  <artifactId>${artifactId-of-lib-which-brings-in-reactor}</artifactId>
  <version>${version-of-lib-which-brings-in-reactor}</version>
  <exclusions>
    <exclusion>
      <groupId>io.projectreactor</groupId>
      <artifactId>reactor-core</artifactId>
    </exclusion>
  </exclusions>
</dependency>

Para obtener más información, vea la guía sobre cómo excluir dependencias transitivas.

Habilitar el registro del SDK de cliente

El SDK de Azure Cosmos DB para Java v4 usa SLF4j como fachada de registro, que admite el registro en plataformas de registro populares como log4j y logback.

Por ejemplo, si quiere usar log4j como plataforma de registro, agregue las siguientes bibliotecas en la classpath de Java.

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-log4j12</artifactId>
  <version>${slf4j.version}</version>
</dependency>
<dependency>
  <groupId>log4j</groupId>
  <artifactId>log4j</artifactId>
  <version>${log4j.version}</version>
</dependency>

Agregue también una configuración de log4j.

# this is a sample log4j configuration

# Set root logger level to INFO and its only appender to A1.
log4j.rootLogger=INFO, A1

log4j.category.com.azure.cosmos=INFO
#log4j.category.io.netty=OFF
#log4j.category.io.projectreactor=OFF
# A1 is set to be a ConsoleAppender.
log4j.appender.A1=org.apache.log4j.ConsoleAppender

# A1 uses PatternLayout.
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%d %5X{pid} [%t] %-5p %c - %m%n

Para obtener más información, revise el registro manual de sfl4j.

Estadísticas de red de SO

Ejecute el comando netstat para hacerse una idea de cuántas conexiones se encuentran en el estado ESTABLISHED y en el estado CLOSE_WAIT.

En Linux, puede ejecutar el comando siguiente.

netstat -nap

En Windows, puede ejecutar el mismo comando con diferentes marcas de argumento:

netstat -abn

Filtrar el resultado para obtener solo las conexiones al punto de conexión de Azure Cosmos DB.

El número de conexiones al punto de conexión de Azure Cosmos DB en el estado ESTABLISHED no puede ser mayor que el tamaño del grupo de conexiones configurado.

Muchas de las conexiones al punto de conexión de Azure Cosmos DB podrían estar en el estado CLOSE_WAIT. Podría haber más de 1000. Un número tan alto como este indica que las conexiones se establecen y se cancelan rápidamente. Esta situación puede causar problemas. Para obtener más información, revise la sección Problemas comunes y soluciones alternativas.

Problemas de consulta comunes

Las métricas de consulta le ayudarán a determinar dónde está dedicando más tiempo la consulta. En las métricas de consulta, puede ver la cantidad que se dedica al back-end en comparación con el cliente. Obtenga más información en la guía de rendimiento de consultas.

Pasos siguientes

  • Aprenda sobre las instrucciones de rendimiento de Java SDK v4
  • Obtenga información sobre los procedimientos recomendados para Java SDK v4