martes, 17 de mayo de 2016

El infierno de los campos TEXT en MySQL

¡CUIDADO CON LOS CAMPOS TEXT / BLOB en MySQL!

Esta semana nos hemos dado bastantes estocazos contra la pared por culpa de los campos Text en MySQL. El principal motivo es que MySQL no los carga en  memoria, de tal forma que si una consulta tiene tablas con campos Text, MySQL accede a disco para manipular cada una de las filas. Esto ralentiza mucho ciertas operaciones, además de que consume muchísima CPU.

La cosa se hace mucho más grave si encima en la consulta no hacemos uso de esos campos. ¡A MySQL le da igual! Si la tabla tiene campos Text, le da igual que tu no los vayas a manipular en tu consulta. Esto es así porque en la estructura interna de datos que emplea para manejar los registros de una tabla, si ésta tiene campos Text no almacena el valor del campo, sino un puntero a un recurso a disco. Es como si el campo Text fuera un fichero, y MySQL en sus registros internos lo que almacena es la URL / Path del fichero.

Ejemplos de este tipo podemos encontrar muchos:
  • Un sistema de blogging, en el que el contenido de los posts lo tengamos en campos Text, mientras que los metadatos (autor, fecha de creación, categoría de contenido) en campos estructurados.
  • etc

Moraleja: A la hora de diseñar tu tabla, si junto con los campos Text tienes otro tipo de campos, que vayas a utilizar de forma independiente, separalos en tablas distintas. Optimizarás mucho las operaciones que no involucren campos Text, hasta tal forma que te compensará hacer un JOIN cuando sí lo involucren.


martes, 7 de octubre de 2014

Textos completos en JQuery Mobile

Por defecto, cuando el framework Jquery Mobile detecta que un texto excede del tamaño reservado de la barra de título, lo recorta y pone unos puntos suspensivos.

Para evitar esto, hay que modificar el estilo CSS del emento html utilizado para el título, añadiendo la propiedad white-space:normal.
white-space: normal

lunes, 6 de octubre de 2014

Leaflet: la importancia de map.invalidateSize()

Ultimamente me he estado pegando más de la cuenta con Leaflet, así que comparto aquí el problema encontrado, y la solución, por si le puede servir de ayuda a alguien.

Origen del problema: tenemos un objeto javascript L.Map de Leaflet, al que hemos añadido una serie de elementos vectoriales (objetos de tipo L.Layer).

Si queremos cambiar la capa, y mostrar otra distinta, reutilizando el objeto mapa existente, tenemos que seguir estos dos pasos:

a) Eliminar los objetos "Layer" previamente cargados. En este caso lo más comodo es haberlos agrupado previamente en un contenedor denominado "LayerGroup".

if(currentLayer){
    if(currentLayer instanceof L.LayerGroup)
         currentLayer.clearLayers();
         map.removeLayer(currentLayer);
    }
}

b) Añadir la nueva capa (objetos Layer) agrupada en un contenedor.

 var lyrs = [];//array donde guardar las geometrias
var layerGroup = L.featureGroup(lyrs); 

c) Cambiar el nivel de zoom del mapa, para que se nos muestra la nueva "capa" que vamos a añadir.
map.fitBounds(layerGroup.getBounds());

Y ¡ojo! Aquí empiezan a aparecer los problemas. Es muy posible que el nivel de zoom que se nos muestre sea siempre el máximo,  y que el mapa (la capa de tiles con ortofoto, open street map, o lo que sea que hayamos querido cargar como capa de base) no aparezca centrado. ¿Cual es el motivo?

Si cambiamos nuestro código de cambio del nivel de zoom, por el siguiente:
var bounds = _layerGroup.getBounds();
var center = bounds.getCenter();
var zoomLevel = map.getBoundsZoom(bounds);

map.setView(center, zoomLevel, true);

Veremos que zoomLevel toma el valor 0.


 ACTUALIZACIÓN: En este bug reportado en la web del proyecto Leaflet se describe perfectamente el problema. No solo zoomLevel toma el valor 0, getSize() devuelve un objeto con valores w=0 y h=0.

En este punto es cuando entra en juego el método invalidateSize() del objeto map. Si lo hacemos así:

          var layerGroup = L.featureGroup(lyrs);
         map.invalidateSize();
         map.fitBounds(layerGroup.getBounds()); 

Nuestro mapa se dibujará sin problemas, mostrando la capa que acabamos de añadir.


Moraleja: map.invalidateSize() recalcula los parámetros del objeto map,parámetros que pueden cambiar con un cambio del área de visualización del dispositivo (horizontal a vertical en móvil, cambio del tamaño del navegador en desktop, etc) o con el "bounds" de las geometrías cargadas. Así que es conveniente siempre llamar a map.invalidateSize() antes de cambiar el nivel de zoom, si pensamos que se ha producido un cambio en la inforamación de estado del mapa.

martes, 29 de julio de 2014

Operaciones de mantenimiento sobre tablas MyISAM de MySQL sin bloquearlas

Actualmente lookingformaps tiene indexados en torno al millón de mapas, y aproximadamente 30 o 40 millones de puntos de interés. Esto ha hecho que la base de datos con la que gestiona la información haya alcanzado dimensiones considerables (en torno a 10 Gb. de información, incluyendo índices).






Looking4Maps trabaja con la base de datos MySQL, por ser muy lígera, y estar muy extendida (trabajando con ella la casi totalidad de proveedores de servicios de hosting de Internet). Además, MySQL proporciona capacidades de gestión espacial básicas, con tipos geométricos, índices espaciales y operaciones espaciales básicas a nivel de rectángulos envolventes (bounding box).

Sin embargo, para poder trabajar con los tipos de datos geométricos, el motor de almacenamiento con el que trabaje deberá ser obligatoriamente MyISAM.

MyISAM es el motor más rapido en operaciones de lectura, pero cuando se van a realizar operaciones de escritura tiene un gran inconveniente: hace un bloqueo a nivel de toda la tabla, de tal forma que en muchas ocasiones ni siquiera permite que el resto de usuarios puedan realizar operaciones de lectura.

Muchas operaciones de MyISAM (INSERT, UPDATE, ALTER TABLE) realizan un bloqueo a nivel de tabla.


Esto hace que, operaciones tan simples como pueda ser modificar el esquema de la tabla para añadir un campo, pueda llegar a dejar inoperativo Looking4Maps mientras la base de datos termina de hacer sus operaciones internas para añadir el campo a la tabla, puesto que no permite realizar accesos de lectura para los nuevos usuarios.

Frente a esto, la mejor solución que he encontrado es mantener dos tablas sincronizadas: una de trabajo, que es con la que trabaja lookingformaps para proporcionar mapas a los usuarios, y otra de mantenimiento, que es la tabla sobre la que el motor de indexación realiza los volcados de los nuevos mapas que va encontrado, o sobre la que yo opero (previa interrupción del robot de búsquedas) para hacer modificaciones estructurales de la base de datos.

Para ello, MySQL permite hacer una copia instantánea de una tabla así:

CREATE TABLE COPIA_TABLA LIKE TABLA;
INSERT INTO COPIA_TABLA SELECT * FROM TABLA;



De tal forma que las operaciones que se realicen sobre COPIA_TABLA no estorban en absoluto al resto de usuarios, que pueden seguir trabajando ya que los accesos al sistema en producción se hacen contra TABLA, no contra COPIA_TABLA.


Una vez terminadas las operaciones sobre la tabla de escritura, se propagan a la tabla principal así:

RENAME TABLE TABLA TO TABLA_VIEJA;
RENAME TABLE COPIA_TABLA TO TABLA;
RENAME TABLE TABLA_VIEJA TO COPIA_TABLA;

Esta solución funciona para un sistema como Looking4Maps, pensado solo para la búsqueda e indexación, no para la creación de nuevos contenidos. Obviamente, llegado el caso de evolucionar Looking4Maps para que sea un sistema de creación de mapas, habrá que cambiar la arquitectura.







lunes, 28 de julio de 2014

Ríos que atraviesan Tokio: Sumida y Tama (隅田川,多摩川)

En las últimas dos semanas están llegando múltiples visitas a la web de  lookingformaps en busca de los ríos que atraviesan Tokio. Los dos ríos principales que atraviesan Tokio son el río Sumida y el río Tama. Se pueden localizar a través de la categoría Ríos_de_Tokio


Desembocadura del río Sumida en la bahía de Tokio.


El río Sumida (en japonés: 隅田川, Sumida-gawa) se origina en la bifurcación artificial del  río Arakawa en Iwabuchi, y desemboca en la bahía de Tokio. A su paso por Tokyo el rio es bastante caudaloso con lo que es navegable. A parte de pequeños barcos que navegan por sus aguas también existe, en Tokyo, una red de transporte por el rio con diversas paradas. Esta red es llamada 水上バス  (Suijō Basu) , que se podría traducir como autobuses acuáticos o de agua. Los principales embarcaderos de estas lineas son 4: Asakusa, Hama Rikyu, Hinode y Odaiba. Hay varias combinaciones que se pueden hacer pero la mas común suele ser la que va desde Asakusa hasta Odaiba o viceversa, esta ruta puede ser directa o con paradas en otros puertos.

El río Tama (多摩川 Tama-gawa) atraviesa Tokio, estableciendo la línea divisoria entre Tokio y Kanagawa. En Tokio es una zona muy popular para la práctica de deportes o simplemente para echar un día de campo, debido a los parques y pistas deportivas que existen a lo largo de la orilla a su paso por Tokio.


viernes, 15 de noviembre de 2013

Velocidad de carga: cómo incrementar un 100% el tráfico a tu página web (II)

En la entrada anterior mostraba una serie de gráficas, basadas en datos empíricos recabados por Google Analytics, en las que se observaba como las mejoras en el rendimiento y en el tiempo de carga de las páginas del portal de búsqueda de mapas Looking4Maps se tradujeron en un incremento de tráfico del 100%, ocasionado por la mejor valoración  que de esta web de mapas hizo el buscador Google, y en un importante aumento de los ingresos recabados por publicidad. En esta entrada, continuación de la anterior, voy a enumerar cuales han sido las técnicas de optimización aplicadas.

  1. Utiliza una herramienta de diagnóstico: Google Page Insights.

    En primer lugar, antes de optimizar a ciegas, lo conveniente es detectar dónde optimizar. Y si uno de los objetivos de optimizar, además de mejorar la experiencia de usuario de nuestros visitantes, es que Google nos ponga una buena nota y así aparezcamos antes en sus resultados de búsqueda, parece razonable utilizar la herramienta de medición de rendimiento Google Page Insights. Para muestra  la nota que Google Page Insights pone ahora mismo al buscador de mapas Looking4Maps.
  2. ¡ Es la base de datos, estúpido!

    En una aplicación web que va a soportar mucho tráfico, y que almacene sus datos en una base de datos relacional (y en la mayoría de los casos MySQL) la base de datos es el principal cuello de botella. Si vuestra web está basada en un gestor de contenidos o plataforma de publicación, como pueden ser WordPress, Joomla, o Drupal, o en algún software de tipo genérico para gestión de foros, comunidades, etc. es probable que los accesos a la base de datos ya estén muy optimizados, pero aún así, estos serán el principal cuello de botella. La base de datos te da muchas cosas (datos ordenados, prevención ante corrupción de los mismos, facilidad de consulta, etc.) pero a cambio, penaliza en el uso de memoria y en el rendimiento. 

    Looking4Maps está programado íntegramente por mí, en PHP, así que al principio los accesos a la base de datos no estaban muy optimizados.  Si éste también es tu caso, y trabajas con tablas grandes (ahora mismo el buscador de mapas tiene indexadas más de 600.000 entradas), desnormaliza, evita los JOINs, evita consultas anidadas, asegurate de que creas índices en aquellas columnas a partir de las que haces consultas, etc. De cara e mejorar el tiempo de carga de una página, es preferible hacer varias consultas muy rápidas, que una única consulta pero más lenta. Por tanto, el contenido principal de la página se puede cargar con una única consulta a la tabla principal de contenido, y contenidos relacionados existentes en otras tablas se pueden cargar de forma asíncrona mediante peticiones AJAX, que no penalizarán el tiempo de carga de la página.

    Cuando trabajamos con un hosting compartido este factor, la velocidad de ejecución de las consultas a la base de datos, cobra todavía una mayor importancia. La razón es que en un hosting compartido cada cuenta de cliente tiene limitado el número simultáneo de conexiones a la base de datos que puede realizar. Supongamos que nuestra web está en un hosting compartido, y que nuestro proveedor nos permite tener 30 conexiones simultáneas a la base de datos. Si nuestras consultas tardan en ejecutarse, o nuestro tráfico es elevado, puede darse el caso de que no queden conexiones disponibles, por lo que las páginas que quieran hacer uso de la base de datos tendrán que esperar a que se liberen conexiones.

    Y este es precisamente el problema relacionado con la base de datos que pueden sufrir los usuarios de productos como WordPress, en los que las consultas sí que están muy optimizadas. Por muy optimizadas que estén nuestras consultas, el número de conexiones que podemos tener con la base de datos es finito, mientras que el número de usuarios que pueden visitar nuestro sitio web no lo es (en términos de órdenes de magnitud ;) ).

    La solución a este problema pasa por cachear los contenidos de la web, en memoria una pequeña cantidad de los contenidos más accedidos, y en fichero todos los contenidos que nuestra cuota de almacenamiento nos permita. Para Wordpress hay disponibles varios plugins de cache. Para Looking4Maps, desarrollado en PHP, he utilizado la libreria PHPFastCache que permite cachear en memoria y también en una base de datos intermedia SQLite. Algunos frameworks de desarrollo PHP como Code Igniter también permiten cachear, tanto los resultados de consulta a base de datos (caché de datos) como las páginas generadas a los usuarios (caché de vista). ¡Todo sea por minimizar las veces que accedemos a la base de datos!
     
  3. Habilita la compresión GZip de tu servidor web.

    Cuando un navegador solicita una página web a un servidor, en realidad se está descargando decenas, incluso cientos, de pequeños recursos en forma de ficheros, a través del protocolo http. Esto lo podeis ver con herramientas de desarrollador como Firebug o Chrome.


    Cuanto mayor sea el tamaño que tienen estos archivos, mayor será el tiempo que el navegador necesita para descargarlos y por tanto el tiempo necesario para cargar la página desde la que se descargan.

    Afortunadamente, los servidores web están preparados para comprimir estos ficheros (normalmente GZip) y los navegadores para detectar que el servidor permite acceder al contenido comprimido, pedirlo comprimido, y descomprimirlo.

    Si usais un hosting con el panel CPanel, podeis habilitar esta opción en la sección "Software / Services" y entrando en la pantalla "Optimize website". Ahí, podrás optar por comprimir ciertos tipos de contenidos o comprimirlo todo.


    Los archivos de imágen ya suelen utilizar buenos algoritmos de compresión, así que lo ideal es comprimir archivos de texto: html, js, css, etc.

  4. Utiliza CloudFlare CDN (Content Delivery Network).

  5. Una CDN (red de distribución de contenido) es una red de ordenadores distribuída por todo el mundo, que se encarga de cachear tus contenidos estáticos, está diseñado para servirlos por tí a muy alta velocidad (con la que aligera de trabajo a tu servidor) y que además sirve estos contenidos a tus usuarios desde el servidor más cercano a su localización geográfica, con lo que disminuye el "número de saltos" por la red que deben realizar estos contenidos hasta llegar a tus usuarios. En la siguiente figura vemos la distribución de servidores por el mundo del CDN CloudFlare.



    ¿Por qué recomiendo CloudFlare? Es el CDN que yo he utilizado, y además de ofrecer un servicio básico gratuito, ofrece una serie de servicios adicionales de valor añadido que también tienen un impacto directo sobre el tiempo de carga de vuestra web. Una vez que el CDN se descarga nuestro contenido estático, y antes de servirlo desde el servidor más próximo a nuestros usuarios, aplica una serie de optimizaciones de rendimiento sobre dichos archivos.
  • "Minimiza" al vuelo los caracteres innecesarios de ficheros de texto, llegando a reducir su tamaño un 20 %, con el consiguiente ahorro en los tiempos de descarga. 

  • Utiliza las características de almacenamiento local de los navegadores modernos (basadas en html 5) para cachear en el cliente.

  • Ajusta automáticamente las cabeceras http de respuesta de los servidores para que den instrucciones a los navegadores sobre cómo deben cachear los recursos servidos, para minimizar el número de peticiones http que éstos le realizan.
  • Modifican el código HTML de las páginas servidas por las webs originales, para que los recursos que bloqueen la carga de la página (archivos javascript, etc) se carguen al final, de forma que parezca que la página se carga más rápido. 

  • Combina múltiples ficheros javascript en un único fichero, de forma que en lugar de a través de múltiples peticiones http se sirvan a través de una única petición.

Con estos sencillos pasos he conseguido la sensible mejora de tráfico descrita en la entrada anterior. Todavía quedan algunos pasos, especialmente de cara a optimizar la carga de la web para dispositivos móviles, pero parece claro que el retorno de inversión en optimizar la carga de una web es inmediato en términos de posicionamiento en Google.

 


 







Velocidad de carga: cómo incrementar un 100% el tráfico a tu página web (I)

El objetivo de todo creador de una web es que ésta sea utilizada: todos queremos que nuestra web tenga mucho tráfico, que muchas personas la visiten al cabo del día.

Está claro que el primer factor que determinará que una web tenga un tráfico elevado es que su contenido tenga calidad, y despierte el interés de los usuarios. En este sentido, www.lookingformaps.com pretende convertirse en el "Google de los Mapas", localizando e indexando las principales fuentes de mapas existentes en Internet, y ofreciendo a los usuarios un buscador que les permita localizar  rutas, tracks GPS, mapas o guías turísticas y posteriormente descargarselas para planificar sus viajes o excursiones.

No obstante, que un producto sea bueno no es suficiente para que la gente lo "compre", es necesario que la gente lo conozca. Y en el caso de la web, todos en mayor o menor medida nos hemos acostumbrado cuando buscamos algo a entrar en Google y poner dos o tres palabras relacionadas con el mismo. Para muestra un botón: en los últimos cinco meses, en torno al 75 % del tráfico de www.lookingformaps.com procede de búsquedas  de Google.



En este sentido, está claro que para que una web tenga un tŕafico alto, un requisito indispensable es que aparezca en los primeros lugares de los resultados de búsquedas mostrados por Google, para aquellas "palabras clave" relacionadas con los contenidos de dicha web.

Google, para decir "cómo ordena" las páginas webs que va a mostrar como resultado de una búsqueda, tiene en cuenta múltiples criterios: la relevancia que se le da a esa web medida a través del número de links que apuntan a ella, la coincidencia de las palabras de búsqueda con el contenido de la web, etc. Los creadores de sitios web intentan tener en cuenta todos estos factores, que Google ha hecho más o menos explícitos a través de sus Directrices para Webmasters, y optimizar sus páginas para que le gusten a Google y el buscador les de una nota alta: es lo que se conoce como el SEO (Search Engine Optimization).

Pero para Google, no solo es importante que el contenido que una página ofrece sea de calidad, y tenga relación con la búsqueda hecha por el visitante. Google da mucha importancia a que la "experiencia de usuario" que va a tener quien visite esa página sea buena, y un factor muy importante en dicha experiencia de usuario es la velocidad de carga de la página.

Ahora bien ¿hasta cuanto de importante es que una web cargue rápido, para que Google la recompense con un lugar superior en sus resultados de búsqueda?

En los últimos meses he hecho una serie de pruebas para "Looking for Maps", y la verdad es que los resultados obtenidos han sido muy reveladores: la reducción en los tiempos de carga de las páginas de la web ha supuesto como mínimo un incremento del 100% en el tráfico del sitio web, y como efecto secundario, prácticamente un incremento del 100% en los ingresos por publicidad.


 En la gráfica de Google Analytics se pueden observar dos momentos clave:
  1. En el que se produce un primer incremento de tráfico, ligado a una serie de optimizaciones básicas en el código del buscador de mapas.
  2. En el que se produce un segundo "salto" cuantitativo en el número de visitas, ligado a una serie de optimizaciones de rendimiento.
Quizás podamos observar mejor estos "saltos" en el tráfico de LookingForMaps.com si en vez de ver la curva diaria de visitas, más sujeta a fluctuaciones, vemos la curva semanal en esta otra gráfica, en la que también se aprecia mejor como el tráfico semanal en términos de número total de visitas únicas semanales se ha duplicado (de menos de 5.000 a más de 12.000).


Aunque no sea una "regla de proporcionalidad", que siempre se cumpla, lo cierto es que la cuantía de ingresos en publicidad es altamente dependiente del número de visitas de una web, siendo habitual que el número de anuncios en los que hacen clicks nuestros visitantes esté en una orquilla de entre el 0,5 % y el 1,5 %, con lo que el aumento del tráfico se traduce también en un aumento de ingresos por publicidad de la web, como podemos ver también en la siguiente gráfica de ingresos de Google Adsense.



En la siguiente entrada detallaré cuales son las medidas de optimización que he aplicado en la carga de las páginas de lookingformaps.com para mejorar su rendimiento. Simplemente, como dato final, esta gráfica conseguida con la herramienta de Google para Webmaster, en la sección "Rastreo / Estadísticas de Rastreo".



 Se puede ver como el tiempo medio de carga se ha reducido a la mitad, si bien aparecen picos. Estos picos responden a momentos concretos en los que el tráfico ha sido muy elevado, muy por encima de la media. Descontando estos picos, se observa como la "curva" se estabiliza, más cerca del eje de las Xs.