Résoudre les problèmes liés à l’utilisation du kit SDK Java v4 Azure Cosmos DB avec des comptes d’API pour NoSQL

S’APPLIQUE À : NoSQL

Important

Cet article traite uniquement de la résolution des problèmes liés au kit SDK Java asynchrone v4 Azure Cosmos DB. Pour plus d’informations, consultez les Notes de publication, le Référentiel Maven et les Conseils sur les performances en lien avec le kit SDK Java asynchrone v4 Azure Cosmos DB. Si vous utilisez actuellement une version antérieure à v4, consultez le guide Migrer vers le Kit de développement logiciel (SDK) Java v4 Azure Cosmos DB pour être accompagné dans la mise à niveau vers v4.

Cet article traite des problèmes courants, solutions de contournement, étapes de diagnostic et outils associés à l’utilisation du kit SDK Java v4 Azure Cosmos DB avec des comptes Azure Cosmos DB for NoSQL. Le kit SDK Java v4 Azure Cosmos DB fournit la représentation logique côté client pour accéder à Azure Cosmos DB for NoSQL. Cet article décrit les outils et les approches qui peuvent vous aider si vous rencontrez des problèmes.

Commencez par cette liste :

  • Jetez un coup d’œil à la section Problèmes courants et solutions de contournement dans cet article.
  • Consultez le kit SDK Java dans le dépôt centralisé Azure Cosmos DB, disponible en open source sur GitHub. Il contient une section Problèmes qui est activement tenue à jour. Vérifiez si un problème similaire au vôtre dispose déjà d’une solution de contournement. Une astuce intéressante consiste à filtrer les problèmes par la balise *cosmos:v4-item*.
  • Consultez les conseils sur les performances pour le kit SDK Java v4 Azure Cosmos DB et suivez les pratiques suggérées.
  • Lisez le reste de cet article, si vous n’avez pas trouvé de solution. Ensuite, consignez un problème GitHub. S’il existe une option permettant d’ajouter des balises à votre problème GitHub, ajoutez une balise *cosmos:v4-item*.

Capturer les diagnostics

Les réponses de base de données, de conteneur, d’élément et de requête dans le SDK Java V4 ont une propriété de diagnostics. Cette propriété enregistre toutes les informations relatives à la requête unique, notamment en cas de nouvelles tentatives ou d’échecs temporaires.

Les diagnostics sont retournés sous forme de chaîne. La chaîne change avec chaque version, car elle est améliorée pour mieux résoudre les différents scénarios. Avec chaque version du Kit de développement logiciel (SDK), la chaîne peut interrompre son format. N’analysez pas la chaîne pour éviter les changements cassants.

L’exemple de code suivant montre comment lire les journaux de diagnostic à l’aide du SDK Java V4 :

Important

Nous vous recommandons de valider la version minimale recommandée du Kit de développement logiciel (SDK) Java V4 et de vous assurer que vous utilisez cette version ou une version ultérieure. Vous pouvez vérifier la version recommandée ici.

Opérations de base de données

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

Opérations sur le conteneur

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

Opérations d'élément

// 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);

Opérations de requête

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());
});

Azure Cosmos DB Exceptions

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

Journalisation des diagnostics

Les versions v4.43.0 et ultérieures du Kit de développement logiciel (SDK) Java V4 prennent en charge la journalisation automatique de Diagnostics de Cosmos pour toutes les requêtes ou erreurs si elles répondent à certains critères. Les développeurs d’applications peuvent définir des seuils de latence (pour les opérations de point (création, lecture, remplacement, upsert, patch) ou les opérations de non point (requête, flux de modification, bloc et traitement)), de frais de requête et de taille de charge utile. Si les requêtes dépassent ces seuils définis, les diagnostics de Cosmos pour ces demandes sont émis automatiquement.

Par défaut, le Kit de développement logiciel (SDK) Java v4 consigne automatiquement ces diagnostics dans un format spécifique. Toutefois, cela peut être modifié en implémentant l’interface CosmosDiagnosticsHandler et en fournissant votre propre gestionnaire de diagnostics personnalisé.

Les CosmosDiagnosticsThresholds et CosmosDiagnosticsHandler peuvent ensuite être utilisés dans l’objet CosmosClientTelemetryConfig, qui doit être transmis à CosmosClientBuilder lors de la création d’un client synchrone ou asynchrone.

REMARQUE : ces seuils de diagnostic sont appliqués entre différents types de diagnostics, notamment la journalisation, le suivi et la télémétrie du client.

Les exemples de code suivants montrent comment définir des seuils de diagnostics, un enregistreur d’événements de diagnostic personnalisé et comment les utiliser via la configuration de télémétrie du client :

Définition de seuils de diagnostics personnalisés

//  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);

Définition d’un gestionnaire de diagnostics personnalisé

//  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());
    }
};

Définition 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();

Conception de nouvelle tentative

Consultez notre guide pour concevoir des applications résilientes avec les kits de développement logiciel (SDK) Azure Cosmos DB pour obtenir des conseils sur la procédure de conception d’applications résilientes et pour découvrir les sémantiques de nouvelles tentatives du kit de développement logiciel (SDK).

Problèmes courants et solutions de contournement

Consulter les métriques du portail

Les métriques du portail vous aident à déterminer si un problème est lié au client ou au service. Par exemple, si les métriques contiennent un taux important de requêtes à débit limité (code d’état HTTP 429), ce qui signifie que la requête est limitée, voir la section Taux de requêtes trop élevé.

Problèmes réseau, échec du délai d’expiration de lecture Netty, débit faible, latence élevée

Recommandations d’ordre général

Pour un résultat optimal :

  • Vérifiez que l’application s’exécute dans la même région que votre compte Azure Cosmos DB.
  • Vérifiez l’utilisation du processeur sur l’ordinateur hôte où l’application est en cours d’exécution. Si l’utilisation du processeur est supérieure ou égale à 50 %, exécutez votre application sur un hôte disposant d’une configuration plus élevée. Vous pouvez aussi distribuer la charge sur plusieurs ordinateurs.

Limitation de la connexion

La limitation de la connexion peut se produire en raison d’une Limite de connexion sur un ordinateur hôte ou d’une insuffisance de ports Azure SNAT (PAT).

Limite de connexion sur un ordinateur hôte

Certains systèmes Linux, tels que Red Hat, ont une limite supérieure quant au nombre total de fichiers ouverts. Les sockets dans Linux étant implémentés en tant que fichiers, ce nombre limite aussi le nombre total de connexions. Exécutez la commande suivante :

ulimit -a

La quantité maximale de fichiers ouverts autorisée, qui sont identifiés comme « nofile », doit être au moins le double votre taille de pool de connexions. Pour plus d’informations, consultez les conseils sur les performances associés au kit SDK Java v4 Azure Cosmos DB.

Insuffisance de ports Azure SNAT (PAT)

Si votre application est déployée sur Machine virtuelle Azure sans adresse IP publique, par défaut les ports Azure SNAT établissent des connexions avec n’importe quel point de terminaison en dehors de votre machine virtuelle. Le nombre de connexions autorisées de la machine virtuelle au point de terminaison Azure Cosmos DB est limité par la configuration Azure SNAT.

Les ports Azure SNAT sont utilisés uniquement quand votre machine virtuelle a une adresse IP privée et qu’un processus à partir de la machine virtuelle tente de se connecter avec une adresse IP publique. Il existe deux solutions de contournement pour éviter la limitation Azure SNAT :

  • Ajoutez votre point de terminaison de service Azure Cosmos DB au sous-réseau de votre réseau virtuel Machines virtuelles Azure. Pour plus d’informations, consultez Points de terminaison de service de réseau virtuel.

    Quand le point de terminaison de service est activé, les requêtes ne sont plus envoyées d’une adresse IP publique à Azure Cosmos DB. Au lieu de cela, les identités du réseau virtuel et du sous-réseau sont envoyées. Cette modification peut entraîner des problèmes de pare-feu si seules les adresses IP publiques sont autorisées. Si vous utilisez un pare-feu, quand vous activez le point de terminaison de service, ajoutez un sous-réseau au pare-feu à l’aide de Listes de contrôle d’accès de réseau virtuel.

  • Assignez une adresse IP publique à votre machine virtuelle Azure.

Impossible d’atteindre le service - pare-feu

ConnectTimeoutException indique que le Kit de développement logiciel (SDK) ne peut pas atteindre le service. Vous pouvez recevoir une erreur semblable à ce qui suit lorsque vous utilisez le mode direct :

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 un pare-feu est en cours d’exécution sur l’ordinateur hébergeant l’application, ouvrez la plage de ports de 10 000 à 20 000 (utilisée par le mode direct). Respectez également la limite de connexion sur un ordinateur hôte.

UnknownHostException

UnknownHostException signifie que le framework Java ne peut pas résoudre l’entrée DNS pour le point de terminaison Cosmos DB sur la machine affectée. Vous devez vérifier que la machine peut résoudre l’entrée DNS ou, si vous avez un logiciel de résolution de DNS personnalisé (par exemple un VPN ou un proxy, ou une solution personnalisée), vérifiez qu’il contient la configuration appropriée pour le point de terminaison DNS que l’erreur indique ne pas pouvoir résoudre. Si l’erreur est constante, vous pouvez vérifier la résolution DNS de la machine via une commande curl vers le point de terminaison décrit dans l’erreur.

Serveur proxy HTTP

Si vous utilisez un proxy HTTP, vérifiez qu’il peut prendre en charge le nombre de connexions configuré dans SDK ConnectionPolicy. Sinon, vous serez confronté à des problèmes de connexion.

Modèle de codage non valide : blocage du thread d’E/S Netty

Le SDK utilise la bibliothèque d’E/S Netty pour communiquer avec Azure Cosmos DB. Le kit SDK comprend une API asynchrone et utilise des API d’E/S non bloquantes fournies par Netty. Les tâches d’E/S du SDK sont effectuées sur les threads d’E/S de Netty. Le nombre de threads d’E/S de Netty est configuré pour être identique au nombre de cœurs de processeur de l’ordinateur de l’application.

Les threads d’E/S de Netty sont destinés à être utilisés uniquement pour les tâches d’E/S non bloquantes de Netty. Le SDK retourne le résultat d’appel de l’API sur l’un des threads d’E/S de Netty au code de l’application. Si l’application effectue une opération de longue durée après avoir reçu les résultats sur le thread Netty, le SDK risque de ne pas avoir suffisamment de threads d’E/S pour effectuer ses tâches d’E/S internes. Ce type de codage d’application peut entraîner un débit faible, une latence élevée et des échecs io.netty.handler.timeout.ReadTimeoutException. La solution de contournement consiste à basculer le thread quand vous savez que l’opération prendra de temps.

Par exemple, examinez l’extrait de code suivant qui ajoute des éléments à un conteneur (regardez ici pour obtenir des conseils sur la configuration de la base de données et du conteneur.) Il se peut que vous effectuiez un travail de longue durée qui prend plus de quelques millisecondes sur le thread Netty. Dans ce cas, vous risquez de passer dans un état où aucun thread d’E/S Netty n’est présent pour traiter les tâches d’E/S. Vous obtenez alors un échec ReadTimeoutException.

API Async du Kit de développement logiciel (SDK) 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 solution de contournement consiste à changer le thread sur lequel vous effectuez le travail qui prend du temps. Définissez une instance singleton du planificateur pour votre application.

API Async du Kit de développement logiciel (SDK) 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);

Vous devrez peut-être effectuer des tâches qui prennent du temps, par exemple des E/S bloquantes ou des tâches qui nécessitent beaucoup de ressources de calcul. Dans ce cas, basculez vers un thread de travail fourni par votre customScheduler à l’aide de l’API .publishOn(customScheduler).

API Async du Kit de développement logiciel (SDK) Java v4 (Maven com.azure::azure-cosmos)

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

En utilisant publishOn(customScheduler), vous libérez le thread d’E/S Netty et basculez vers votre propre thread personnalisé fourni par le planificateur personnalisé. Cette modification résout le problème. Vous n’obtiendrez plus d’échec io.netty.handler.timeout.ReadTimeoutException.

Taux de requêtes trop élevé

Cet échec est une erreur côté serveur. Il indique que vous avez consommé votre débit provisionné. Réessayez ultérieurement. Si vous obtenez souvent cet échec, augmentez le débit de la collection.

  • Implémentation de l’interruption à intervalles définis par getRetryAfterInMilliseconds

    Lors du test de performances, vous devez augmenter la charge jusqu’à une limite d’un petit nombre de requêtes. En cas de limitation, l’application client doit s’interrompre pour l’intervalle de nouvelle tentative spécifié sur le serveur. Le respect de l’interruption garantit un temps d’attente minimal entre chaque tentative.

Gestion des erreurs d’une chaîne réactive du SDK Java

La gestion des erreurs du SDK Java Azure Cosmos DB est importante quand il s’agit de la logique d’application du client. Il existe différents mécanismes de gestion des erreurs fournis par l’infrastructure reactor-core, qui peuvent être utilisés dans différents scénarios. Nous recommandons aux clients de comprendre ces opérateurs de gestion des erreurs en détail et d’utiliser ceux qui correspondent le mieux à leurs scénarios de logique de nouvelle tentative.

Important

Nous déconseillons d’utiliser l’opérateur onErrorContinue(), car celui-ci n’est pas pris en charge dans tous les scénarios. Notez que onErrorContinue() est un opérateur spécialisé susceptible de rendre peu clair le comportement de votre chaîne réactive. Il fonctionne sur les opérateurs en amont et non pas en aval, il nécessite une prise en charge spécifique de l’opérateur pour fonctionner, et l’étendue peut facilement se propager en amont dans le code de la bibliothèque qui ne l’anticipe pas (ce qui entraîne un comportement inattendu). Pour plus d’informations sur cet opérateur spécial, consultez la documentation de onErrorContinue().

Échec de connexion à l’émulateur Azure Cosmos DB

Le certificat HTTPS de l’émulateur Azure Cosmos DB est auto-signé. Pour que le SDK fonctionne avec l’émulateur, importez le certificat de l’émulateur dans Java TrustStore. Pour plus d’informations, consultez Exporter les certificats de l’émulateur Azure Cosmos DB.

Problèmes de conflits de dépendances

Le Kit de développement logiciel (SDK) Java Azure Cosmos DB extrait de nombreuses dépendances. En règle générale, si l’arborescence des dépendances de votre projet comprend une version antérieure d’un artefact dont le Kit de développement logiciel (SDK) Java Azure Cosmos DB dépend, des erreurs inattendues peuvent se produire lors de l’exécution de votre application. Si vous déboguez la raison pour laquelle votre application lève une exception de manière inattendue, il est judicieux de bien vérifier que votre arborescence des dépendances n’extrait pas accidentellement une version plus ancienne d’une ou de plusieurs dépendances du Kit de développement logiciel (SDK) Java Azure Cosmos DB.

La solution de contournement pour un tel problème consiste à identifier quelle dépendance de votre projet récupère l’ancienne version, à exclure la dépendance transitive sur cette version antérieure, puis à permettre au kit SDK Java Azure Cosmos DB d’intégrer la version plus récente.

Pour identifier la dépendance de votre projet qui récupère une version antérieure d’un élément dont le kit SDK Java Azure Cosmos DB dépend, exécutez la commande suivante sur le fichier pom.xml de votre projet :

mvn dependency:tree

Pour en savoir plus, consultez le guide d’arborescence des dépendances maven.

Une fois que vous savez quelle dépendance de votre projet dépend d’une version antérieure, vous pouvez modifier la dépendance sur cette bibliothèque dans votre fichier pom, et exclure la dépendance transitive en suivant l’exemple ci-dessous (qui suppose que reactor-core est la dépendance obsolète) :

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

Pour en savoir plus, consultez le guide relatif à l’exclusion de dépendances transitives.

Activer la journalisation de SDK client

Le kit SDK Java v4 Azure Cosmos DB utilise SLF4j comme façade de journalisation prenant en charge la journalisation dans les frameworks de journalisation populaires tels que log4j et logback.

Par exemple, si vous souhaitez utiliser log4j en tant que framework de journalisation, ajoutez les bibliothèques suivantes dans votre chemin de classe 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>

Ajoutez également une configuration 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

Pour plus d’informations, consultez le manuel de journalisation sfl4j.

Statistiques réseau du système d’exploitation

Exécutez la commande netstat pour avoir une idée du nombre de connexions à l’état ESTABLISHED, CLOSE_WAIT, et ainsi de suite.

Sur Linux, vous pouvez exécuter la commande suivante.

netstat -nap

Sur Windows, vous pouvez exécuter la même commande avec d’autres indicateurs d’argument :

netstat -abn

Filtrez le résultat uniquement sur les connexions au point de terminaison Azure Cosmos DB.

Le nombre de connexions au point de terminaison Azure Cosmos DB à l’état ESTABLISHED ne doit pas être supérieur à la taille du pool de connexions configuré.

De nombreuses connexions au point de terminaison Azure Cosmos DB peuvent se trouver à l’état CLOSE_WAIT. Il peut y en avoir plus de 1000. Un nombre si élevé indique que les connexions sont établies et détruites rapidement. Cette situation peut provoquer des problèmes. Pour plus d’informations, consultez la section Problèmes courants et solutions de contournement.

Problèmes courants liés aux requêtes

Les métriques de requête vous aident à déterminer où la requête passe la majeure partie du temps. Elles vous permettent de voir quelle portion du temps est consacrée au serveur principal et au client. En savoir plus sur le Guide des performances des requêtes.

Étapes suivantes