viernes, 26 de junio de 2009

Optimización de consultas en rails

Cuando en rails se hace una consulta a la base de datos sobre una tabla que contiene mucha información, no todo el tiempo se pierde en la consulta. Una parte importante del tiempo se pierde en la creación de cada objeto ActiveRecord.

Usando directamente el método select_all de la conexión ActiveRecord devolverá un array de hash, ahorrando el tiempo de construcción de objetos.

Benchmark.bm{|x| x.report{100.times{BigTable.find(:all)}}}
user system total real
10.090000 2.090000 12.180000 ( 19.199150)

Benchmark.bmbm{|x| x.report{100.times{BigTable.connection.select_all('select * from big_table')}}}
user system total real
7.350000 2.080000 9.430000 ( 16.646406)

En general, no es buena idea guardar todos los elementos de una tabla grande en un array, pero aún seleccionando tan solo el índice se obtiene una mejora sustancial.

Benchmark.bm{|x| x.report{100.times{BigTable.find(:all, :select => :id)}}}
user system total real
0.610000 0.010000 0.620000 ( 0.707702)

Benchmark.bmbm{|x| x.report{100.times{BigTable.connection.select_all('select id from big_table')}}}
user system total real
0.080000 0.000000 0.080000 ( 0.161878)

sábado, 20 de junio de 2009

Crear extensiones de ruby en c

Crear extensiones en C del lenguaje ruby es bastante sencillo.

En primer lugar hay que crear un fichero para extconf, este nos generará el fichero makefile de forma automática.

En el fichero extconf.rb debería haber algo como:

#!/usr/bin/env ruby

require 'mkmf'
create_makefile("ruby1")

Donde ruby1 es el nombre de la extensión.

En segundo lugar hay que crear la extensión en código C, simplemente hay que incluir la cabecera ruby.h.

La función Init_ruby1 es la que se ejecutará al importar la extensión desde código ruby.

#include "ruby.h"

static VALUE c_printf(VALUE self, VALUE anObject){
printf("%s\n", STR2CSTR(anObject));
return Qnil;
}

static VALUE c_init(VALUE self){
return self;
}


VALUE FromC;

void Init_ruby1() {
rb_define_global_function("printfc", c_printf, 1);
FromC = rb_define_class("FromC", rb_cObject);
rb_define_method(FromC, "initialize", c_init, 0);
rb_define_method(FromC, "printfc", c_printf, 1);
}

En este ejemplo se define una función global printfc, se define la clase FromC, el método de instancia printfc de esa clase.

Para compilar e instalar este código hay que ejecutar ruby extconf.rb, make y make install.

Ya deberíamos tener disponible la librería ruby1, al ejecutar require 'ruby1' podremos ejecutar las funciones de nuestro código en C.

Hay una gran cantidad de funciones disponibles para crear extensiones en la siguiente dirección: http://www.rubycentral.com/book/ext_ruby.html

miércoles, 3 de junio de 2009

Definición de named_scopes dependientes del tiempo actual

Si tenemos una clase ActiveRecord como la siguiente:

class Post < ActiveRecord::Base
named_scope :test, :conditions => ['updated_at < ?', Time.now]
end

En modo development funciona sin problemas, sin embargo en modo production la consulta que se genera no es la esperada.

Al arrancar rails en modo production se cachean las clases, al cargar este modelo en concreto se evalúa Time.now haciendo que para todas las consultas se coja como fecha la hora en la que se ha cargado el modelo.

El descrito anteriormente no es el comportamiento que esperaríamos al definir el named_scope, sino que debería obtener la fecha y hora actual para cada consulta, no en la definición.

Revisando la definición de los named_scopes nos encontramos lo siguiente:

def named_scope(name, options = {}, &block)
name = name.to_sym
scopes[name] = lambda do |parent_scope, *args|
Scope.new(parent_scope, case options
when Hash
options
when Proc
options.call(*args)
end, &block)
end
(class << self; self end).instance_eval do
define_method name do |*args|
scopes[name].call(self, *args)
end
end
end


En nuestro caso pasamos un hash como opciones, por lo que efectivamente se evaluará el Time.now solo en la definición de dicho hash.

Si en lugar de eso definimos un objeto de tipo Proc se evaluará cada vez.

Por lo tanto para arreglar el comportamiento del primer ejemplo deberíamos escribirlo como:

class Post < ActiveRecord::Base
named_scope :test, lambda{{:conditions => ['updated_at < ?', Time.now]}}
end