Logo de La Coctelera

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.

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.

Si trabajáis con control de versiones una de las operaciones más habituales es ver las modificaciones de un fichero. Eso en Subversion se hace con svn diff.

Sin embargo yo utilizo un script que creo que me pasó porras para que me muestre en colores las modificaciones.

El script es este:


#!/usr/bin/env ruby

`svn diff #{ARGV.join(' ')}`.each do |line| puts( if line =~ /^\+(.*)$/ "\e[32m#{$&}\e[0m" elsif line =~ /^-(.*)$/ "\e[31m#{$&}\e[0m" else line end ) end

Con darle permisos de ejecución ya podéis tener un svndiff por ejemplo.

No pongo el equivalente en git porque es bastante inmediato.

  • 6 comentarios compártelo favorito
  • Tags:

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.

  • 3 comentarios compártelo favorito
  • Tags: ,

Reconozco que me ha constado dios y ayuda dar con la regla adecuada, así que por eso me he decidido a postearla.

El problema es el siguiente: en unvlog.com (sí, ese proyecto del que no he hablado) somos muy Rails y tenemos para todos los recursos que tiene sentido una ruta que responde a un formato u otro según se le solicite pero con esta forma:

En concreto, esta URL corresponde al feed de un blog, feed que está cacheado en disco gracias a la caché de página de Rails. Pues bien, la idea es que el servidor web sirva directamente esta caché, así que una regla podría ser:


  if (-f /cache/$request_filename) {
          rewrite (.*) /cache/$1 break;
  }

(Esto, asumiendo que tenemos la caché en public/cache)

El problema es que esta regla no funciona, y creo que se debe a que blat.atom lleva un '.' y no es capaz de asignar la URL a la variable $request_filename.

Al final ha sido tan fácil como utilizar otra variable que he encontrado en la parte rusa del wiki, $request_uri:


  if (-f $document_root/cache/$request_uri) {
          rewrite ^/(.*).atom$ /cache/$1.atom break;
  }

Con esto ha funcionado perfectamente y ya tenemos cacheados y servidos por Nginx los feeds en unvlog.com.

  • 2 comentarios compártelo favorito
  • Tags: ,

Memcached (o Memcache) incorpora un sistema de estadísticas interno que nos permite conocer el estado del demonio, su capacidad restante de almacenamiento, uso, conextiones, etcétera.

Para acceder a él a través del cliente de Ruby, por ejemplo, basta con cargar una consola en el entorno deseado e invocar al método stats del objeto de caché.

Por ejemplo, si nuestra configuración es la siguiente:


  CACHE = MemCache.new 'localhost:11211', :namespace => 'wadus_fragments'

Podemos utilizar el objeto CACHE de la siguiente manera:


  pp CACHE.stats
  {"bytes"=>746532339,
   "pid"=>10303,
   "connection_structures"=>19,
   "time"=>1200349556,
   "limit_maxbytes"=>1073741824,
   "cmd_get"=>689486,
   "version"=>"1.1.12",
   "bytes_written"=>3296488315,
   "cmd_set"=>265986,
   "get_misses"=>265168,
   "total_connections"=>1238,
   "curr_connections"=>17,
   "curr_items"=>66977,
   "uptime"=>56490,
   "get_hits"=>424318,
   "total_items"=>265986,
   "rusage_system"=>95.012555,
   "rusage_user"=>17.16339,
   "bytes_read"=>3226552602}

La interpretación de todos estos valores se puede encontrar en la definición del protocolo:

  Name              Type     Meaning
  ----------------------------------
  pid               32u      Process id of this server process
  uptime            32u      Number of seconds this server has been running
  time              32u      current UNIX time according to the server
  version           string   Version string of this server
  pointer_size      32       Default size of pointers on the host OS
                             (generally 32 or 64)
  rusage_user       32u:32u  Accumulated user time for this process 
                             (seconds:microseconds)
  rusage_system     32u:32u  Accumulated system time for this process 
                             (seconds:microseconds)
  curr_items        32u      Current number of items stored by the server
  total_items       32u      Total number of items stored by this server 
                             ever since it started
  bytes             64u      Current number of bytes used by this server 
                             to store items
  curr_connections  32u      Number of open connections
  total_connections 32u      Total number of connections opened since 
                             the server started running
  connection_structures 32u  Number of connection structures allocated 
                             by the server
  cmd_get           64u      Cumulative number of retrieval requests
  cmd_set           64u      Cumulative number of storage requests
  get_hits          64u      Number of keys that have been requested and 
                             found present
  get_misses        64u      Number of items that have been requested 
                             and not found
  evictions         64u      Number of valid items removed from cache                                                                           
                             to free memory for new items                                                                                       
  bytes_read        64u      Total number of bytes read by this server 
                             from network
  bytes_written     64u      Total number of bytes sent by this server to 
                             network
  limit_maxbytes    32u      Number of bytes this server is allowed to
                             use for storage. 
  threads           32u      Number of worker threads requested.
                             (see doc/threads.txt)

En concreto cuatro parámetros me han parecido interesantes de observar:

  • el parámetro bytes junto con el de limit_maxbytes: el primero representa el total de bytes ocupados actualmente, frente el máximo.
  • el número de fallos de caché que refleja el valor del parámetro get_misses junto con el número de aciertos get_hits

Los primeros nos permiten saber si hemos llegado al límite el almacenamiento que necesita nuestra aplicación y el segundo nos permite saber si nuestra política de caché es adecuada: diviendo hits entre misses obtenemos la proporción de fragmentos encontrados frente a fragmentos que ya no existían. Un valor de 1 indica que por cada dos búsquedas, una tiene éxito y otra no.

Por supuesto un valor inferior a uno muestra que la caché no sirve de nada (indicaría por ejemplo que necesitaríamos ampliar el Memcache o que tenemos una política de borrado "exagerada").

  • 1 comentario compártelo favorito
  • Tags: , ,

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,

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.

Sobre in web we trust

Avatar de Fernando
Valencia y Madrid, España
ver perfil »
contacto »
Blog personal de Fernando Blat, desarrollador en The Cocktail, sobre programación web y Ruby on Rails. Puedes seguir a "mi otro yo" en blat.