El viaje para mejorar el desempeño no está completo sin sumergirnos en el camino que siguen los eventos hacia su destino. Cuando Filebeat o cualquier otro ritmo publica eventos en un Elasticsearch® remoto, depende de su API _bulk . Esto significa que Beats no solo realiza solicitudes a Elasticsearch, sino que también analiza sus respuestas, lo que como resultado aumenta la utilización de la memoria.
Anteriormente, se mostro cómo lograr reducir la asignación de Beats al reducir el uso de Logger en la ruta activa del evento, lo que redujo la memoria asignada general.
Investigación
Antes de poder resolver cualquier problema de rendimiento, primero debemos comprender la línea de base. Para eso, comenzamos analizando un pprof del conjunto de pruebas comparativas y nos centramos en la asignación de memoria de la función PublishEvent. Esta función es responsable de enviar la solicitud _bulk y analizar la respuesta de Elasticsearch.
Al observar el método PublishEvents, vimos que asigna 5 MB directamente y ~41 GB de memoria indirecta de otros métodos. Con base en estos resultados, comenzamos analizando esas asignaciones indirectas y de dónde provienen.
Observando la ruta del código y las funciones involucradas, vimos que ejecuta la llamada HTTP _bulk, para luego consumir la respuesta usando el io.ReadAll . Al seguir la definición de esa función , vimos una advertencia de obsolescencia.
Deprecated: As of Go 1.16, this function simply calls io.ReadAll.
El siguiente paso es abordar la forma en que consumimos la respuesta misma. Dado que la respuesta es algo que sucederá en el contexto de NewConnection, tendría sentido reutilizar el mismo búfer para consumir esa respuesta en comparación con llamar previamente a io.ReadAll para cada respuesta. De esta manera, se utiliza una implementación más actualizada y más eficiente en memoria y reducimos la cantidad de asignaciones que vamos a realizar cada vez que inicializamos una conexión.
Una vez que se haya terminado con los cambios, debemos volvemos a nuestra prueba comparativa de Go y validamos el impacto. Para facilitar la comparación de resultados, Go proporciona una herramienta llamada benchstat que puede ayudarnos a comparar los dos resultados.
# benchstat baseline.txt initialize_per_connection.txtngoos: darwinngoarch: arm64npkg: github.com/elastic/beats/v7/libbeat/esleg/eslegclientn │ baseline.txt │ initialize_per_connection.txt │n │ sec/op │ sec/op vs base │nExecHTTPRequest-12 2.995µ ± 2% 2.905µ ± 1% -3.01% (p=0.000 n=8)nn │ baseline.txt │ initialize_per_connection.txt │n │ B/op │ B/op vs base │nExecHTTPRequest-12 6.055Ki ± 0% 5.555Ki ± 0% -8.26% (p=0.000 n=8)nn │ baseline.txt │ initialize_per_connection.txt │n │ allocs/op │ allocs/op vs base │nExecHTTPRequest-12 27.00 ± 0% 26.00 ± 0% -3.70% (p=0.000 n=8)
La cantidad real de memoria asignada depende del tamaño de la respuesta que obtenemos de Elasticsearch, por lo que otra área que podemos considerar es si hay algo que podamos hacer para reducir ese tamaño de respuesta. El código que deserializa la respuesta ya está realizando una inicialización diferida y se salta secciones de la respuesta. Pero para analizarlo, todavía necesitamos analizar la respuesta completa, incluida la parte de la respuesta que no necesitamos.
La idea es consumir solo el campo de la respuesta que usemos, reduciendo los bytes de respuesta por parte de Elasticsearch. Al observar la documentación de Elasticsearch, vemos la función de filtrado de respuestas , que podemos aprovechar y reducir los campos de respuesta para respuestas masivas a los que solo se requieren de nuestro deserializador personalizado.
Esto es importante, ya que ahora hemos reducido la cantidad de memoria que el GC tiene que limpiar y hemos liberado algunos de los ciclos de la CPU para otras tareas. Cambiar el pprof al perfil de CPU y, en particular, comparar los dos perfiles de CPU del mismo punto de referencia, tomados en el mismo punto de control.
El resultado
El resultado de nuestra investigación resultó en mejoras realizadas para Beats en v8.11.0 . Curiosamente, esta investigación también nos llevó a realizar cambios en el complemento de salida Logstash® Elasticsearch para implementar la misma técnica de filtrado de respuestas.
Lecciones aprendidas
Esta investigación demostró una cosa: se debe dar seguimiento a las advertencias obsoletas. Es una buena oportunidad para revisar la implementación y encontrar formas de mejorar la asignación de memoria. Además, para aplicaciones que envían/reciben grandes cantidades de datos, debemos usar solo las partes de la respuesta que necesitamos porque el tamaño de la respuesta se asigna en el montón de nuestra aplicación cuando nos preocupamos por el contenido de la respuesta.
Obtenga más información sobre las posibilidades con Filebeat .