Optimizando el uso de recursos en peticiones HTTP con Go

Sirpyerre Rojas
4 min readJan 26, 2023

--

Hoy en el trabajo nos dimos cuenta que la API que recientemente estuve desarrollando junto con otros colegas, se estaba comiendo los recursos a los bestia, en especial la memoria.

Al no cerrar las peticiones (HTTP), se mantenían conexiones abiertas innecesariamente, lo que agotaba los recursos del sistema (memoria) y a una disminución en el rendimiento de la aplicación. En esta imagen se puede observar como va aumentando el consumo de memoria:

Consumo de memoria.

Aumentar los recursos de los pods en Kubernetes y las posibles consecuencias

Así que lo más fácil fue aumentar los recursos de los pods de Kubernetes (yo no tengo experiencia en este tema), el equipo de SRE e infra se encargaron de hacer esta configuración.

El aumento de los recursos (como la memoria o el procesador) de los pods en Kubernetes puede ser una solución temporal, especialmente si se detecta que se están agotando los recursos disponibles. Es importante tener en cuenta que aumentar los recursos también aumenta el uso de recursos en el sistema, y si no se resuelve el problema, puede llevar a una situación en la que los pods escalan rápidamente y consumen toda la memoria disponible, lo que puede provocar que se reinicien los pods.

Para evitar esto es importante detectar y solucionar el problema, ya sea a través de la optimización del código o la gestión adecuada de los recursos. Por ejemplo, si se detecta que hay una fuga de memoria en la aplicación, se pueden utilizar herramientas como pprof para identificar las funciones o secciones del código que están utilizando demasiada memoria y optimizarlas.

En esta imagen podemos ver como fueron aumentando los pods.

Aumento de pods por consumo de memoria.

Detectar y solucionar una fuga de memoria con pprof

Ya teniendo esta solución temporal lo siguiente fue detectar donde había fuga de memoria y porque pasaba. Me reuní con un equipo que tiene mucha experiencia en este tema.

Para detectar un memory leak usamos la herramienta pprof de Go, siguiendo la documentación, lo primero fue poner el import del paquete en el main.go:

import _ "net/http/pprof"

A continuación agregue el siguiente código al inicio de la función main():

go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()

Una vez corriendo el servidor, se puede ejecutar go tool pprof http://localhost:8080/debug/pprof para obtener una vista de la memoria utilizada por la aplicación. En el navegador abrimos la url del pprof.

Utilizando un cliente singleton para evitar la creación de objetos innecesarios

Después de hacer varios queries a los endpoints dimos con el que pensábamos tenía la fuga. Al hacer las peticiones, el número de gorutines aumentaba y nos dimos cuenta que se estaba creando un cliente por cada petición que se realizaban a las APIS externas.

Crear un nuevo cliente HTTP para cada petición puede ser ineficiente en términos de uso de recursos, ya que se estarían creando nuevos objetos y abriendo conexiones para cada petición. En lugar de esto, se recomienda utilizar un solo cliente HTTP para todas las peticiones. Esto se puede lograr mediante el uso de un patrón de diseño “singleton”, que permite crear una única instancia de un objeto y compartirla entre diferentes partes de la aplicación.

Para crear un singleton del cliente HTTP en Go, se puede utilizar una variable global y una función para inicializar y obtener la instancia del cliente. Por ejemplo:

package main

import (
"net/http"
)

var client *http.Client

func init() {
client = &http.Client{}
}

func GetClient() *http.Client {
return client
}

Luego, en cualquier parte de la aplicación donde se necesite hacer una petición HTTP, se puede utilizar la función GetClient() para obtener la instancia del cliente. Por ejemplo:

package main

import (
"fmt"
"io/ioutil"
)

func main() {
resp, err := GetClient().Get("http://example.com")
if err != nil {
// manejar el error
}
defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
// manejar el error
}
fmt.Println(string(body))
}

Con estas mejoras nuestra API mejoro muchisimo ya que de tener 20 pods bajo drasticamente a 7 y posteriormente a 5 que es el minimo de pods que se crean por default para nuestra aplicación.

Reducción del numero de pods.

Conclusión y recomendaciones finales

En conclusión, es importante la optimización del uso de recursos en aplicaciones desarrolladas en Go, especialmente en el manejo de peticiones HTTP. Cerrando las peticiones y utilizando un cliente singleton, se puede mejora significativamente el rendimiento y evitar problemas como el agotamiento de los recursos del sistema. Además, el aumentar los recursos de los pods en Kubernetes puede ser una solución temporal, pero es esencial detectar y resolver el problema principal para evitar que los pods escalen rápidamente y consuman toda la memoria disponible.

Espero que este post haya sido útil para entender mejor cómo optimizar el uso de recursos en aplicaciones Go. Muchas gracias por leer.

--

--

Sirpyerre Rojas

Desarrollador backend apasionado, especializado en Go y PHP. Siempre intentando mejorar mi código. Disfruto pasar tiempo con mi esposa e hijos.