Desarrollo

Arréglalo hasta que funcione: una guía sencilla de performance en Ruby on Rails

· 7 min de lectura

En mi experiencia trabajando con varias apps de Rails en producción estos últimos años, encontré algunas reglas prácticas para corregir un endpoint lento y herramientas que ayudan a prevenir este tipo de problemas incluso antes de que el código llegue a producción.

Cuando una app de Rails corre en producción, el cliente, el usuario o quien sea casi siempre te dice lo mismo: «ESTO ESTÁ LENTÍSIMO». Entonces toca parar, medir, corregir y apagar el incendio antes de que sea tarde. ¿A quién le gusta una llamada a medianoche un viernes de un cliente o un jefe gruñón diciendo que la app está lenta o que no funciona? Exacto: a nadie. Así es como he aprendido a lidiar con esto.

Conoce a tu enemigo

Primero hay que conocer al enemigo para saber qué te puedes encontrar en la selva de apps Rails que desarrollas tú o que escribió alguien más. Los problemas que más te roban tiempo suelen ser:

  • Tú mismo (por no usar herramientas que podrían prevenir el error)
  • Tareas que deberían correr en background
  • Acceso de red (llamadas pesadas a APIs externas)
  • Falta de caché o de memoization
  • Operaciones de base de datos (el famoso N+1 o foreign keys sin índice)

Suelen ser justo los errores que nos meten en problemas, y son fáciles de pasar por alto cuando hay prisa por entregar, cuando sale un hotfix o por falta de experiencia.

Prepara tus armas secretas

Estas son algunas herramientas básicas que incluyo en los proyectos para detectar y prevenir errores que terminan en endpoints lentos.

HERRAMIENTAS DE ACTIVE RECORD

Acceder a la base de datos o procesar un archivo grande puede tardar. Estas gemas ayudan a identificar problemas antes de que aterricen en producción.

1) Bullet

gem 'bullet' es una de las soluciones más populares y fáciles para eliminar las odiosas consultas N+1. Puede mejorar la velocidad de forma drástica. Con mil registros quizá ni lo notes; con cientos de miles, se vuelve un problema serio.

2) Rails Panel

Si usas Google Chrome o Firefox, hay una extensión que te muestra el desglose del tiempo en rendering, ActiveRecord, caché, etc. Más info aquí.

Es importante agregar gem 'meta_request' al Gemfile para que los datos aparezcan en Rails Panel después de instalar la extensión.

3) Rack Mini Profiler

gem 'rack-mini-profiler': al instalarla y recargar el servidor verás un popup en la esquina superior izquierda. Al hacer clic tienes el detalle de cuántas peticiones se completaron para renderizar la página y cuánto tarda cada una. Sirve para entender cómo está rindiendo la app.

4) Active Record Query Trace

gem 'active_record_query_trace' es para quien vive en la consola: incluye el SQL crudo para detectar queries lentas en los logs y te dice qué método las generó.

5) Active Record Doctor

gem 'active_record_doctor' detecta foreign keys sin índice. Es muy común que, al crear asociaciones has_many a través de una tabla intermedia, se escapen algunos índices.

HERRAMIENTAS DE CONOCIMIENTO

No toda mejora de velocidad la detecta una gema. Estos patrones también ayudan si se usan con criterio.

1) Active Record

El ORM es una herramienta increíble que nos quita mucha carga. ¿A qué costo? Hay que entender lo básico y conocer SQL para no perderte el scope que hace más rápida la app.

2) Falta de caché

Cachear es una forma efectiva de mejorar los tiempos de respuesta. Puede ser tan simple como cachear una respuesta JSON de tu API. Rails trae funcionalidad lista para usar. Para conocer los tipos de caché, visita esta guía.

# Example of a low level caching on a model class
def self.cached_find(id)
  Rails.cache.fetch("#{name}/#{id}", expires_in: 12.hours) do
    find(id)
  end
end

3) Memoization

La memoization guarda un valor ya calculado para no repetir el trabajo en llamadas siguientes. Por ejemplo current_user o current_account: identificas el registro una vez y lo reutilizas.

# Memoization example on a current_account access
def current_account
  @current_account ||= current_user.accounts.find(session[:account_id])
end

4) Falta de background jobs

Un error clásico es no mandar a background el trabajo que deja la cola o el request esperando. Una regla práctica: si tienes que llamar una API o un web service externo, separa la lógica en pasos.

HERRAMIENTAS DE MÉTRICAS

Producción siempre será la mejor maestra. Cuando entran usuarios reales aparecen errores que no se previenen sin experiencia previa. Necesitamos enterarnos a tiempo y estar listos para actuar.

1) Datadog / New Relic

Datadog y New Relic hacen casi lo mismo; la elección suele ser precio y funcionalidades extra. Dan un análisis profundo del performance de la app en producción y cómo se comporta con tráfico real.

2) Rollbar

Cuando algo falla, quieres saber dónde pasó y qué hacía el usuario. Rollbar monitorea errores en tiempo real, te da el stack completo y una UI para ver qué ya se corrigió y qué sigue pendiente.

Mis pasos para depurar

Estas herramientas y este conocimiento ayudan a construir una app rápida, de la que te sientas orgulloso y que el cliente ame. ¿A quién no le gusta una app que carga en segundos? Google encontró que el 53% de los visitantes móviles se va si una página no carga en tres segundos. ¡TRES SEGUNDOS! Por eso importa el performance: te puedes quedar sin clientes.

Después de saber qué herramientas usar, mis pasos para depurar empiezan por perfilar y buscar dónde se frena la app.

  1. Revisa la vista HTML o, si es una API, la respuesta JSON, y mira si puedes mejorarla con caché. Lo más común que he visto es que algunas APIs no tienen caché en absoluto.
  2. Busca llamadas a APIs externas que puedan irse a un background job.
  3. Revisa partials largos. Si estás mejorando una vista HTML, empieza por el partial más lento. Si es un endpoint JSON, mira cómo se genera: jbuilder, Active Model Serializer o fast_jsonapi tienen enfoques distintos, pero todos renderizan partials al anidar asociaciones.
  4. Carga la vista y revisa en consola con Bullet si hay N+1. Rails Panel o Mini Profiler también dan pistas.

Conclusión

Aprendí por las malas… Espero que esto te sirva para aprender de los errores de alguien más y evitar que tu app de Rails se caiga. Casi todos nos hemos sentido orgullosos de ver el código en vivo; cuando llega tráfico real y cientos de usuarios al mismo tiempo, pagamos el precio de los mismos errores. Yo ya pagué el de ser novato. Toma nota y happy coding!