coge la información y corre

20 Consejos para Mejorar tu MySQL que quizás no conocías

23 de agosto del 2010 Escrito por Alex Barros

Las operaciones sobre bases de datos suelen ser los principales cuellos de botella en las aplicaciones web. Por tanto es tarea de los programadores estructurar apropiadamente, escribir peticiones apropiadas, y programar mejor código. A continuación mostramos algunas técnicas de optimización MySQL.

1. Optimiza tus peticiones para la caché.

La mayoría de servidores MySQL tienen habilitado el sistema de caché. Es uno de los métodos más efectivos para mejorar el rendimiento, que vienen de la mano del motor de base de datos. Cuando la misma petición se ejecuta varias veces, el resultado se obtiene de la caché, que resulta mucho más rápida.

El problema es que, es tan sencillo y transparente para el programador, que la mayoría de nosotros tendemos a ignorarlo. Algunas cosas que hacemos de hecho pueden evitar que la caché haga su trabajo.

  1. //La cache NO funciona
  2. $r = mysql_query("SELECT nombre FROM usuarios WHERE registro >= CURDATE()");
  3.  
  4. // La caché sí funciona
  5. $hoy = date("Y-m-d");
  6. $r = mysql_query("SELECT nombre FROM usuarios WHERE registro >= ‘$hoy’");

la razón por la que no funciona en el primer caso es por el uso de CURDATE(). Puede aplicarse a todas las funciones no deterministas, como NOW() y RAND(). Dado que el resultado retornado por la función puede cambiar, MySQL decide deshabitar la caché en esa consulta.

2. Usa EXPLAIN en tus consultas SELECT

Utilizar la palabra clave EXPLAIN te dará muchos detalles internos de lo que hace MySQL para ejecutar tu consulta. Esto te puede ayudar a detectar los cuellos de botella y otros problemas con tu query o la estructura de la tabla.

El resultado de una query EXPLAIN te mostrará los índices que se están utilizando, cómo se está explorando la tabla, cómo se está ordenando, etc…

Coge una consulta SELECT (preferiblemente una compleja, con uniones), y añade la palabra EXPLAIN al principio del todo. Puedes utilizar por ejemplo PhpMyAdmin para esto. Te devolverá los resultados en una sencilla tabla. Por ejemplo, pongamos que me he olvidado de poner un índice a una columna, con la que estoy ejecutando

unoptimized_explain

Después de añadir el índice al campo group_id:

optimized_explain

Ahora en lugar de escanear 7883 filas, sólo escaneará 9 y 16 filas de las dos tablas.

3. Usa LIMIT 1 Cuando sólo quieras una única fila.

A veces, cuando estás realizando consultas a tus tablas, ya sabes que sólo necesitas una única fila. En estos casos debes solicitar a la base de datos un único resultado, o de lo contrario comprobará todos y cada uno de las coincidencias de la cláusula WHERE.

En estos casos, añadir LIMIT 1 a tu query puede mejorar significativamente la velocidad. De esta forma la base de datos dejará de escanear resultados en el momento que encuentre uno, en lugar de recorrer toda la tabla o un índice.

  1. // Tengo usuarios de Valencia?
  2.  
  3. // lo que NO hay que hacer:
  4. $r = mysql_query("SELECT * FROM user WHERE ciudad = ‘Valencia’");
  5. if (mysql_num_rows($r) > 0) {
  6.    // …
  7. }
  8.  
  9. // mucho mejor:
  10. $r = mysql_query("SELECT 1 FROM user WHERE ciudad = ‘Valencia’ LIMIT 1");
  11. if (mysql_num_rows($r) > 0) {
  12.    // …
  13. }

4. Indexa los campos de Búsqueda

Los índices no son sólo para las claves primarias o las claves únicas. Si en tu tabla hay columnas sobre las que vas a realizar búsquedas, deberías indexarlas casi siempre.

search_index

Como puedes ver, esta regla se aplica también a las búsquedas parciales como “apellido LIKE ‘a%’”. Cuando se busca desde el comienzo de la cadena, MySQL es capaz de utilizar el índice de esta columna.

Deberías también comprender en qué tipos de búsqueda no pueden utilizarse índices normales. Por ejemplo, cuando buscas una palabra dentro de un texto (p.e. “WHERE contenido LIKE ‘%manzana%’”), no observarás ningún beneficio con un índice normal. En este caso sería mejor utilizar una búsqueda FULLTEXT o construir tu propia solución de indexación.

5. Indexa, y utiliza el mismo tipo de columna para los Join

Si tu aplicación contiene muchas sentencias JOIN debes asegurarte de que las columnas que unes están indexadas en ambas tablas. Esto afecta en cómo MySQL optimiza internamente las operaciones JOIN.

Además, las columnas que vas a unir deben ser del mismo tipo. Por ejemplo, si estás uniendo una columna de tipo DECIMAL con una columna de tipo INT de otra tabla, MySQL no será capaz de usar al menos uno de los dos índices. Incluso la codificación de caracteres necesita ser del mismo tipo para las columnas de tipo String.

  1. // buscando compañias en mi ciudad
  2. $r = mysql_query("SELECT nombre_companyia FROM usuarios
  3.   LEFT JOIN companyias ON (usuarios.ciudad = companyias.ciudad)
  4.   WHERE usuarios.id = $user_id");
  5.  
  6. // ambas columnas ciudad deben estar indexadas
  7. // y ambas deberían ser del mismo tipo y codificación de caracteres
  8. // o MySQL tendrá que hacer un escaneo total de las tablas

6. No uses ORDER BY RAND()

Éste es uno de esos truquillos que suenan muy bien a primera vista, y donde muchos programadores novatos suelen caer. Puede que no hayas caído en la cuenta del increíble cuello de botella que se puede provocar si utilizas esta técnica en tus peticiones.

Si en verdad necesitas tablas aleatorias para tu resultado, hay formas mucho mejores de hacerlo. Está claro que ocuparán más código, pero estarás previniendo un posible embotellamiento que aumenta exponencialmente a medida que tu contenido crece. El problema es que MySQL tendrá que ejecutar RAND() (que requiere de potencia de procesado) para cada una de las filas antes de ordenarlas y devolver una simple fila.

  1. // la forma de NO hacerlo:
  2. $r = mysql_query("SELECT nombreusuario FROM usuarios ORDER BY RAND() LIMIT 1");
  3.  
  4. // mucho mejor:
  5.  
  6. $r = mysql_query("SELECT count(*) FROM usuarios");
  7. $d = mysql_fetch_row($r);
  8. $rand = mt_rand(0,$d[0]1);
  9.  
  10. $r = mysql_query("SELECT nombreusuario FROM usuarios LIMIT $rand, 1");

De forma que seleccionas un número aleatorio inferior a la cantidad de resultados y lo usas como el desplazamiento en la cláusula LIMIT.

7. Evita SELECT *

Cuanta más información se lee de las tablas, más lenta se ejecutará la petición SQL. Aumenta el tiempo que toma para las operaciones en disco. Además cuando el servidor de bases de datos está separado del servidor web, tendrás mayores retrasos de red debido a que la información tiene que ser transferida entre ambos servidores.

Es un buen hábito especificar siempre las columnas que necesitas cuando estás haciendo un SELECT.

  1. // preferible no hacer:
  2. $r = mysql_query("SELECT * FROM usuarios WHERE id_usuario = 1");
  3. echo "Bienvenido {$d['nombreusuario']}";
  4.  
  5. // mejor:
  6. $r = mysql_query("SELECT nombreusuario FROM usuarios WHERE id_usuario = 1");
  7. echo "Bienvenido {$d['nombreusuario']}";
  8.  
  9. // las diferencias son mucho más significativas cuanta más información haya

8. Ten casi siempre un campo identificativo

Ten en cada tabla una columna id con las propiedades PRIMARY KEY, AUTO_INCREMENT y alguna de las variantes de INT. Además es preferible que sea UNSIGNED (sin signo) ya que el valor nunca podrá ser negativo.

Incluso si tienes una tabla de usuarios cuyos nombres de usuario sean únicos, no los uses como clave primaria. Los campos VARCHAR como clave primaria son muy lentos. Y tendrás una mejor estructura en tu código si referencias a todos tus usuarios por sus ids internamente.

También hay una serie de operaciones internas que realiza el motor de MySQL por sí mismo, que usa la clave primaria, lo cual se hace incluso más importante cuanto más compleja sea la base de datos (clusters, particionados, etc…).

Una posible excepción a la regla son las “tablas de asociación”, utilizadas en las relaciones “muchos a muchos” entre dos tablas. Por ejemplo, una tabla “etiquetas_articulos” que contiene dos columnas: id_articulo, id_etiqueta, que es utilizada para las relaciones entre las tablas “articulos” y “etiquetas”. Estas tablas pueden tener una clave PRIMARY que contenga ambos campos.

9. Usa ENUM antes que VARCHAR

Las columnas de tipo ENUM son muy rápidas y compactas. Internamente se almacenan como TINYINT, aunque pueden contener y representar valores de cadenas. Esto las hace un perfecto candidato para algunos campos.

Si tienes un campo que contendrá sólo unos pocos valores distintos, utiliza ENUM en lugar de VARCHAR. Por ejemplo, podría ser una columna llamada “estado”, y sólo unos pocos valores como “activo”, “inactivo”, “pendiente”, “caducado”, etc…

De hecho hay una forma de obtener sugerencias del propio MySQL para reestructurar nuestra tabla. Cuando tienes un campo VARCHAR te puede sugerir que cambies ésa columna al tipo ENUM. Esto se hace utilizando la llamada a PROCEDURE ANALYSE(). Lo cual nos lleva a:

10. Obtén sugerencias con PROCEDURE ANALYSE()

PROCEDURE ANALYSE() permitirá a MySQL analizar la estructura de las columnas y los datos actuales que contienen para retornar ciertas sugerencias que serán de tu interés. Sólo es útil si hay información en las tablas, porque esto toma gran importancia en la toma de decisiones.

Por ejemplo, si creaste un campo INT para tu clave primaria, pero no tienes muchas filas, podría sugerirte que uses MEDIUMINT en su lugar. O si estas usando un campo VARCHAR, podría sugerirte que lo conviertas en ENUM, si sólo estás escribiendo unos pocos valores.

También puedes ejecutarlo pulsando en “Propose table structure” (proponer estructura de tabla) en la interfaz de PhpMyAdmin, en una de las vistas de tus tablas.

suggestions

Ten presente que esto son sólo sugerencias. Y si tu tabla va a crecer mucho, podrían no ser buenas sugerencias a seguir. La decisión es tuya en última instancia.

11. Usa NOT NULL si puedes

A no ser que tengas una razón específica para usar el valor NULL, deberías establecer siempre tus columnas como NOT NULL.

En primer lugar, pregúntate a tí mismo si habría alguna diferencia entre tener una cadena vacía y un valor NULL (o para campos INT: 0 contra NULL). Si no hay problema entre los dos valores, no necesitas un campo NULL. (¿Sabías que Oracle considera a NULL y una cadena vacía como lo mismo?)

Las columnas NULL necesitan espacio adicional y pueden añadir complejidad a tus sentencias de comparación. Simplemente evítalas siempre que puedas. En cualquier caso, entiendo que en algunos casos muy específicos haya razón para usar columnas NULL, lo cual no es siempre algo malo.

Extraído de la documentación de MySQL:

“las columnas NULL requieren espacio adicional en la fila a grabar donde los valores son NULL. Para las tablas MyISAM, cada columna NULL toma un bit extra, redondeando hacia arriba al byte más cercano.”

12. Declaraciones preparadas

Existen múltiples beneficios al usar declaraciones preparadas, tanto a nivel de productividad como de seguridad.

Las declaraciones preparadas filtran las variables que le pasas por defecto, lo que es perfecto para proteger tu aplicación contra ataques de inyección SQL. Claro que puedes filtrar tus variables manualmente, pero estos métodos son propensos al error humano y al despiste del programador. Este problema no es tan acentuado cuando se utiliza algún tipo de Framework u ORM.

Ya que queríamos centrarnos en la productividad, deberíamos mencionar los beneficios que ofrece este area. Estos beneficios son más significativos cuando la misma consulta va a utilizarse varias veces en tu aplicación. Puedes asignar diferentes valores a una misma declaración, y MySQL sólo tendrá que analizarla una vez.

Además, las últimas versiones de MySQL transmiten declaraciones preparadas de forma binaria nativamente, más eficientes y que ayudan a reducir los retrasos de red.

Hubo un tiempo en que muchos programadores solían evitar las declaraciones preparadas a propósito, por una única razón: no estaban siendo cacheadas por la caché de consultas de MySQL. Pero aproximadamente en la versión 5.1, el cacheo de consultas también ha sido soportado.

Para utilizar declaraciones preparadas en PHP puedes echar un ojo a la extensión mysqli o utilizar una capa de abstracción de base de datos como PDO.

  1. // creamos la declaración preparada
  2. if ($stmt = $mysqli->prepare("SELECT nombre FROM usuarios WHERE ciudad=?")) {
  3.  
  4.    // pasamos los parámetros
  5.    $stmt->bind_param("s", $ciudad);
  6.  
  7.    // ejecutamos
  8.    $stmt->execute();
  9.  
  10.    // pasamos la variable de resultado
  11.    $stmt->bind_result($nombre);
  12.  
  13.    // obtenemos el resultado
  14.    $stmt->fetch();
  15.  
  16.    printf("%s es de %s\n", $nombre, $ciudad);
  17.  
  18.    $stmt->close();
  19. }

13. Consultas fuera de buffer

Normalmente cuando estás ejecutando una consulta en un script, éste se esperará a que acabe la ejecución de esta consulta antes de que pueda continuar. Pero puedes cambiar este comportamiento sacando la consulta fuera del búffer.

Puedes echar un ojo a la genial explicación que hacen en la documentación de PHP para la función the mysql_unbuffered_query():

“mysql_unbuffered_query() envía la query SQL a MySQL, sin recuperar ni colocar en búfer las filas de resultado automáticamente, como mysql_query() lo hace. Por una parte, esto ahorra una considerable cantidad de memoria con las consultas SQL que producen conjuntos grandes de resultados y se puede empezar a trabajar con el conjunto de resultado inmediatamente después de que la primera fila ha sido recuperada: no necesita esperar hasta que la consulta SQL completa haya sido ejecutada. Para usar mysql_unbuffered_query() cuando se usan múltiples conexiones con la BD, se necesita indicar el parámetro opcional link_identifier para identificar que conexión se desea utilizar.”

Sin embargo los beneficios de mysql_unbuffered_query() tienen un precio: no poder usar mysql_num_rows() ni mysql_data_seek() en un conjunto de resultados devuelto por mysql_unbuffered_query(). También tendrás que recuperar todas las filas de resultado de una consulta SQL sin búfer antes de poder enviar una nueva consulta SQL a MySQL.

14. Almacena las direcciones IP como UNSIGNED INT

Muchos programadores crearían un campo VARCHAR(15) sin darse cuenta de que pueden almacenar las direcciones IP como números enteros. Cuando usas un INT sólo haces uso de 4 bytes en la memoria, y cuenta además con un tamaño fijo en la tabla.

Pero hay que asegurarse de que la columna sea UNSIGNED INT (entero sin signo) porque las direcciones IP hacen uso de todo el rango de 32 bits sin signo.

En tus consultas puedes utilizar la función INET_ATON() para convertir una dirección IP en entero, e INET_NTOA() para hacer lo contrario. También existen funciones parecidas en PHP llamadas ip2long() y long2ip().

  1. $r = "UPDATE users SET ip = INET_ATON(‘{$_SERVER['REMOTE_ADDR']}’) WHERE user_id = $user_id";

15. Las tablas de tamaño fijo (Estáticas) son más rápidas

Cuando cada una de las columnas en una tabla es de tamaña fijo (“fixed-length”), la tabla entera se considera “estática” o de “tamaño fijo”. Algunos ejemplos de tipos de columna que NO son de tamaño fijo son: VARCHAR, TEXT, BLOB. Si incluyes sólo uno de estos tipos de columna, la tabla dejará de ser de tamaño fijo y tendrá que ser tratada de forma distinta por el motor de MySQL.

Las tablas de tamaño fijo pueden incrementar la productividad porque para el motor de MySQL es más rápido buscar entre sus registros. Cuando quiere leer una fila en concreto de la tabla, puede calcular rápidamente la posición que ocupa. Si el tamaño de fila no es fijo, cada vez que tiene que buscar, ha de consultar primero el índice de la clave primaria.

También resultan más sencillas de cachear, y de reconstruir después de un accidente. Pero por otra parte también podrían ocupar más espacio. Por ejemplo, si conviertes un campo VARCHAR(20) en CHAR(20), siempre ocupará 20 bytes en la memoria independientemente de lo que contenga.

Usando técnicas de “Particionado Vertical”, puedes separar las columnas de tamaño variable en una tabla aparte. Lo cual nos lleva a:

16. Particionado Vertical

El particionado vertical es el acto de separar la estructura de tu tabla de forma vertical por razones de optimización.

Ejemplo 1: Seguramente tendrás una tabla de usuarios que contiene una dirección postal, la cual no se utiliza muy a menudo. Aquí podrías dividir la tabla y almacenar las direcciones en una tabla separada. De esta forma tu tabla de usuarios principal tendría un tamaño más ajustado. Como sabes, cuanto más pequeñas más rápidas son las tablas.

Ejemplo 2: Tienes un campo de “ultimo_acceso” en tu tabla. Se actualiza cada vez que un usuario accede a tu página. Pero cada acceso hace que la cache de consultas de esa tabla se libere. Lo que puedes hacer es colocar este campo en otra tabla para que las modificaciones en tu tabla de usuarios se mantenga al mínimo.

Pero también tienes que asegurarte de que no necesitas juntar las dos tablas constantemente después del particionado o sufrirás una caída en el rendimiento, justo lo contrario a lo que buscábamos.

17. Divide las consultas DELETE o INSERT grandes

Si necesitas ejecutar una consulta DELETE o INSERT que sea grande en una página web activa, tienes que tener cuidado de no alterar el tráfico web. Cuando una consulta grande como esas se ejecuta, puede bloquear tus tablas y paralizar tu aplicación web momentaneamente.

Apache ejecuta muchos procesos/hilos paralelamente. De ahí que funcione mucho más eficientemente cuando los scripts dejan de ejecutarse tan pronto como es posible, para que los servidores no experimenten muchas conexiones abiertas y procesos de una que consumen recursos, especialmente memoria primaria.

Si en algún momento bloqueas tus tablas en un periodo largo (como 30 segundos o más), en una web con mucho tráfico, causarás un apilamiento de procesos y consultas, que llevará mucho tiempo de concluir o que incluso podría estropear tu servidor web.

Si tienes algún script de mantenimiento que tiene que borrar una gran cantidad de filas, simplemente utiliza la cláusula LIMIT para hacerlo en porciones más pequeñas y así evitar la congestión.

  1. while (1) {  
  2.     mysql_query("DELETE FROM logs WHERE log_date <= ’2009-10-01′ LIMIT 10000");  
  3.     if (mysql_affected_rows() == 0) {  
  4.         // finalizado el borrado
  5.         break;  
  6.     }  
  7.     // incluso viene bien parar un poco
  8.     usleep(50000);  
  9. }  

18. Las columnas pequeñas son más rápidas

En los motores de bases de datos, la memoria en disco probablemente sea el cuello de botella más significativo. En términos de productividad, mantener las cosas reducidas y más compactas suele ayudar a reducir la cantidad de transferencia desde disco.

La documentación de MySQL tiene una lista de Requerimientos de Almacenamiento para todos los tipos de dato.

Si está previsto que una tabla tenga muy pocos registros, no hay razón para usar un INT para la clave primaria, en lugar de un MEDIUMINT, SMALLINT o incluso en algunos casos TINYINT. Y si no necesitas el componente del tiempo, puedes utilizar DATE en lugar de DATETIME.

Simplemente, debes asegurarte de que dejas espacio razonable para poder crecer, o podrías acabar como Slashdot.

19. Escoge el motor de almacenamiento adecuado

Los dos principales motores en MySQL son MyISAM y InnoDB, Cada uno tiene sus pros y sus contras.

MyISAM is adecuado para aplicaciones con mucha lectura, pero no escala cuando hay muchas escrituras. Incluso si estás editando un campo de una fila, la tabla completa se bloquea, y ningún otro proceso puede siquiera leer hasta que la consulta ha finalizado. MyISAM es muy rápido calculando consultas de tipo SELECT COUNT(*).

Inno DB tiende a ser un motor más complicado y puede ser más lento que MyISAM para la mayoría de aplicaciones pequeñas. Pero soporta bloqueo basado en fila, lo cual escala mejor. También soporta algunas características más avanzadas como las transacciones.

20. Usa un Mapeador de objetos relacionales

Al usar un ORM (Object Relational Mapper), puedes conseguir algunas mejoras en la productividad. Cualquier cosa que puede hacer un ORM, puedes programarlo a mano también. Pero podría significar demasiado trabajo extra y requerir de un alto nivel de experiencia.

Los ORM son perfectos para la “carga perezosa”. Significa que se puede obtener valores sólo cuando se necesitan. Pero hay que tener cuidado porque podría acabar creando demasiadas mini peticiones que perjudicarían al rendimiento.

Los ORM también pueden agrupar tus consultas en transacciones, que operan mucho más rápido que enviar consultas individuales a la base de datos.

Actualmente un ORM recomendable para PHP es Doctrine. Puedes leer cómo instalarlo en este artículo (inglés).

21. Ten cuidado con las conexiones persistentes

El objetivo de las Conexiones Persistentes es reducir el esfuerzo de reabrir conexiones con MySQL. Cuando se crea una conexión persistente, queda abierta incluso después de que el script haya acabado de ejecutarse. Dado que Apache reutiliza sus procesos hijos, el siguiente script reutilizaría la misma conexión MySQL.

En la teoría suena muy bien. Pero desde mi experiencia personal (y la de muchos otros), esta característica acaba por no merecer la pena. Puedes tener serios problemas con los límites de conexión, problemas de memoria y mucho más.

Apache ejecuta de forma extremadamente paralela, y crea muchos procesos hijo. Esta es la principal razón por la que las conexiones persistentes no funcionan muy bien en este entorno. Antes de que consideres usar la función mysql_pconnect(), consulta a tu administrador de sistemas.

Extraído y traducido de nettuts por Alex Barros. Fecha del original: 25-11-09.

Categorias: MySQL | 7 comentarios »

API de Valenbisi (Servicio de JCDeacaux)

19 de julio del 2010 Escrito por Alex Barros

Valenbisi es un servicio que ofrece el ayuntamiento de Valencia de bicicletas públicas, mediante una red de “estaciones” donde coger o dejar una bici.

La web que tienen ( http://www.valenbisi.es ) me parece muy deficiente, por lo que me quiero aventurar a hacer una aplicación simplificada, más 2.0, con la API de Google Maps.

La web de Valenbisi (la empresa encargada es la francesa JCDecaux) no ofrece una API pública, pero no es muy difícil averiguar el sistema que usan para cargar los datos en su web (que es lo que voy a detallar a continuación). Sólo esperemos que no modifiquen esta funcionalidad, que resulta verdaderamente accesible.

Actualización:

Desde la aplicación biciv.com se ofrece una sencilla API que en una sola consulta devuelve los valores actuales de todas las estaciones en formato JSON.

Puedes acceder a este listado en la dirección http://biciv.com/api/stations/now/

Se trata de dos archivos clave, que contienen toda la información en el formato XML:

1: Las estaciones

El primer archivo nos va a dar información de todas las estaciones existentes, junto con información relativa a la localización.

http://www.valenbisi.es/service/carto

Devuelve un archivo similar a este fragmento:

  1. <carto>
  2.   <markers>
  3.     <marker name="063_AVDA. DEL PUERTO I" number="63" address="AVDA. DEL PUERTO I – Esq. C/Antonio Suarez" fullAddress="AVDA. DEL PUERTO I – Esq. C/Antonio Suarez  " lat="39.46755709145644" lng="-0.357891042655049" open="1" bonus="0"/>
  4.     <marker name="064_AVDA. DEL PUERTO II" number="64" address="AVDA. DEL PUERTO II – Frente a los numeros 61 y 63" fullAddress="AVDA. DEL PUERTO II – Frente a los numeros 61 y 63  " lat="39.46638708872079" lng="-0.353943030206345" open="1" bonus="0"/>
  5.     <marker name="065_AVDA. DEL PUERTO III" number="65" address="AVDA. DEL PUERTO III – Esquina con Calle Doctor Manuela" fullAddress="AVDA. DEL PUERTO III – Esquina con Calle Doctor Manuela  " lat="39.46549608664973" lng="-0.351062021089717" open="1" bonus="0"/>
  6.     <marker name="066_GUILLEN DE ANGLESOLA" number="66" address="GUILLEN DE ANGLESOLA – Esquina con Avenida del Puerto" fullAddress="GUILLEN DE ANGLESOLA – Esquina con Avenida del Puerto  " lat="39.46414708365747" lng="-0.34636900630001" open="1" bonus="0"/>
  7.     <marker name="067_JUAN VERDEGUER" number="67" address="JUAN VERDEGUER – Esquina con Calle Boters" fullAddress="JUAN VERDEGUER – Esquina con Calle Boters  " lat="39.458805068652005" lng="-0.337036975447925" open="0" bonus="1"/>
  8. </carto>

Cada marker contiene toda la información de una estación, que son el nombre, el número (este dato es fundamental para el siguiente paso), la dirección, datos cartográficos y si está operativa (la propiedad bonus no he comprendido todavía qué significa)

2: Detalles de la estación

Cuando quieres centrarte en los datos de una estación concreta, debes acudir al siguiente archivo. Se trata de una dirección base a la que debes añadir al final el número de estación que deseas obtener.

http://www.valenbisi.es/service/stationdetails/valence/num_estacion
(donde num_estacion es un número entero correspondiente a una estación.)

Devuelve un archivo como este:

  1. <station>
  2.   <available>14</available>
  3.   <free>2</free>
  4.   <total>19</total>
  5.   <ticket>0</ticket>
  6. </station>

Es decir, el número de bicicletas disponibles (avaliable), el número de bornetas disponibles para aparcar (free) el número total de bornetas con que cuenta la estación (total) y si admite la opción de alquiler con tarjeta bancaria (ticket).

A partir de aquí lo único que hace falta es un parser de XML para el lenguaje que estés utilizando, y una conexión a internet, el resto lo pone la imaginación del programador.

Que vivan las APIs!

(Editado: podéis ver mi experimento: bicis disponibles de Valenbisi en http://bicivalencia.com y una versión móvil en http://bicivalencia.com/m)

Categorias: Boozox | 18 comentarios »

Defensa a ultranza del Software Libre

25 de noviembre del 2009 Escrito por Alex Barros

En una discusión con el equipo de desarrollo del índice de dificultad IBP Index, en el que les sugiero que liberen su código por ser un proyecto sin ánimo de lucro, y con interés por propagarse (dos razones de peso para liberar código), me contestaron algo así como

entendemos la idea, pero no queremos que surjan 100.000 versiones distintas y así perder el ser un referente como índice de dificultad

(La cita no es literal)

El planteamiento de entrada me parece absurdo. No veo ningún interés en clonar el código y alterar los parámetros para ofrecer el mismo producto. No es interesante ni para el que lo ofrece, ni mucho menos para los usuarios, que preferirán “el que usa la mayoría”

Por eso me he dedicado a redactarles una respuesta que me ha llevado tanto de escribir que quiero compartirlo en éste mi blog:

Disculpad el tono de los anteriores mails. Resulta muy frustrante trabajar con tan poca documentación, más aún cuando mi tiempo para dedicar es escaso, al ser una contribución altruista.

Sigo sin estar de acuerdo con vuestro planteamiento, estoy seguro de que es fruto de un prejuicio, muy común en programadores y equipos que nunca han liberado código. Y me explico: una vez se da el paso de liberar un código, como es el caso del cálculo del índice IBP, planteamientos típicos son:

  • “me van a robar el código”
  • “no van a respetar la licencia que le ponga”
  • “van a surgir 100.000 versiones diferentes que acapararán mi target o mi público potencial, desbancándome”
  • “perderé la ilusión ante esta situación de caos”

Sin embargo, la realidad es siempre muy distinta. Los hechos del software libre son:

  • Consigues que otros programadores aporten ideas, en ocasiones brillantes, para que el software mejore más rápido y trabaje de forma más eficiente. Se llegan a conseguir dinámicas de trabajo muy potentes en algunos proyectos libres
  • La licencia es respetada en el 99% de los casos. Por norma, el 1% que no respeta tu licencia no consigue la más mínima repercusión y es muy fácil olvidarse de estos caso por lamentables y anecdóticos.
  • Nunca salen 100.000 versiones de un proyecto libre. De hecho, casi nunca hay dos proyectos que hagan lo mismo. Lo que sí es común es un fenómeno llamado “bifurcación” (o fork en inglés) que consiste en crear un proyecto nuevo basándose en otro, pero con objetivos distintos. Ésto, lejos de perjudicar a los usuarios (por ejemplo con muchos índices diferentes para calcular dificultad en rutas de montaña) lo que hace es beneficiarles, ya que son alternativas al proyecto padre, pero adaptadas a otro objetivo (por ejemplo adaptar IBP para calcular la dificultad de un trazado en la luna destinado a robots motorizados). Tened en cuenta que si licenciáis el código bajo GNU/GPL todo fork está OBLIGADO a liberar también su código, una licencia libre es un documento legal y su incumplimiento un hecho demandable ante la ley.
  • Lejos de perder la ilusión, suele resultar una inyección de motivación. Recibes sugerencias y trackback de compañeros programadores que te ayudan a mejorar el software y a plantearte nuevas ideas y nuevas formas de enfrentar los problemas. La comunidad de programadores es siempre muy activa y respetuosa.

Por último, ofrezco algunos ejemplos de éxito rotundo del software de código abierto: enciclopedia Wikipedia, servidores Apache, lenguaje PHP, sistema operativo Ubuntu, navegador Firefox, sistema de gestión de blogs WordPress y podría continuar con miles de proyectos que han alcanzado un éxito más que evidente gracias a ser código abierto. Caso similar son las aplicaciones de software privativo que fundamentan sus sistemas con Software Libre (véase YouTube, Flickr, Wikiloc, Google, etc…)

Espero que os lo volváis a plantear, una vez más o las que hagan falta (me podéis plantear cualquier duda del tema), y que superéis los miedos; porque más allá del código lo que realmente tiene valor son las ideas, y nadie os va a hacer sombra si tenéis una comunidad apoyándoos (que la tenéis) y si vosotros, junto con otros programadores que crean en el proyecto, mantenéis IBP como un software actualizado, versátil y de calidad.

También quería comentaros que finalmente he creado una clase en PHP para Gpsia que administra las llamadas a IBP. Es software libre por lo que no sólo podéis publicarlo en vuestra web, sinó que en cierta forma es un compromiso el distribuirlo. Os lo adjunto.

Un saludo, y gracias por vuestra atención.

Por cierto, el código es éste:

ibp.class.php

  1. <?php
  2. // Class for getting the IBPIndex given a local filename, further information on http://www.ibpindex.com
  3. // By Alex Barros for Gpsia software. License: GNU/GPL v3
  4. // Requirements: PHP + Curl
  5.  
  6.  
  7. define(‘IBP_TMP’, ‘/tmp’); //temporary folder with 777 chmod permission
  8.  
  9. class IBP {
  10.     var $filename; //Source filename
  11.     var $ibp; //Resoult: NN XX (N=number X =alphabetic)
  12.    
  13.     function IBP($filename = false){ //Constructor
  14.         if(!empty($filename)) $this->getIBP($filename);
  15.     }
  16.     function getIBP($filename) {
  17.         if(strlen(basename($filename)) > 55) { //filenames are limited
  18.             $filename = $this->shortenName($filename); //Makes a copy with shorter filename in the temporary folder
  19.             $isTmp = true;
  20.         }
  21.        
  22.         $this->filename = $filename;
  23.        
  24.         if(file_exists($filename)) {
  25.             //Post fields
  26.             $post_data = array();
  27.             $post_data[‘fichero’] = "@$filename";
  28.  
  29.             $post_data[‘MOD’] = ‘BTT’;
  30.             $post_data[‘UDO’] = ‘Gpsia’;
  31.            
  32.             //Curl connection
  33.  
  34.             $ch = curl_init();
  35.             curl_setopt($ch, CURLOPT_URL, "http://www.ibpindex.com/esp/ibpresponse.asp" );
  36.             curl_setopt($ch, CURLOPT_POST, 1 );
  37.             curl_setopt($ch, CURLOPT_HEADER, 0);
  38.             curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); //Needed because redirection is used on the app
  39.  
  40.             curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
  41.             curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  42.             $postResult = curl_exec($ch); //return result
  43.            
  44.             if (curl_errno($ch)) {
  45.                 die(curl_error($ch)); //this stops the execution under a Curl failure
  46.             }
  47.            
  48.             curl_close($ch); //close connection
  49.            
  50.             $this->ibp = $postResult;
  51.            
  52.             if($isTmp) @unlink($filename); //remove temporary file
  53.            
  54.             return $postResult;
  55.         }
  56.     }
  57.     function shortenName($filename) {
  58.         $newName = IBP_TMP.‘/IBP-’.substr(basename($filename),-20); //new shorter temporary filename
  59.         if(file_put_contents($newName, file_get_contents($filename)))
  60.             return $newName;
  61.         return false;
  62.     }
  63. }
  64.  
  65. // usage: $ibp = new IBP(‘path/to/file.gpx’); echo $ibp->ibp; //Returns something like 696 AB
  66.  
  67. ?>

Categorias: Sin categoría | 7 comentarios »

Múltiples join y solución al encadenar LEFT JOINS

5 de noviembre del 2009 Escrito por Alex Barros

Desde que publiqué el artículo de Inner Join para unir tablas y más especialmente el de Explicación visual de los SQL Join – Unir tablas con SQL me han consultado en muchos comentarios cómo hacer múltiples JOIN en una sóla consulta.

Hasta hoy no lo había necesitado, y nunca supe contestar. Hasta hoy.

Encadenar Joins es tan simple como ponerlos uno a continuación de otro.
ejemplo:

  1. SELECT A.*, B.*, C.id FROM tabla_a A
  2. INNER JOIN tabla_b B ON A.id = B.foo
  3. LEFT JOIN tabla_c C ON A.id = C.bar

Y así sucesivamente. Puedes tener tantos joins como necesites, y si es necesario jugar con los paréntesis para cambiar la preferencia (por defecto MySQL lee de izquierda a derecha).

Pero una vez esto aclarado, me he encontrado con un problema. Lo que yo quería hacer es una serie de LEFT JOINs para consultar en una serie de tablas la cantidad (COUNT()) de registros de otras tablas que estaban asociados a la clave primaria de una tabla principal.

En mi caso concreto, tengo una serie de Rutas de bicicleta en una tabla, y en otras tablas satélite tengo almacenados los comentarios asociados a la ruta, las votaciones, los archivos adjuntos, etc… de modo que haciendo un COUNT en estas tablas para cada una de las claves primarias de la principal (Rutas) me saldría la cantidad asociada.

Así que hice algo como esto:

  1. SELECT R.*, COUNT(C.id) comments, COUNT(S.id) saved, COUNT(V.id) votes, COUNT(P.id) photos, U.name owner, U.mail, U.about, U.image avatar, U.authority
  2.  
  3. FROM geo_routes R
  4. LEFT JOIN geo_comments_routes C ON R.id = C.cid
  5. LEFT JOIN geo_saved S ON R.id = S.route
  6. LEFT JOIN geo_votes V ON R.id = V.route
  7. LEFT JOIN geo_photos P ON R.id = P.route
  8. LEFT JOIN geo_users U ON U.id = R.property
  9.  
  10. GROUP BY R.id

Pero esta consulta no daba valores correctos, sinó que en estos campos COUNT aparecía generalmente el mismo número replicado.

La solución es usar la cláusula DISTINCT en los COUNT, ya que en cada uno de los LEFT JOIN se están asociando muchos campos a cada registro de la tabla principal, y si no especificas el que te interesa cuántos DISTINTOS hay (y no sólo cuántos hay) te hincha la cifra o simplemente te la altera.

De modo que mi consulta ha quedado así:

  1. SELECT R.*, COUNT(DISTINCT C.id) comments, COUNT(DISTINCT S.id) saved, COUNT(DISTINCT V.id) votes, COUNT(DISTINCT P.id) photos, U.name owner, U.mail, U.about, U.image avatar, U.authority
  2.  
  3. FROM geo_routes R
  4. LEFT JOIN geo_comments_routes C ON R.id = C.cid
  5. LEFT JOIN geo_saved S ON R.id = S.route
  6. LEFT JOIN geo_votes V ON R.id = V.route
  7. LEFT JOIN geo_photos P ON R.id = P.route
  8. LEFT JOIN geo_users U ON U.id = R.property
  9.  
  10. GROUP BY R.id

Después he asociado a esta consulta una Vista, para poder tratarla como si fuera una tabla con esos campos ya incluidos.

Espero que estos consejos os puedan servir de mucho! A mi me ha costado mucho dar con las respuestas.

Categorias: Boozox | 17 comentarios »

5 máximas

29 de septiembre del 2009 Escrito por Alex Barros

Hoy se me han pasado por la cabeza estas máximas que creo que mueven mi vida.

  1. Ser Feliz
  2. Echarle Morro
  3. Reclamar lo que es justo
  4. Currárselo mucho
  5. Hacer que todo merezca la pena

No necesariamente en ese orden, sinó todas a la vez.

Ah, y por teneros un poco up-to-date: Actualmente retomando Informática (esta vez de Gestión), rodando y mejorando la WebSerie Tu Muerte Está Cerca, Trabajos diversos como himformático, y trabajos diversos como colaborador en rodajes.

Categorias: MySQL | 5 comentarios »

¿Por qué cambio el código por la cámara de vídeo?

26 de agosto del 2009 Escrito por Alex Barros

La decisión está tomada, voy a cambiar el rumbo de mi vida: cambiaré la creación de webs y aplicaciones online por la creación de productos audiovisuales de mano de mi productora Zumbe.

¿Pero por qué?

Porque haga lo que haga, me gusta entregarme al 100% dando el máximo de mi. En internet esto significa pasar horas y horas y más horas tecleando frente al ordenador, alejándote de las relaciones humanas. En el cine esto significa socializar con todo tu equipo, conocer gente y lugares increibles, caminar de un lado a otro…

Porque cuando haces páginas web todo el mundo quiere que le ayudes desinteresadamente, cosa que no me desagrada, pero sin duda absorve. Por no hablar de la cantidad de ofertas de empleo “basura” por parte de amigos, amigos de amigos y familiares, remunerados pero muy mal remunerados, porque total “es lo que haces todo el día en casa sólo que encima te llevas algo a la buxaca

Porque amo la parte técnica y la parte artística por igual. En el cine puedes controlar el 50% de ambas fácilmente, pero en la creación de webs la parte artística es muy limitada y terriblemente compleja (hablo de buenos diseños de webs)

Porque quiero. Y me parece una aventura que va a merecer mucho la pena.

No me atrevo a sentenciar la muerte de este blog de temática informática, pero muy probablemente continúe mucho tiempo en coma. Nop he resistido a la tentación

Gracias a todos por comprenderlo, y espero que veáis mis producciones con las mismas ganas que veíais mis tutoriales. Porque la informática le gusta a mucha gente, pero el entretenimiento… ¿a quién no le gusta?

Categorias: Boozox | 12 comentarios »

Proyectos on progress

2 de junio del 2009 Escrito por Alex Barros

Rodaje de “Tu Muerte Está Cerca”

35%

Se han rodado el capítulo 2 casi completo, y los capítulos 3 y 4 parcialmente. Faltan el 5 y 6. Estreno en Septiembre de 2009

Gpsia 3.0

55%

Falta mucho diseño artístico y de interfaz manejable. Gran parte del núcleo ya está listo.

Motriz 1.0

5%

Proyecto en desarrollo, similar a un framework web.

Categorias: Boozox | 8 comentarios »

Iré a la Valencia Pillow Fight

28 de mayo del 2009 Escrito por Alex Barros

Yo voy… nos veremos allí?
13 de Junio en la pza. de la Virgen. Si estás de exámenes, no veo mejor manera de desahogarse! :D

Categorias: Boozox | 3 comentarios »

Script PHP para explorar archivos y directorios recursivamente

2 de mayo del 2009 Escrito por Alex Barros

No es la primera vez que necesito construirme un script como éste, así que voy a publicarlo tanto para mí en el futuro como para todo aquél que pueda servirle.

Este script de PHP sencillamente recorre todos los archivos y directorios (y archivos dentro de directorios) a partir de cierta dirección que le proporciones, para realizar la tarea que tu quieras.

En mi caso, necesitaba encontrar todos los archivos PHP, y buscar en su interior el primer parámetro de todas las apariciones de la función t()

Para ello hice un ereg (expresión regular) al nombre de cada archivo, y a los que fueran php, hice otra expresión regular más compleja, que extrajera el primer parámetro de todas las funciones t() ejecutadas en el archivo, y lo almacenase en un array.

  1. <?php
  2. $contenido = array();
  3. function inspecRec($dir) {
  4.         global $contenido;
  5.         if ($gd = opendir($dir)) { //Abro directorio
  6.                 while (($ar = readdir($gd)) !== false) { //recorro su interior
  7.                         if(eregi(".*\.php",$ar)) { //compruebo extension
  8.                                 $co = file_get_contents($dir.‘/’.$ar); //extraigo su contenido
  9.                                 preg_match_all("/[^a-zA-Z]t\(‘(.*)’(,.+)?\)/Ui",$co,$re); //compruebo funcion t()
  10.                                
  11.                                 if(count($re[1])) { //si ha encontrado contenido…
  12.                                         echo "<br><strong>$dir/$ar</strong>"; flush(); //imprimo el nombre de archivo
  13.                                         foreach($re[1] as $r){ //introduzco frases
  14.                                                 if(!isset($contenido[$r]))
  15.                                                         $contenido[$r] = $r;
  16.                                         }
  17.                                 } else {
  18.                                         echo "<br>$dir/$ar"; flush(); //imprimo nombre de archivo
  19.                                 }
  20.                         } elseif(is_dir($ar) && $ar != ‘.’  && $ar != ‘..’) { //si es un directorio..
  21.                                 inspecRec($ar); //recursivamente lo inspecciono tambien
  22.                         }
  23.                 }
  24.                 closedir($gd); //cierro el recurso
  25.         } else {
  26.                 echo "<hr>Error: $dir<br>";
  27.         }
  28. }
  29.  
  30. inspecRec(‘./’); //Ejecuto desde el directorio actual
  31. ?>

[Descargar código]

El uso que le he dado esta vez, más concretamente, era recorrer todos los archivos del programa Gpsia, para buscar la función t() que es la encargada de traducir. Quiero pasar del sistema actual hecho por mí, al sistema estándar de traducción que es gettext(), soportado por PHP (como WordPress). (Hablaré de esto próximamente)

Estas traducciones se hacen a partir de archivos .po, y mi script genera automáticamente un archivo .po preparado para ser traducido a cualquier idioma. El código específico es éste:

  1. msgid ""
  2. msgstr ""
  3. "Project-Id-Version: Gpsia 3.0\n"
  4. "POT-Creation-Date: \n"
  5. "PO-Revision-Date: \n"
  6. "Last-Translator: Alex Barros <zumbenet@gmail.com>\n"
  7. "Language-Team: \n"
  8. "MIME-Version: 1.0\n"
  9. "Content-Type: text/plain; charset=utf-8\n"
  10. "Content-Transfer-Encoding: 8bit\n"
  11. "X-Poedit-Language: Spanish\n"
  12. "X-Poedit-Country: SPAIN\n"
  13. "X-Poedit-SourceCharset: utf-8\n"
  14. <?php
  15. $frases = array();
  16.  
  17. function inspecRec($dir) {
  18.         global $frases;
  19.         if ($gd = opendir($dir)) {
  20.                 while (($ar = readdir($gd)) !== false) {
  21.                         if(eregi(".*\.php",$ar)) {
  22.                                 $co = file_get_contents($dir.‘/’.$ar);
  23.                                 preg_match_all("/[^a-zA-Z]t\(‘(.*)’(,.+)?\)/Ui",$co,$re);
  24.                                
  25.                                 if(count($re[1])) {
  26.                                         #echo "<br><strong>$dir/$ar</strong>"; flush();
  27.                                         foreach($re[1] as $r){
  28.                                                 if(!isset($frases[$r]))
  29.                                                         $frases[$r] = $r;
  30.                                         }
  31.                                 } else {
  32.                                         #echo "<br>$dir/$ar"; flush();
  33.                                 }
  34.                         } elseif(is_dir($ar) && $ar != ‘.’  && $ar != ‘..’) {
  35.                                 inspecRec($ar);
  36.                         }
  37.                 }
  38.                 closedir($gd);
  39.         } else {
  40.                 #echo "<hr>Error: $dir<br>";
  41.         }
  42. }
  43. inspecRec(‘./’);
  44. sort($frases);
  45. foreach($frases as $f) {
  46.         echo "msgid \"".str_replace(array(‘"’,"\n"),array(\”,""),stripslashes($f))."\"\nmsgstr \"\"\n\n";
  47. }
  48. ?>

[Descargar código]

Categorias: Boozox | 5 comentarios »

Retomando Gpsia con fuerza

28 de abril del 2009 Escrito por Alex Barros

Gpsia es una aplicación que desarrollo en mi tiempo libre, que consiste en subir y compartir rutas grabadas con GPS, con muchas características muy interesantes.

Nokia Sports Tracker foto de nightoEs una aplicación con mucho potencial, porque el de “usuarios de GPS con ganas de copartir rutas”, es un público que está ahí, que está esperando que le ofrezcas una buena solución. Además me encanta porque es una aplicación que realmente ayuda a la gente a conseguir lo que quiere, no es una banalidad 2.0 (que abundan mucho por la red).

Pues bien, de vez en cuando me dan venazos y retomo mis proyectos de golpe, y anoche fue uno de esos días. Tengo miles de hojas con bocetos de lo que quiero construir, tanto en diseño como en funcionalidad, y en corrección de bugs y optimización.

No recordaba cuánto me gusta desarrollar aplicaciones! Estos días voy a intentar dedicarle tiempo a desarrollar todo lo que tengo en mente. Entre otras cosas, aprenderé a sincronizar programas en C y programas en PHP.

¡Qué nervios! ¿Alguien tiene idea?

[Fotografía de Nighto]

Categorias: Boozox | 2 comentarios »

« Entradas Anteriores

del.icio.us meneame.net RSS

Search:

Bicivalencia Localiza las estaciones de Valenbisi, servicio público de bicicletas en Valencia, España. Ver más Gpsia Descubre y comparte rutas por todo el mundo, tomadas con GPS. Ver más Imaset Edita tus imágenes de Wordpress con este sencillo plugin. Ver más

¡Mi música es tuya!

Digo yo que...

Mis fotos de Flickr

Entradas recientes

Sitios de interés

Meta:

Respeta el copyleft

,