Problèmes de précision et solutions sous la requête d'agrégation Elasticsearch 8.X

1. Problèmes d'environnement en ligne

L'étudiant Gupao a demandé : "Je faisais un test en consultant le document d'exécution. Lorsque agg demande avg, qu'il soit double ou long, les données ne sont pas exactes. Comment résoudre ce problème dans l'environnement de production ?

737b136799fdb8d9b14bda46f0de0414.jpeg

37dbd666e63f5fd02159ffc2913546df.png

2. Classification des problèmes et scénarios d'occurrence

Les problèmes ci-dessus peuvent être classés comme suit : problèmes de précision sous la requête d'agrégation Elasticsearch .

Dans le travail quotidien de traitement des données, nous rencontrons souvent des opérations telles que la requête de données volumineuses, les statistiques et l'agrégation à l'aide d'Elasticsearch. Elasticsearch affiche d'excellentes performances de recherche dans la pratique, mais dans certaines opérations d'agrégation complexes, telles que la moyenne (avg), il peut y avoir des problèmes de précision des données inexactes .

Ensuite, nous présenterons en détail le scénario d'occurrence, les causes possibles et les solutions de ce problème.

Dans Elasticsearch, le problème de précision des données se produit principalement dans l'opération d'agrégation (agrégation). Par exemple, lorsque nous effectuons des opérations sur un grand nombre, telles que la sommation (sum) et la valeur moyenne (avg), nous pouvons rencontrer des problèmes de précision causés par le type de données (double ou long). En effet, Elasticsearch utilise une méthode appelée "calcul en virgule flottante" pour effectuer des calculs de grands nombres afin d'améliorer les performances et l'efficacité lors de l'exécution d'opérations d'agrégation, et cette méthode de calcul perd souvent une certaine précision lors du traitement de grands nombres .

3. Minimiser la récurrence du problème

Illustrons ce problème par un exemple simple. Nous avons des données d'articles stockées dans Elasticsearch et nous voulons maintenant calculer le prix moyen de tous les articles.

Le DSL pour les données et les requêtes est le suivant (vérifié dans l'environnement Elasticsearch 8.X) :

  • données:

POST /product/_bulk
{ "index" : { "_id" : "1" } }
{ "name" : "商品1", "price" : 1234.56 }
{ "index" : { "_id" : "2" } }
{ "name" : "商品2", "price" : 7890.12 }
  • Requête DSL :

GET /product/_search
{
  "size": 0,
  "aggs": {
    "avg_price": {
      "avg": {
        "field": "price"
      }
    }
  }
}

Bien que nous nous attendions à ce que le prix moyen soit (1234,56 + 7890,12) / 2 = 4562,34, en raison de la précision des calculs en virgule flottante, les résultats renvoyés peuvent être légèrement biaisés, comme le montre la figure ci-dessous.

e4cdb04ce9266742828c7d6d576221ce.png

4. Discussion et mise en œuvre de la solution

Comment résoudre le problème de précision mentionné ci-dessus après agrégation ? Nous combinons les connaissances de base d'Elasticsearch et l'expérience pratique pour proposer les trois solutions suivantes.

  • Solution 1 : utilisez le type scaled_float pour améliorer la précision.

  • Solution 2 : utilisez scripted_metric pour améliorer la précision.

  • Option 3 : écrivez du code au niveau de l'entreprise pour l'implémenter vous-même.

Ensuite, nous allons pratiquer et interpréter les trois solutions ci-dessus une par une.

4.1 Améliorer la précision avec le type scaled_float

4.1.1 Qu'est-ce que scaled_float ?

scaled_float Il s'agit d'un type de données numérique spécial fourni par Elasticsearch pour stocker des nombres avec des décimales.

Contrairement float à et double , scaled_float est en fait un long type sauf qu'il stocke les nombres réels à virgule flottante multipliés par un facteur d'échelle donné.

Dans de nombreux scénarios d'application, nous devons stocker des nombres avec des décimales, telles que des prix, des notes, etc. float et double sont des types de données couramment utilisés, mais ils présentent certains problèmes : par exemple, ils peuvent perdre en précision lors du stockage et du tri, et ils occupent plus d'espace de stockage que les types entiers. Au lieu de cela,   scaled_float le float est en fait multiplié par un scaling factoret le résultat est stocké sous la forme   long .

Par exemple, si scaling factorest 100, alors le nombre 12.34 sera stocké sous la forme 1234. Lors de l'interrogation et du retour des résultats, Elasticsearch divise par scaling factor et renvoie le nombre à virgule flottante d'origine.

4.1.2 Avantages de scaled_float

  • La précision est plus précise et contrôlable

Comparé à float et double, scaled_float est plus précis dans le stockage et le tri, car il s'agit en fait d'un entier long stocké et il n'y a pas de problème de précision avec les nombres à virgule flottante.

  • meilleure performance

Étant donné que scaled_float utilise le type long, il occupe moins d'espace de stockage et offre de meilleures performances.

  • plus flexible

Le facteur d'échelle peut être défini selon les besoins pour équilibrer la précision et les performances. Si une plus grande précision est requise, un facteur d'échelle plus grand peut être utilisé. Si le projet doit se concentrer sur les performances et l'espace de stockage, vous pouvez utiliser un facteur d'échelle plus petit.

4.1.3 Utilisation de scaled_float dans Elasticsearch

Pour utiliser scaled_float dans Elasticsearch, vous devez définir le type de champ dans le mappage et fournir un facteur d'échelle. Par exemple:

{
  "properties": {
    "price": {
      "type": "scaled_float",
      "scaling_factor": 100.0
    }
  }
}

Ce mappage définit un champ scaled_float appelé prix avec un facteur d'échelle de 100. Cela signifie que tous les prix seront multipliés par 100 et stockés aussi longtemps.

Par exemple, un prix de 12,34 serait stocké sous la forme 1234.

Dans l'ensemble, scaled_float est un outil très utile qui offre une meilleure précision et de meilleures performances dans les situations où les nombres à virgule flottante doivent être stockés.

4.1.4 Combat réel pour résoudre des problèmes similaires au début

Dans cet exemple, nous avons deux produits dont les prix sont flottants.

Si vous souhaitez utiliser scaled_float, vous devez d'abord configurer un mappage. En supposant que vous souhaitiez stocker les prix avec une précision infime, vous pouvez définir scaling_factor sur 100,0. Voici les étapes pour définir un mappage :

Tout d'abord, créez un nouvel index et définissez le mappage :

PUT /product
{
  "mappings": {
    "properties": {
      "name": {
        "type": "text"
      },
      "price": {
        "type": "scaled_float",
        "scaling_factor": 100.0
      }
    }
  }
}

Cette commande crée un nouveau produit d'index et définit deux champs : name (type text) et price (type scaled_float, scaling_factor 100.0).

Ensuite, les données d'insertion en bloc par lot sont les suivantes :

POST /product/_bulk
{ "index" : { "_id" : "1" } }
{ "name" : "商品1", "price" : 1234.56 }
{ "index" : { "_id" : "2" } }
{ "name" : "商品2", "price" : 7890.12 }

Dans ce processus, la valeur du champ de prix sera automatiquement multipliée par scaling_factor (100,0 dans ce cas), puis stockée en tant que type long. Ainsi, les valeurs réelles stockées sont 123456 et 789012.

Lors de l'interrogation, Elasticsearch divisera automatiquement le prix par scaling_factor et renverra le nombre à virgule flottante d'origine. Par exemple, si vous exécutez la requête suivante :

GET /product/_doc/1

Le résultat renvoyé sera :

{
  "_index": "product",
  "_id": "1",
  "_version": 1,
  "_seq_no": 0,
  "_primary_term": 1,
  "found": true,
  "_source": {
    "name": "商品1",
    "price": 1234.56
  }
}

Bien que le prix soit multiplié par 100 lorsqu'il est stocké, il est divisé par 100 lorsqu'il est interrogé, de sorte que le prix affiché est toujours de 1234,56.

De cette façon, les prix peuvent être stockés et interrogés avec moins d'espace de stockage et de meilleures performances tout en maintenant une haute précision.

Au final nous obtenons ce qui suit :

GET product/_search
{
  "size": 0,
  "aggs": {
    "avg_price": {
      "avg": {
        "field": "price"
      }
    }
  }
}

Comme indiqué ci-dessous, la précision résultante est celle attendue.

b1c23a9853a3cdb9b44e273c31bc852a.png

4.2 Utilisation de scripted_metric pour améliorer la précision

Face à cette situation, nous pouvons utiliser une autre fonction puissante d'Elasticsearch - le calcul de script (scripted_metric) pour résoudre.

scripted_metric nous permet de personnaliser une logique d'agrégation complexe, telle que la DSL suivante :

####务必要删除索引
DELETE product

POST /product/_bulk
{ "index" : { "_id" : "1" } }
{ "name" : "商品1", "price" : 1234.56 }
{ "index" : { "_id" : "2" } }
{ "name" : "商品2", "price" : 7890.12 }

GET /product/_search
{
  "size": 0,
  "aggs": {
    "avg_price": {
      "scripted_metric": {
        "init_script": "state.total = 0.0; state.count = 0",
        "map_script": "state.total += params._source.price; state.count++",
        "combine_script": "HashMap result = new HashMap(); result.put('total', state.total); result.put('count', state.count); return result",
        "reduce_script": """
  double total = 0.0; long count = 0; 
  for (state in states) { 
    total += state['total']; 
    count += state['count']; 
  }
  double average = total / count;
  DecimalFormat df = new DecimalFormat("#.00");
  return df.format(average);
        """
      }
    }
  }
}

Elasticsearch est un moteur de recherche et d'analyse distribué, ce qui signifie que les données peuvent être stockées et traitées sur plusieurs partitions. Pour traiter les données distribuées, Elasticsearch utilise un modèle de programmation appelé map-reduce. Ce modèle est divisé en deux étapes : cartographie (Map) et réduction (Reduce). init_script, map_script, combine_script et reduce_script sont tous des composants de ce modèle pour des agrégations plus complexes.

Dans le script ci-dessus, nous avons défini quatre étapes :

  • init_script : script d'initialisation qui crée un nouvel état pour chaque agrégation sur chaque partition.

  • map_script : un script de mappage qui traite les documents d'entrée et convertit leur état dans un format pouvant être fusionné.

  • combine_script : un script de combinaison pour fusionner l'état de chaque partition au niveau du nœud.

  • reduce_script : un script de réduction pour fusionner l'état globalement.

De cette façon, nous pouvons obtenir une moyenne plus précise.

La signification spécifique du script ci-dessus est expliquée comme suit :

  • init_script : ce script est exécuté une fois par partition, créant un nouvel état pour chaque partition.

Dans le script ci-dessus, il crée un objet d'état qui contient une somme (total) et un compteur (compte). L'objet d'état est initialisé à {total : 0,0, nombre : 0}.

  • map_script : ce script est exécuté une fois par document.

Dans le script ci-dessus, il lit pricele champ de chaque document et y ajoute cette valeur totaltout en incrémentant countla valeur. De cette façon, totalla somme de tous les prix des documents sera incluse et countle nombre de documents traités sera inclus.

  • combine_script : ce script est exécuté une fois par partition, combinant l'état de chaque partition.

Dans le script ci-dessus, il totalmet simplement la somme dans countun HashMapretour. S'il y a beaucoup d'états à fusionner, il peut y avoir un prétraitement dans ce script.

  • reduce_script : ce script est exécuté une fois lorsque les résultats sont fusionnés, et l'état de tous les fragments est réduit pour calculer le résultat final.

Dans le script ci-dessus, il parcourt les états de tous les fragments, calcule la somme , totalpuis countcalcule le prix moyen. DecimalFormatUne chaîne utilisée pour formater le prix moyen à deux décimales.

En termes simples, il s'agit d'un processus étape par étape de calcul de la moyenne : d'abord initialiser l'état, puis mettre à jour l'état pour chaque document, puis fusionner l'état sur chaque fragment, et enfin fusionner l'état globalement et calculer le résultat.

Le résultat final est illustré dans la figure ci-dessous, atteignant la précision attendue.

77ca765af6678045470c08d8f11974d2.png

4.3 Au niveau métier, écrivez vous-même le code.

Contrôle de la précision au niveau de l'application : transmettez les données brutes à la couche d'application, puis effectuez des calculs précis au niveau de la couche d'application. L'avantage de cette méthode est que des résultats très précis peuvent être obtenus, mais l'inconvénient est qu'une grande quantité de données peut devoir être traitée, ce qui augmente la charge de transmission et de calcul du réseau.

Le traitement des problèmes d'exactitude des données au niveau de l'application nécessite généralement deux étapes :

  • Premièrement, les données brutes doivent être obtenues à partir d'Elasticsearch ;

  • Ensuite, des calculs précis sont effectués au niveau de la couche application.

Voici un exemple de gestion de la précision des données en Java :

En supposant que l'application système est écrite en Java, vous pouvez utiliser la classe BigDecimal de Java pour des calculs précis en virgule flottante. Voici un exemple simple :

BigDecimal price1 = new BigDecimal("1234.56");
BigDecimal price2 = new BigDecimal("7890.12");
BigDecimal average = price1.add(price2).divide(new BigDecimal(2), 2, RoundingMode.HALF_UP);

System.out.println(average);  // 输出:4562.34

Dans l'exemple ci-dessus, nous avons d'abord créé deux objets BigDecimal représentant deux prix. Ensuite, nous appelons la méthode add pour les additionner, puis nous appelons la méthode diviser pour calculer la moyenne. Enfin, nous utilisons le paramètre RoundingMode.HALF_UP pour contrôler le mode d'arrondi.

Notez que cette approche nécessite que toutes les données soient traitées au niveau de la couche d'application, ce qui peut entraîner des problèmes de performances si le volume de données est important. Afin de réduire la charge de transmission et de calcul des données, il peut être nécessaire d'utiliser des requêtes plus précises dans Elasticsearch pour obtenir uniquement les données requises, ou d'utiliser la fonction d'agrégation d'Elasticsearch pour réduire la quantité de données renvoyées.

En outre, il peut être nécessaire d'effectuer certaines optimisations au niveau de la couche d'application, telles que l'utilisation de technologies telles que le traitement parallèle et la mise en cache pour améliorer les performances de traitement. La méthode spécifique sera déterminée en fonction de la situation spécifique et des besoins de l'application.

5. Résumé

En général, bien qu'Elasticsearch puisse avoir le problème de l'exactitude des données inexactes lors de l'exécution d'opérations d'agrégation, des résultats plus précis peuvent être obtenus en utilisant le type scaled_float pour améliorer la précision, en utilisant scripted_metric pour améliorer la précision et en écrivant du code au niveau de l'entreprise pour obtenir des résultats plus précis. résultats.

Lorsque nous rencontrons des problèmes similaires, nous devons choisir la solution la plus appropriée en fonction de la situation réelle. D'une part, les exigences de précision doivent être prises en compte, et d'autre part, les performances des requêtes et la consommation de ressources doivent également être prises en compte. Nous devons utiliser les calculs de script en temps opportun pour améliorer la précision des opérations d'agrégation en fonction des besoins réels de l'entreprise.

lecture recommandée

  1. Première diffusion sur tout le réseau ! De 0 à 1 vidéo de dédouanement Elasticsearch 8.X

  2. Liste de cognition de la méthodologie Dead Elasticsearch 8.X

  3. Comment apprendre systématiquement Elasticsearch ?

  4. 2023, fais quelque chose

f61a4e975f2aec87d8cd8470e352d7a7.jpeg

Acquérir plus de produits secs plus rapidement en moins de temps !

Améliorez-vous avec près de 2000+ passionnés d'Elastic dans le monde entier !

cc625dfb83be0e5364b17070320e54ab.gif

A l'ère des grands modèles, apprenez les produits secs avancés avec une longueur d'avance !

Je suppose que tu aimes

Origine blog.csdn.net/wojiushiwo987/article/details/131618237
conseillé
Classement