13
Feb
09

nos mudamos

Después de ver que el blog iba tirando para delante (poco a poco…) pensamos darle un poco más de oficialidad al asunto, y de paso hacer algunas mejoras tanto visuales como técnicas al blog, por lo que finalmente nos mudarnos a otro blog alojado en nuestros propios servidores.

Para todos aquellos que nos sigan, a partir de ahora os esperamos en el nuevo blog técnico de 11870.com por lo que será necesario actualizar los feeds, delicious o lo que usemos.

Saludos y os esperamos en el nuevo blog.

31
Ene
09

Temporary tables y replicación a esclavos

El título de esta entrada debería ser más bien «Cómo perder tus servidores esclavos haciendo el tonto» y ahora veremos porqué.

Supongamos que estamos haciendo tareas de mantenimiento en la base de datos, supongamos que tenemos que hacer un UPDATE sobre algunas filas de determinada tabla (area mismo). Con la particularidad de que determinar sobre qué filas tenemos que hacer el UPDATE no lo podemos hacer con una única query y decidimos hacer uso de las temporary tables o tablas temporales de MySQL. Las tablas temporales son tablas MyISAM que sólo perduran durante sesión en la que estamos conectados (si nos desconectamos y conectamos de nuevo, hemos perdido la tabla), lo que las hace especialmente útiles para estas tareas de mantenimiento.

Así que creamos la tabla:

mysql> CREATE TEMPORARY TABLE idsChungos (id integer primary key);
Query OK, 0 rows affected (0.00 sec)
mysql>

Y ahora vamos insertando los ids de las filas que queremos tocar:

mysql> INSERT INTO idsChungos VALUES (SELECT id FROM area WHERE …);

mysql> INSERT INTO idsChungos VALUES (SELECT id FROM area WHERE …);

mysql> INSERT INTO idsChungos VALUES (SELECT id FROM area WHERE …);

Justo en este ejemplo no se ve la necesidad de utilizar una tabla para almacenar los ids, pero no podemos usar una subquery sobre la misma tabla sobre la que vamos a hacer el UPDATE (aplica también para DELETEs).

Y ahora que ya tenemos los ids:

mysql> UPDATE area SET boundaries = NULL WHERE id IN (SELECT id FROM idsChungos);
Query OK, 7263475 rows affected (0.00 sec)
mysql>

Y nos ha ido todo a la perfección, salvo porque 30 segundos después empiezan a saltar las alarmas de servidores MySQL esclavos perdidos. En ese momento es cuando dices: aaaahhhhh y te pegas una palmadita en la frente: las tablas temporales NO se replican a los servidores esclavos. Cuando el esclavo intenta hacer el último UPDATE dice: «la tabla area no sé ni lo que es» y se para la replicación. Pensándolo dos minutos, tiene todo el sentido del mundo.

Un diez oye, pero había que aprenderlo de alguna manera. Menos mal que podemos recuperar los esclavos en caliente.

18
Ene
09

Cómo crear un comando para ubiquity

Ubiquity es una extensión de Firefox desarrollada por Mozilla que permite ejecutar verbos o comandos al estilo de un lanzador de aplicaciones de escritorio como QuickSilver o Gnome-Do. Estos comandos son los que añaden funcionalidad a Ubiquity, y los que deben especificarse para realizar alguna acción. Se pueden programar sin necesidad de reiniciar el navegador en ningún momento, a diferencia de lo que ocurre cuando se programan extensiones para firefox, pudiendo probar los cambios inmediatamente. No hay más que utilizar el Command Editor que proporciona Ubiquity al que se puede acceder a través del comando help.

El esqueleto básico de un comando el el siguiente:

CmdUtils.CreateCommand({
     name: "11870",
     icon: "http://11870.com/favicon.ico",
     takes: {"search term": noun_arb_text},
     preview: function(pblock, search_term) {
          ...
     },
     execute: function(search_term) {
          ...
     }
})

CmdUtils es un espacio de nombres que proporciona Ubiquity y contiene todas las herramientas disponibles para crear un comando. Para ello deberemos ejecutar el método CreateCommand pasándole un hash que deberá contener los siguientes elementos:

  • name: Nombre del comando, se usará cuando se quiera ejecutar.
  • icon: Favicon del comando, aparecerá al lado del nombre cuando se ejecuta.
  • takes: Parámetros del comando. Es un hash parámetro, tipo de datos. Esto lo explicaremos más abajo.
  • preview: Método que se ejecutan mientras se están escribiendo los parámetros a el comando. Esta función recibe 2 parámetros. El primero es el nodo DOM donde debe insertarse el resultado, y el segundo es el parámetro que ha introducido el usuario, en nuestro caso un término de búsqueda.
  • execute: Método que se ejecuta cuando pulsamos enter sobre el comando. Sólo recibe el parámetro.

Otro punto interesante son los modificadores o modifiers que nos permiten recibir más parámetros en el comando especificando una palabra clave a partir de la cual vendrá el modificador. Por ejemplo, cuando un usuario escriba:

11870 restaurante en madrid

la palabra clave “en” identificará a madrid como un modificador y se recibirá en las funciones preview y execute como un nuevo parámetro. Para especificar un modificador en nuestro comando deberemos añadir la siguiente linea:

modifiers: {"en": noun_type_ooslocality},

donde “en” es la palabra clave y noun_type_ooslocality es el tipo de dato que se va a recibir. También deberemos añadir un nuevo parámetro a preview y execute donde recibir los modificadores. Este parámetro es un hash donde la clave es el modificador.

Estos tipos de datos también son personalizables y se pueden crear fácilmente siguiendo la siguiente estructura:

var noun_type_ooslocality = {
     _name: "localidad",
     suggest: function(text, html){
          ...
          return return [ CmdUtils.makeSugg(text, null, date) ];
     }
}

Cuándo el comando recibe como parámetro un tipo de datos lo primero que hace es ejecutar el método suggest, el cual devuelve una lista con todos los posibles valores para esa entrada. En nuestro caso hemos creado un noun_type llamado noun_type_ooslocality que, a través de la API de 11870, comprobará las localidades que disponemos que se corresponden con la entrada y se mostrará al usuario una lista para que pueda concretar su búsqueda.

Ubiquity también proporciona:

  • Geolocalización: Podemos obtener nuestra localización actual usando CmdUtils.getGeoLocation() para lo cual actualmente usa la API de MaxMind.
  • JavascriptTemplates: Que nos facilita la tarea de generar los preview, para lo cual usan TrimPath’s JavascriptTemplates.
  • JQuery: especialmente para las llamadas AJAX.

Y para acabar, os dejo aquí el comando 11870 para Ubiquity a modo de ejemplo, para que jueguéis con él todo lo que queráis :D

11
Dic
08

Instalación desatendida de Java JRE/JDK en Ubuntu/Debian

¿Necesitas instalar java en 40 servidores? ¿Has creado un script y al lanzarlo has caído en que es inútil ya que debes aceptar la licencia de Sun para completar la instalación?

Tenemos la solución a tus problemas. Ejecuta el siguiente script antes de la instalación y se saltará el diálogo de conformidad con la licencia:

#!/bin/bash
. /usr/share/debconf/confmodule
db_version 2.0
db_capb backup
license=sun-dlj-v1-1
db_get shared/accepted-$license
if [ "$RET" = "true" ]; then
echo “$license license has already been accepted” >&2
exit 0
fi
db_set shared/accepted-$license true
exit 0

De nada.

21
Nov
08

pybuddy: hacking ambient devices (for fun & profit)

Aquellos que sigáis nuestro blog corporativo ya habréis visto a nuestro i-buddy tuneado. Ese post no tenía mucho detalle técnico, así que aquí vamos a dar alguna pista más.

En realidad todo empezó en El corte inglés. Sí, en serio. Un buen amigo y geek, luna, y un servidor estaban echando un vistazo a la sección de informática cuando nos encontramos con el i-buddy. Y nos dijimos: seguro que se puede hacer algo, si alguien no lo ha hecho ya. Una semana más tarde, teníamos un juguete nuevo cada uno y un código del que partir. El fruto del cacharreo se llama pybuddy: un demonio escrito en python que atiende a comandos enviados vía UDP por red.

Esto significa que una vez lanzado el demonio podemos controlar el buddy con comandos tal que así:

echo “DEMO” | nc -q0 -u localhost 8888

En principio, todos los comandos se han implementado con lo que el juego ahora viene de hacer combinaciones con ellos. Por ejemplo, en el vídeo explicativo usamos este archivo de comandos y lo lanzamos así:

cat commands | nc -q0 -u localhost 8888

Como se puede ver, el timing fue perfecto ;)

En la oficina, de vez en cuando a alguien le dar por juguetear con el bicho que está enchufado a mi ordenador y recibe peticiones de cualquier ordenador de la red local, con lo que suele estar bastante agitado. Pero también lo tenemos recibiendo información importante de lo que ocurre en nuestras máquinas en producción, con el script que se curró Pablo en un momento: access-log-client.

El procedimiento de instalación no está documentado por ahora, pero es bastante sencillo:

  1. Hacemos un checkout del proyecto: svn checkout http://pybuddy.googlecode.com/svn/trunk/ pybuddy-read-only
  2. Editamos el fichero de configuración pybuddy.cfg, si queremos cambiar alguno de los parámetros por defecto (tipo de buddy, usuario con el que correr, logging, etc).
  3. Lanzamos el demonio, como root (luego cambiará de usuario según lo configurado): sudo src/pybuddy-daemon.py pybuddy.cfg
$ sudo src/pybuddy-daemon.py
2008-11-21 12:15:07,662 INFO Read config file: /usr/local/etc/pybuddy.cfg
2008-11-21 12:15:07,662 INFO Starting search…
2008-11-21 12:15:07,808 INFO ibuddy found! vend: 4400 prod: 1
2008-11-21 12:15:07,808 INFO endpoint
2008-11-21 12:15:08,424 INFO Starting daemon…
$

A partir de este momento, el buddy está listo para recibir comandos. A ver quién es el primero que se curra un plugin para Pidgin, gajim o similar.

Happy hacking.

16
Oct
08

Manteniendo el orden de resultados SQL según un ‘in’

Si queremos obtener resultados ordenados con una query SQL y la condición del where es un IN, tenemos un problema. Pongamos que tenemos una query como la siguiente:

mysql> select * from category where status in (’inactive’, ‘active’, ‘mutant’);

Con esto pretendemos obtener todos los campos de todas las filas de category, estando éstas agrupadas y ordenadas en el mismo orden en el que las hemos puesto en el IN. Si añadimos un GROUP BY nos aseguramos de que se agrupan, pero no de que se respete el orden que hemos puesto en la condición.

El orden del resultado será indeterminado (yo pensaba que iba a ser por id en un principio, pero no). La cosa tenía pinta de que íbamos a tener que realizar la ordenación a posteriori en la aplicación, aunque ha resultado ser que no. En MySQL podemos hacer lo siguiente:

mysql> select * from category where status in (’inactive’, ‘active’, ‘mutant’) order by field(status,’inactive’, ‘active’, ‘mutant’) ;

Que funciona gracias a la función field de MySQL. Y problema resuelto.

07
Oct
08

Recuperando un servidor esclavo de MySQL en caliente

A veces, shit happens… lo sabemos todos. Y pasa en todos los lados y en el momento más inoportuno. Hoy he descubierto que podía recuperar un esclavo de MySQL en caliente porque utilizamos un engine transaccional (InnoDB).

maestro$ mysqldump --single-transaction --databases db1 db2 --master-data=1 -u root -R -p > /tmp/dump.sql

scp de rigor a la máquina destino, stop slave, carga, start slave:

maestro$ scp /tmp/dump.sql esclavo:/tmp
esclavo$ echo “stop slave;” | mysql -u root -p
Enter password:
esclavo$ mysql -u root -p < /tmp/dump
esclavo$ echo “start slave;” | mysql -u root -p

La iluminación ha venido por parte de High Performance MySQL: Optimization, Backups, Replication, and More, un hayquetener donde los haya y libro que recomiendo prácticamente a cualquiera que se tenga que pelear con este servidor de base de datos.

En el libro, además, usan pipes:

maestro$ mysqldump --single-transaction --databases db1 db2 --master-data=1 -u root -R -p | mysql -u root -h esclavo -p
26
Jun
08

versión móvil

Acabamos de hacer pública nuestra versión para móviles. Son tres vistas, la caja de búsqueda, los resultados de la búsqueda y la página de servicio.

Para hacer las páginas más ligeras hemos eliminado todas las imágenes y opiniones. De cada servicio dejamos sólo nombre, dirección, teléfono y el mapa descargado como una imagen.

La aplicación está hecha en grails. Queríamos probar grails porque se integra muy bien con librerías y otros desarrollos que tenemos hechos en java. De hecho seguro que cuando esta aplicación crezca usaremos esas magníficas capacidades, pero ahora mismo esta aplicación sólo se comunica con el buscador por HTTP y con la base de datos por JDBC. Así que seguramente la única excusa que se nos ocurre para haber usado grails es porque nos apetecía probarlo :) .

La experiencia ha sido buena aunque hemos echado de menos una buena integración con IDEs, en concreto con Eclipse. No obstante tendremos que programar más para fundamentar mejor nuestra evaluación.

Implementamos un Device Filter para filtrar a los Iphones por el User-Agent, a estos se les ofrecen las mismas vistas que al resto de los dispositivos excepto por las hojas de estilo y los scripts de Javascript que son especiales para aprovechar las capacidades de los navegadores Safari (WebKit):

  • Manejo de eventos especiales generados por webkit como el Orientation Event, para modificar la interfaz según la orientación del aparato.

Para el resto de dispositivos las vistas se sirven con XHTML Mobile Profile 1.0; usamos atributos acceskeys en algunos enlaces para habilitar la navegación a través del teclado del dispositivo y la función de hacer llamada de la especificación WTAI (Wireless Telephony Application Interface) para que los dispositivos reconozcan números de teléfono en el contenido de las páginas y permitan hacer llamadas directamente.

20
May
08

Simplificación de polígonos con el algoritmo Douglas-Peucker

Hace bastante tiempo ya nos encontramos con un problema del que no teníamos ni idea: nuestra fuente de datos nos pasaba datos de áreas geográficas (más concreto, los contornos) como listas de miles de puntos. En aquel entonces, la API de Google Maps dejaba frito el navegador cuando se intentaba dibujar un polígono con tal número de vértices (hoy ha mejorado y en vez de pintar un polígono vía Javascript, la API te tira una imagen a partir de la segunda petición).

¿Qué tuvimos que hacer? Reducir el número de puntos que definían cada uno de los polígonos intentando ser lo más fieles posible a la forma original porque pintar una provincia cualquiera como un cuadrado no es solución. No sólo teníamos datos de provincias: también teníamos de distritos y barrios. Y eso hacía que esta simplificación de polígonos fuera algo bastante intratable de hacer a mano. Fue así como descubrimos que existía una forma de hacerlo automáticamente y cuyo algoritmo fue desarrollado por David Douglas & Thomas Peucker y es conocido como Douglas-Peucker polyline simplification algorythm.

Partimos de un trozo de código en Pascal que encontramos en algún lugar y lo portamos a Python, que era el lenguaje con el que hicimos todo el script de importación de datos. Se quedó pendiente de publicación el asunto, ya que en su día nadie había portado dicho código a Python (ni Ruby ni Perl ni ningún lenguaje de script medianamente actual). El caso es que eso ha sido hasta hace un par de semanas Schuyler Erle publicó su versión en Mapping Hacks. Su código es mucho más bonito que el nuestro, así que nos ahorraremos el attachment ;)

30
Abr
08

Spring 2.5 y los filtros de contexto

El cambio a spring 2.5 conlleva una nueva forma de cargar tus clases como beans, lo que ellos llaman contexto. Este contexto es el responsable de añadir como beans las clases que tengas marcadas con las anotaciones que spring te da de serie(@Component, @Controller, @Service, @Repository). Si buceamos un poco en el código de spring podemos ver que la clase encargada de hacer esto es ClassPathBeanDefinitionScanner.java. Esta clase tiene implementada una pequeña jerarquía de filtros para añadir o quitar clases del contexto cuando arranca nuestra aplicación. Por defecto, añade todas las clases que estén marcadas con las anotaciones que hemos visto anteriormente:


  this.includeFilters.add(new AnnotationTypeFilter(Component.class));

Ahora vamos a ver las opciones que nos da el framework para añadir clases a ese contexto. En su documentación podemos ver un claro ejemplo de como añadir una nueva anotación:


  <context:component-scan>
    <context:include-filter type="annotation"
      expression="CustomAnnotation"/>
  </context:component-scan>

Lo que viene a decir el xml es que al contexto le vamos a añadir un filtro de tipo anotación y para todas las clases que estén marcadas con la anotación que va dentro del atributo expression.

Con lo que él internamente hará algo tal que así:


  this.includeFilters.add(new AnnotationTypeFilter(CustomAnnotation.class));

Lo que no se explica en la documentación es que por defecto, la clase que se encarga de parsear el contexto, ComponentScanBeanDefinitionParser.java, también soporta otros tipos de filtros además del de anotaciones.

El más sencillo sería el filtro de asignación, que simplemente añadiría una clase o un grupo de clases que extiendan o implementen algo común:


  <context:component-scan>
    <context:include-filter type="assignable"
      expression="org.myapp.IService"/>
  </context:component-scan>

Con este código se añadirían todas las clases que extendieran de la interfaz org.myapp.IService.

Otro de los filtros que soporta es por expresiones regulares, con este filtro spring cargará en el contexto las clases que coincidan con una expresión regular determinada:


  <context:component-scan>
    <context:include-filter type="regex"
      expression="(Service|Controller)\w+"/>
  </context:component-scan>

Con este código se cargarán todas clases que empiecen por Service o Coltroller.

Por último, tenemos un filtro un poco más complejo que se encarga de evaluar expresiones propias de Aspect Oriented Programming(AOP):


  <context:component-scan>
    <context:include-filter type="aspectj"
      expression="Service || Controller"/>
  </context:component-scan>

Con lo que cargaremos todas las clases que extiendan o implementen la clase Service o Controller.

La verdad es que con el grupo de anotaciones que spring trae de serie es más que suficiente para resolver la mayoría de los problemas pero nos podemos encontrar en escenarios donde no queremos tener como dependencia spring en una clase que puede ser utilizada tanto por spring como por otra aplicación o módulo que no use spring, con lo que acceder a los filtros del contexto nos vendrá muy bien.