La Coctelera

in web we trust Fernando Blat's blog, freelance web developer

Categoría: Ruby on Rails

18 Agosto 2008

Generating static maps with attachment_fu and static-gmaps gem

Google Maps is, may be, the coolest service for integrating maps in your web applications: it offers a powerful and easy API with no limit usage.

The problem is that it loads quite Javascript that has to connect to the main Google Maps page in order to draw our map. In order to avoid that we can use static Google Maps, it is, a picture (generated in real time) of the map we want to visualize. The problem with this is that you can't interact with the map (because it is an image) and that there is a limitation per day. But may be you find it useful in a particular moment.

For example, in iwannagothere.net we have a small map in every place that shows the location of that city:

In that situation we can change the map because the map is not for navigating along the city. For that you have a map in each item.

In Ruby on Rails, the programming framework of iwannagothere.net there is a gem called static-gmaps that does the hard work for you. What's more, we have found the way to integrate this maps with attachment_fu, in order you save a copy of the map in your filesystem and then avoid the day limitation usage.

Basically we have an attachment_fu kind of model (nothing special here):

class PlaceMap < ActiveRecord::Base
   belongs_to :place
 
   has_attachment  :content_type => :image, 
                   :storage => :file_system,
                   :processor=> :rmagick,
                   :path_prefix => 'public/userfiles/place_maps', 
                   :max_size => 1.megabyte
 
   validates_as_attachment  
 end
 

The we generate the static map instance:

	
 map = StaticGmaps::Map.new :center   => [ place.lat, place.long ],
                             :zoom     => 12,
                             :size     => [ 334, 144 ],
                             :key      => APP['gmkey']  
 

And save it in our PlaceMap model:

 PlaceMap.create(:uploaded_data => (open(map.url)), :place_id => place.id, :content_type => 'image/gif')
 

The problem here is that we are not uploading a file in the usual way: we are trying to save a raw of data. Attachment_fu doesn't know how to do that, so we have to apply a little patch:

 module Technoweenie
   module AttachmentFu
     module InstanceMethods
 
       def uploaded_data=(file_data)
         return nil if file_data.nil? || file_data.size == 0
         self.content_type = file_data.content_type
         self.filename     = Time.now.to_i.to_s+'.gif'
         if file_data.is_a?(StringIO)
           file_data.rewind
           self.temp_data = file_data.read
         else
           self.temp_path = file_data
         end
       end          
 
     end
   end
 end
 

Here we are giving a random filename, generated from the current time.

And that's all, really easy :)

9 Junio 2008

Comprobar si una relacíon contiene un elemento

Muchas veces cuando trabajamos con ActiveRecord nos olvidamos / ignoramos qué hace por debajo.

Un claro ejemplo lo encontramos cuando tenemos una relación :has_many y queremos comprobar si un elemento está incluída en dicha relación.

Por ejemplo:

 class Place < ActiveRecord::Base
   has_many :items
 end
 
 class Item < ActiveRecord::Base
   belongs_to :place
 end
 

Seguro que más de una vez, para comprobar que un place contiene un ítem hemos utilizado el "rubysta" include?:

 place.items.include?(item)
 

Pensándolo fríamente 0.2 segundos nos damos cuenta de que estamos cargando un array con todos los elementos de una relación, que pueden ser 10 o pueden ser 1.000.000. Y ya sabemos lo generoso que es Ruby con la memoria que toma y que nunca deja ir.

Un truco para evitar que esto suceda sin dejar de utilizar include? es definir dicho método dentro de la relación tal que así:

 class Place < ActiveRecord::Base
   has_many :items do 
     def include?(item)
       count(:conditions => ["item_id = ?", item.id]) > 0
     end
   end
 end
 

Es decir, resolvemos el problema con un simple COUNT de Mysql.

¿Alguien se anima a hacer un plugin? Creo que sería bastante inmediato para cuando se cumplen las convenciones y sabemos que la clave foránea de la relación es singular del nombre de la relación_id. O quizá ni eso, si encontramos la forma de preguntarle a ActiveRecord cuál es la clave foránea de una relación.

5 Junio 2008

Visualizar en el log de producción las querys lentas

Cuando tienes una aplicación en producción es más que necesario controlar los tiempos de las peticiones más lentas.

Sin embargo muchas veces con el log de Rails no es suficiente, y hay que alcanzar un mayor nivel de detalle.

El log en modo debug nos da todo el detalle que necesitamos, pero mostrando demasiada información (¿de qué nos sirve saber que un SELECT sencillo tarda 0.001 segundos?).

Pero si en ese log pudiéramos filtrar las consultas SQL más lentas sí que tendríamos información de consultas lentas asociadas a cada petición al servidor.

Gracias al plugin query trace y a una pequeña modificación podemos mostrar qué consultas tardan más de 1 segundo:

 module ActiveRecord
   module ConnectionAdapters
     class AbstractAdapter
       def log_info(sql, name, runtime)
         return unless @logger
         @logger.info(
           format_log_entry(
             "#{name.nil? ? "SQL" : name} (#{sprintf("%f", runtime)})",
             sql.gsub(/ +/, " ")
           )
         ) if (RAILS_ENV == 'production' && runtime > 1.0) || RAILS_ENV != 'production'
       end
     end
   end
 end
 

Este código lo he probado en Rails 1.2.x, no sé si funcionará en otras versiones.

24 Febrero 2008

Límites en claves y valores de Memcached

Una cuestión que hay que tener presente a la hora de trabajar con Memcached y que no se suele tener en cuenta es el tamaño de las claves y los valores:

  • claves: máximo de 250 caracteres
  • valores alojados: máximo 1 Megabyte

Es muy difícil que una aplicación Rails las claves generadas a partir de las URL's tengan tanto tamaño, pero puede darse el caso y hay que tenerlo presente.

En el grupo de discusión acts_as_cached esta semana han propuesto hashear (con MD5, por ejemplo) las claves antes de guardarlas para curarnos en salud.

30 Noviembre 2007

Caché en Rails

Rompo este silencio para subir una de mis charlas de la Conferencia Rails :

Caché en Rails

Pronto, todas las demás, colgadas en la web de la Conferencia,

3 Septiembre 2007

Un pequeña benchmark sobre servidores web + aplicación con Rails y Merb

Testing Various Configurations of Rails, Merb, Swiftiply, and Nginx es una más que interesante comparativa entre el clásico Mongrel y una alternativa que ha surgido hace poco Swiftiply: un servidor proxy que además, nos regala una variante de Mongrel, el evented Mongrel, totalmente compatible, pero que basa su funcionamiento en gestión de eventos (EventMachine), en lugar de utilizar los clásicos threads.

Además, en el post utilizan Nginx como servidor web y Ruby on Rails y Merb, como frameworks de desarrollo, así como Memcached para almacenar sesiones.

Los resultados, muy interesantes:

  • Merb es mucho más rápido que Rails, a costa de perder parte de la magia
  • Nginx es una gran alternativa a Apache, sobretodo cuando la memoria es un recurso escaso (en mi nuevo Slicehost, por ejemplo)
  • y, el más importante, el evented Mongrel funciona bastante mejor que el Mongrel en situaciones de alta concurrencia

Así que si te gusta "jugar" y quieres probarlo, lo recomiendo: la instalación es muy sencilla y los resultados saltan a la vista.

18 Agosto 2007

Sobre httperf y las pruebas de carga

Andaba yo viendo esta mañana el screencast de Peepcode Benchmarking with httperf bastante emocionado (ya que la fama precede a estos screencasts) para ver cómo podía mejorar las pruebas de rendimiento que estoy haciendo contra una aplicación Rails que he instalado en mi recién adquirido Slicehost.

La situación es la siguiente: es una cuenta Slicehost de 256Mb de memoria, en el que tú instalas lo que quieres. El problema es que debes de instalar un servidor web, los servidores de aplicaciones y la base de datos, y todos ellos han de luchar por esos 256Mb. Así que al final me he decidido por instalar esto:

  • servidor web: Nginx, que tenía ganas de probarlo tras leer a uno de los sysadmins decir que Nginx era lo más estable que había visto nunca, y que recomendaba usar Apache sólo en el caso de querer que un mismo servidor web compartiera diferentes aplicaciones y en distintos lenguajes (PHP y Rails, por ejemplo). Además, su uso de memoria es ridículo.
  • servidor de aplicaciones: Mongrel. Considerando los 60Mb que viene a consumir un Mongrel de memoria, el número de Mongrels en el clúster lo he establecido a 3.
  • servidor de base de datos: MySQL. No había otra opción.

Además de todo esto, hay un servidor Memcached de 32Mb para guardar las sesiones.

Y claro, quería hacer pruebas de carga contra el stack completo para ver de rendimiento qué tal.

Las pruebas se han realizado contra una página sin caché y con bastantes consultas a la base de datos, cuyo tiempo de respuesta medio es de 0.005 segundos o lo que es lo mismo, unas 20 req/s.

El screencast

El screencast se queda algo corto en cuanto a uso de la herramienta httperf, ya que el autor pierde mucho tiempo explicando conceptos teóricos y muy básicos de Estadística: media y desviación estándar, así como cómo mostrar luego los resultados a través de gráficas (unas muy bonitas generadas con gruff).

Posteriormente las pruebas las realiza contra un único Mongrel, explicando con todo detalle:

  • la importancia de utilizar datos reales: la muestra debe de ser representativa para que los resultados lo sean
  • la importancia de obtener muchas muestras: cuantas más muestras, la media y la varianza son más significativas también
  • la importancia de anotar todos los resultados
  • la importancia de la interpretación de los mismos en función de los resultados obtenidos y entender qué significan

Las pruebas las realiza para comparar el rendimiento de su aplicación sin usar caché, utilizando caché de acción y utilizando caché de página. Así que establece el baseline en los resultados obtenidos sin utilizar caché y a partir de los obtenidos utilizando caché establece porcentajes de mejora.

Otros aspectos a tener en cuenta

Hay otras muchas situaciones en las que es recomendable hacer pruebas de rendimiento contra tu propia aplicación:

  • calcular el número de Mongrels a lanzar en el servidor de aplicaciones. Una buena explicación (aunque breve también), la da Zed en la página de Mongrel: How many Mongrels
  • comparar el rendimiento entre distintos sistemas de almacenamiento de sesiones
  • comparar distintos algoritmos de balanceo en el módulo de proxy
  • determinar el tope máximo de ficheros servidos a través de una unidad NFS
  • ...

En estos casos es posible que nos interese conocer algún parámetro más de httperf que los que se han explicado en el vídeo:

  • --rate: determinar el número de peticiones concurrentes lanzadas en un segundo
  • --wsess: determina cada cuántas peticiones se inicia una nueva sesión

Y además, si vais a hacer pruebas de carga, hay que tener en cuenta estas perogrulladas, que a veces no lo son tanto:

  1. el número de Mongrels en el clúster
  2. la conexión: ¡no hagas las pruebas con un ADSL! En mi caso he pasado de 30 req/s a más de 50 req/s sólo por lanzar las pruebas desde una máquina con una conexión decente
  3. testea el stack entero: sobre todo porque es interesante ver si hay problemas de conectividad entre una capa (web) y la otra (aplicación), o si el proxy que reparte la carga está mal configurado, etcétera

13 Agosto 2007

Trabajando con BackgroundDRB en el entorno de test

BackgrounDRB es un proyecto en desarrollo que pretende crear un servicio estable para ejecutar tareas en segundo plano, sin que estas entorpezcan la navegación del usuario por la aplicación, relentizando sus tiempos de respuesta.

La verdad es que era un completo desconocido para mí hasta que encontré la presentación de Sergio Espeja en la conferencia de Rails: Tareas en background con RoR y BackgrounDRb.

En este año que ha pasado, hay una nueva versión que corrige algunos fallos y deja otros abiertos, pero que es, dicen, bastante estable. Y la verdad es que con la anterior tuve muchos problemas para crear mis workers pero con esta estoy encantado.

Pero este post no era para hablar de las bondades del proyecto, que son muchas, sino para contar cómo hacer que en los tests no necesitemos tener lanzado un servidor BackgroundDRB para que estos funcionen, sino que podemos utilizar un mock.

He encontrado uno entre los tickets del proyecto, y que se ve que aún no está incluído en la última versión.

Basta con descargarlo en test/mocks/test e incluir la siguiente línea en el test_helper.rb:


 require File.dirname(__FILE__) + '/mocks/test/backgroundrb_mock.rb'
 

Y voilá, tests funcionando de nuevo.