Problemas de precisión y soluciones en la consulta de agregación de Elasticsearch 8.X

1. Problemas del entorno en línea

El estudiante Gupao preguntó: Estaba haciendo una prueba mientras miraba el documento de tiempo de ejecución. Cuando agg solicita un promedio, no importa si es doble o largo, los datos no son precisos. ¿Cómo resolver este problema en el entorno de producción?

737b136799fdb8d9b14bda46f0de0414.jpeg

37dbd666e63f5fd02159ffc2913546df.png

2. Clasificación de problemas y escenarios de ocurrencia

Los problemas anteriores se pueden clasificar como: problemas de precisión en la consulta de agregación de Elasticsearch .

En el trabajo diario de procesamiento de datos, a menudo nos encontramos con operaciones como consultas de big data, estadísticas y agregación mediante Elasticsearch. Elasticsearch muestra un rendimiento de búsqueda excelente en la práctica, pero en algunas operaciones de agregación complejas, como promediar (avg), puede haber problemas con la precisión de los datos imprecisos .

A continuación, presentaremos en detalle el escenario de ocurrencia, las posibles causas y las soluciones de este problema.

En Elasticsearch, el problema de la precisión de los datos ocurre principalmente en la operación de agregación (agregación). Por ejemplo, cuando hacemos algunas operaciones con números grandes, como la sumatoria (sum) y el valor promedio (avg), podemos encontrarnos con problemas de precisión causados ​​por el tipo de datos (doble o largo). Esto se debe a que Elasticsearch utiliza un método llamado "cálculo de punto flotante" para realizar cálculos de números grandes con el fin de mejorar el rendimiento y la eficiencia al realizar operaciones de agregación, y este método de cálculo a menudo pierde algo de precisión al procesar números grandes .

3. Minimiza la recurrencia del problema

Ilustremos este problema con un ejemplo sencillo. Tenemos algunos datos de artículos almacenados en Elasticsearch y ahora queremos calcular el precio promedio de todos los artículos.

El DSL para datos y consultas es el siguiente (verificado en el entorno de Elasticsearch 8.X):

  • datos:

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

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

Aunque esperamos que el precio medio sea (1234,56 + 7890,12) / 2 = 4562,34, debido a la precisión de los cálculos de coma flotante, los resultados devueltos pueden estar ligeramente sesgados, como se muestra en la siguiente figura.

e4cdb04ce9266742828c7d6d576221ce.png

4. Discusión e implementación de la solución

¿Cómo resolver el problema de precisión mencionado anteriormente después de la agregación? Combinamos el conocimiento básico de Elasticsearch y la experiencia práctica para dar las siguientes tres soluciones.

  • Solución 1: utilice el tipo scaled_float para mejorar la precisión.

  • Solución 2: utilice scripted_metric para mejorar la precisión.

  • Opción 3: Escriba código a nivel empresarial para implementarlo usted mismo.

A continuación, practicaremos e interpretaremos las tres soluciones anteriores una por una.

4.1 Mejorar la precisión con el tipo scaled_float

4.1.1 ¿Qué es scaled_float?

scaled_float Es un tipo de dato numérico especial proporcionado por Elasticsearch para almacenar números con decimales.

A diferencia de float y double , scaled_float es en realidad un long tipo excepto que almacena números reales de coma flotante multiplicados por un factor de escala determinado.

En muchos escenarios de aplicación, necesitamos almacenar números con decimales, como precios, calificaciones, etc. float y double son tipos de datos de uso común, pero tienen algunos problemas: por ejemplo, pueden perder precisión al almacenar y ordenar, y ocupan más espacio de almacenamiento que los tipos enteros. En su lugar,   scaled_float el flotante se multiplica por uno scaling factory el resultado se almacena como   long .

Por ejemplo, si scaling factores 100, el número 12,34 se almacenará como 1234. Al consultar y devolver resultados, Elasticsearch dividirá scaling factor y devolverá el número de coma flotante original.

4.1.2 Ventajas de scaled_float

  • La precisión es más precisa y controlable.

En comparación con float y double, scaled_float es más preciso en el almacenamiento y la clasificación, porque en realidad es un entero largo almacenado y no hay ningún problema de precisión con los números de punto flotante.

  • mejor interpretación

Dado que scaled_float usa el tipo largo, ocupa menos espacio de almacenamiento y tiene un mejor rendimiento.

  • mas flexible

El factor de escala se puede establecer según sea necesario para equilibrar la precisión y el rendimiento. Si se requiere una mayor precisión, se puede utilizar un factor de escala mayor. Si el proyecto debe centrarse en el rendimiento y el espacio de almacenamiento, puede utilizar un factor de escala menor.

4.1.3 Usando scaled_float en Elasticsearch

Para usar scaled_float en Elasticsearch, debe definir el tipo de campo en el mapeo y proporcionar un factor de escala. Por ejemplo:

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

Esta asignación define un campo scaled_float llamado precio con un factor de escala de 100. Esto significa que todos los precios se multiplicarán por 100 y se almacenarán durante tanto tiempo.

Por ejemplo, un precio de 12,34 se almacenaría como 1234.

En general, scaled_float es una herramienta muy útil que proporciona mayor precisión y rendimiento en situaciones en las que es necesario almacenar números de punto flotante.

4.1.4 Combate real para resolver problemas similares al principio

En este ejemplo tenemos dos productos cuyos precios son flotantes.

Si desea utilizar scaled_float, primero debe configurar una asignación. Suponiendo que desea almacenar precios con precisión de minutos, puede establecer scaling_factor en 100,0. Estos son los pasos para definir una asignación:

Primero, cree un nuevo índice y defina la asignación:

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

Este comando crea un nuevo producto de índice y define dos campos: nombre (escriba texto) y precio (escriba scaled_float, scaling_factor 100.0).

Luego, los datos de inserción masiva por lotes son los siguientes:

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

En este proceso, el valor del campo de precio se multiplicará automáticamente por scaling_factor (100,0 en este caso) y luego se almacenará como tipo largo. Entonces, los valores almacenados reales son 123456 y 789012.

Al consultar, Elasticsearch dividirá automáticamente el precio por scaling_factor y devolverá el número de punto flotante original. Por ejemplo, si ejecuta la siguiente consulta:

GET /product/_doc/1

El resultado devuelto será:

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

Aunque el precio se multiplica por 100 cuando se almacena, se divide por 100 cuando se consulta, por lo que el precio que se ve sigue siendo 1234,56.

De esta forma, los precios se pueden almacenar y consultar con menos espacio de almacenamiento y un mejor rendimiento manteniendo una alta precisión.

Al final conseguimos lo siguiente:

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

Como se muestra a continuación, la precisión resultante es la esperada.

b1c23a9853a3cdb9b44e273c31bc852a.png

4.2 Uso de scripted_metric para mejorar la precisión

Ante esta situación, podemos usar otra poderosa función de Elasticsearch: cálculo de script (scripted_metric) para resolver.

scripted_metric nos permite personalizar la lógica de agregación compleja, como el siguiente DSL:

####务必要删除索引
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 es un motor de análisis y búsqueda distribuida, lo que significa que los datos se pueden almacenar y procesar en múltiples fragmentos. Para procesar datos distribuidos, Elasticsearch usa un modelo de programación llamado map-reduce. Este modelo se divide en dos pasos: mapeo (Map) y reducción (Reduce). init_script, map_script, combine_script y reduce_script son todos componentes de este modelo para agregaciones más complejas.

En el script anterior, definimos cuatro pasos:

  • init_script: secuencia de comandos de inicialización que crea un nuevo estado para cada agregación en cada fragmento.

  • map_script: un script de mapeo que procesa documentos de entrada y convierte su estado en un formato que se puede fusionar.

  • combine_script: un script de combinación para fusionar el estado de cada fragmento en el nivel de nodo.

  • reduce_script: un script de reducción para fusionar el estado globalmente.

De esta manera, podemos obtener un promedio más preciso.

El significado específico de la secuencia de comandos anterior se explica a continuación:

  • init_script: este script se ejecuta una vez por fragmento, creando un nuevo estado para cada fragmento.

En el script anterior, crea un objeto de estado que contiene una suma (total) y un contador (recuento). El objeto de estado se inicializa a {total: 0.0, cuenta: 0}.

  • map_script: este script se ejecuta una vez por documento.

En el script anterior, lee priceel campo de cada documento y le agrega este valor totalmientras incrementa countel valor. De esta forma, totalse incluirá la suma de todos los precios de los documentos y countse incluirá la cantidad de documentos procesados.

  • combine_script: este script se ejecuta una vez por fragmento, combinando el estado de cada fragmento.

En el script anterior, simplemente totalpone la suma en countuna HashMapdevolución. Si hay muchos estados para fusionar, puede haber algún preprocesamiento en este script.

  • reduce_script: este script se ejecuta una vez cuando se fusionan los resultados y el estado de todos los fragmentos se reduce para calcular el resultado final.

En el script anterior, itera sobre los estados de todos los fragmentos, calcula la suma totaly countluego calcula el precio promedio. DecimalFormatUna cadena utilizada para formatear el precio promedio con dos decimales.

En términos simples, este es un proceso paso a paso para calcular el promedio: primero inicialice el estado, luego actualice el estado para cada documento, luego combine el estado en cada fragmento y finalmente combine el estado globalmente y calcule el resultado.

El resultado final se muestra en la siguiente figura, logrando la precisión esperada.

77ca765af6678045470c08d8f11974d2.png

4.3 A nivel empresarial, escriba el código usted mismo.

Control de precisión a nivel de aplicación: obtenga datos sin procesar en la capa de aplicación y luego realice cálculos precisos en la capa de aplicación. La ventaja de este método es que se pueden obtener resultados muy precisos, pero la desventaja es que es posible que sea necesario procesar una gran cantidad de datos, lo que aumenta la carga de la transmisión y el cálculo de la red.

Tratar los problemas de precisión de los datos en el nivel de la aplicación generalmente requiere dos pasos:

  • Primero, los datos sin procesar deben obtenerse de Elasticsearch;

  • Luego, se realizan cálculos precisos en la capa de aplicación.

El siguiente es un ejemplo de manejo de precisión de datos en Java:

Suponiendo que la aplicación del sistema está escrita en Java, puede usar la clase BigDecimal de Java para cálculos precisos de coma flotante. Aquí hay un ejemplo 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

En el ejemplo anterior, primero creamos dos objetos BigDecimal que representan dos precios. Luego llamamos al método add para sumarlas y luego llamamos al método divide para calcular el promedio. Finalmente, usamos el parámetro RoundingMode.HALF_UP para controlar el modo de redondeo.

Tenga en cuenta que este enfoque requiere que todos los datos se procesen en la capa de aplicación, lo que puede causar problemas de rendimiento si el volumen de datos es grande. Para reducir la carga de la transmisión y el cálculo de datos, puede ser necesario usar consultas más precisas en Elasticsearch para obtener solo los datos requeridos, o usar la función de agregación de Elasticsearch para reducir la cantidad de datos devueltos.

Además, puede ser necesario realizar algunas optimizaciones en la capa de aplicación, como el uso de tecnologías como el procesamiento en paralelo y el almacenamiento en caché para mejorar el rendimiento del procesamiento. El método específico se determinará de acuerdo con la situación específica y las necesidades de la aplicación.

5. Resumen

En general, aunque Elasticsearch puede tener una precisión de datos inexacta al realizar operaciones de agregación, se pueden obtener resultados más precisos usando el tipo scaled_float para mejorar la precisión, usando scripted_metric para mejorar la precisión y escribiendo código a nivel empresarial para lograr resultados más precisos.

Cuando nos encontramos con problemas similares, debemos elegir la solución más adecuada de acuerdo con la situación real. Por un lado, se deben considerar los requisitos de precisión y, por otro lado, también se debe considerar el rendimiento de la consulta y el consumo de recursos. Deberíamos usar cálculos de secuencias de comandos de manera oportuna para mejorar la precisión de las operaciones de agregación de acuerdo con las necesidades reales del negocio.

Lectura recomendada

  1. ¡Primer lanzamiento en toda la red! De 0 a 1 vídeo de liquidación de Elasticsearch 8.X

  2. Heavyweight | Dead Elasticsearch 8.X Metodología Lista de cognición

  3. ¿Cómo aprender sistemáticamente Elasticsearch?

  4. 2023, haz algo

f61a4e975f2aec87d8cd8470e352d7a7.jpeg

¡Adquiera más productos secos más rápido en menos tiempo!

¡Mejore con casi 2000+ entusiastas de Elastic en todo el mundo!

cc625dfb83be0e5364b17070320e54ab.gif

¡En la era de los modelos grandes, aprenda productos secos avanzados un paso adelante!

Supongo que te gusta

Origin blog.csdn.net/wojiushiwo987/article/details/131618237
Recomendado
Clasificación