Abel Muiño

home

Ruby on Rails no es lento, es que eres cortito

13 Oct 2010

Uno de mis mayores problemas con Ruby on Rails es que un gran poder conlleva una gran responsabilidad.

En otras palabras, es muy fácil cagarla pensando que estás haciendo las cosas bien, usando DSLs elegantes o fantásticos one-liners dónde otros lenguajes usarí­an un par de decenas de lí­neas.

Pero eso era antes, cuando trabajaba en un consultoras dónde lo raro es encontrar gente con interés por su trabajo (y ganas de ser mejor en él). Ahora que soy autónomo, he desarrollado un cariño especial hacia él. Eso si, amigos que os conformáis con el programador medio: sigo sin recomendaros hacer proyectos con RoR :-).

Centrémonos.

Hoy voy a hablar de cómo cagarla en performance: ¡toca un ejemplo!

Vamos a averiguar si un usuario tiene alguna factura pagada. A nivel de base de datos es una relación 1-N entre dos tablas (`users` y `bills`) dónde `bills` tiene una columna indicando la fecha de pago… si es no-nula entonces está pagada.

Cuatro opciones, usando Rails3:

rails benchmarker 1000 \
> 'User.find(2).bills.paid.any?' \
> 'User.find(2).bills.paid.exists?' \
> 'Bills.paid.where(:user_id => 2).any?' \
> 'Bills.paid.where(:user_id => 2).exists?'
            user     system      total        real
#1      2.360000   0.090000   2.450000 (  2.682265)
#2      2.250000   0.090000   2.340000 (  2.536643)
#3      1.240000   0.040000   1.280000 (  1.407173)
#4      0.970000   0.040000   1.010000 (  1.104962)

Las dos primeras sentencias son las más lentas porque Rails ejecuta una consulta para encontrar al usuario antes de buscar las facturas (aunque, en teorí­a, tiene información suficiente para no hacerlo).

La diferencia entre el uso de `any?` y `exists?` es que el primero ejecuta un `count(*)`, obligando a la base de datos a trabajar sobre todas las facturas pagadas, mientras que `exists?` simplemente intenta recuperar el `id` de una factura pagada (la primera que la BD tenga a mano), limitando la cantidad de datos que se mueven entre la BD.

La diferencia entre el caso más rápido y el más lento es de, aproximadamente, x2.5 veces (con mis muy escasas filas en BD).

Si este patrón se repite en toda la aplicación, podemos estar hablando de casi triplicar el coste en servidores… para una aplicación «tipo intranet» puede ser tolerable, pero si tu aplicación está en internet con miles/millones de usuarios, tus costes se disparan sólo porque el programador eligió mal el one-liner.