¡Por las barbas de Hari Seldon!

No sé si alegrarme y llenarme de expectativas, o echarme a temblar: según SlashDot se están preparando dos películas basadas en obras de Isaac Asimov, una de ellas de La Fundación.

Todos los que hayáis leído la saga seguro que habéis pensado en algún momento cómo era posible que no se hubiera llevado (o intentado llevar) al cine. La historia es larga y compleja, manteniendo un hilo narrativo a lo largo de varias épocas y con personajes y situaciones muy dispares y continuas referencias hechos, acciones y decisiones anteriores y sus implicaciones en el curso de la historia futura. Es una historia que en libro está muy bien pero en cine, y más a lo largo de varias películas, es complejo mantener la atención del espectador palomitero medio. Aunque como comentan en SlashDot, El Señor de los Anillos tampoco era fácil y no ha quedado mal, y Watchmen tres cuartos de lo mismo y por ahora pinta bien.

Gracias, Fon

Creo que no he escrito nada sobre Fon, al menos no centrándome exclusivamente en el tema aunque lo haya tocado de pasada.

Por si alguien no lo conoce, Fon es una red de gente que comparte vía WiFi su conexión a Internet, con intención de crear un acceso WiFi común global. Para los no-foneros, conectarse a la red es de pago y parte de la cuota va para el propietario del fon-spot (el punto de acceso); si eres fonero, puedes conectarte gratis a cualquier fon-spot, en cualquier parte del mundo. Para hacerte fonero lo único que tienes que hacer es comprar una fonera (el punto de acceso, unos 30€) y conectarla a Internet. La red está bastante extendida, sobre todo en grandes ciudades y en ciertos barrios en los que se han formado comunidades WiFi más o menos activas.

El caso es que me hice fonero casi de rebote: hacía tiempo que había leído sobre el tema y me llamaba la atención pero no me decidía, hasta que empecé a tener problemas con el router WiFi de la ADSL de Telefónica y el iPod Touch, y buscando buscando routers WiFi vi que la fonera era uno de los más baratos, con el añadido de entrar en la red Fon. Tengo la fonera más de seis meses, bastante gente ha pasado por mi fon-spot, pero yo nunca había llegado a usar la conexión fon de otros.

Hasta que me he cambiado de piso. No sabéis lo cómodo que es y la alegría que te llevas al llegar la primera noche que vas a pasar en tu nuevo piso, encender el ordenador y ver un ESSID tipo FON_xxxxxx. :-D Inteneeeeee… X-D

Cuando me fui a vivir a Las Rozas una de las priemras cosas que hice fue llamar a Telefónica para contratar un ADSL. La verdad, comparé ofertas en BandaAncha y algún otro sitio pero no buscando la más barata o la que más ancho de banda tuviera, si no la que menos tardaran en gestionarme el alta y la instalación. X-D Y no me defraudaron, en mi experiencia Telefónica en esos aspectos se lleva el gato al agua (también en el precio, ¡pero al alza!) y en tres o cuatro días tenía Internet. Sin embargo ahora al cambiar de nuevo de piso ya no tengo prisa: llevo dos semanas viviendo aquí y esta vez si que he comparado distintas ofertas, he llamado a unos y otros operadores… y todavía no me he decidido. Ya.com tiene muchas papeletas; la segunda opción sería Orange, pero después de leer la experiencia de algunos, no se yo… Lo que si que acabo de hacer hace un par de días es pedir a Telefónica el traslado de la línea, porque o me quedo con Telefónica, o en cualquier caso para irme con otro operador primero necesito tener línea de Telefónica. Pero vaya, sin prisas. Y sabiendo que si tengo mala suerte y el operador falla, no me instala la línea en dos meses, da problemas… siempre voy a tener un backup WiFi.

Ah, y todo esto con mi fon-spot apagado, claro. No sé si hay algún límite pero … las dos semanas que llevo aquí mi fonera está en un cajón y yo sigo con acceso a la red. Y estoy haciendo uso, no me estoy bajando cosas a lo loco pero Dexter, The Big Bang Theory, The IT Crowd, Terminator The Sarah Connor Chronicles y Heroes caen religiosamente tooodas las semanas. :-)

Gracias, Fon. Y en particular, gracias, FON_antonio. :-D

Retorno de las estrellas

Verás…, imagínate una reunión de mundos. Primero rosa, un espacio infinito del rosa más fino y pálido, y en él, penetrando en él, un segundo espacio ya más oscuro y después de un rojo ya casi azulado, pero muy lejos, y rodeándolo todo, la fosforescencia, sin gravedad, no como una nube ni como la niebla…, diferente. No encuentro palabras para explicarlo. Salimos los dos del cohete y lo contemplamos. Eri, no lo comprendo. Verás, incluso ahora siento un nudo en la garganta, de tan hermoso que era. Piensa esto: allí no hay vida. No hay plantas, ni animales, ni pájaros, nada, ningunos ojos que puedan contemplarlo. Estoy completamente seguro de que desde la creación del mundo nadie lo había visto, y Arder y yo fuimos los primeros. Y si nuestro gravímetro no se hubiera estropeado, por lo que tuvimos que aterrizar allí para arreglarlo, pues el cuarzo estaba roto y se había escapado el mercurio, nadie habría estado allí hasta el fin del mundo, nadie lo habría visto. ¡Es realmente misterioso! Se tienen unos deseos directos… Oh, no sé… No podíamos irnos, sencillamente. Olvidamos por qué habíamos aterrizado y permanecimos quietos, mirando.

¿Qué te ha demostrado Starck? ¿La inutilidad de la cosmodromía? ¡Como si no lo supiéramos nosotros mismos! ¿Y los polos? ¿Qué había en los polos? Los hombres que los conquistaron sabían muy bien que allí no había nada. ¿Y la Luna? ¿Qué buscaba el grupo de Ross en el cráter Eratóstenes? ¿Brillantes? ¿Y por qué Bant y Yegorin han atravesado el centro del disco de Mercurio? ¿Para adquirir un buen bronceado? ¿y Kellen y Offshag? Lo único que sabían cuando volaron a la fría nube de Cerbero era que allí se puede perder la vida. ¿Has entendido lo que Starck dice realmente? El hombre ha de comer, beber y vestirse; todo lo demás es una locura. Todos tenemos nuestro propio Starck, Bregg. Cada era lo ha tenido. ¿Para qué os envió Gimma a ti y a Arder? Para que recogierais muestras con el succionador Corona. Pero ¿quién envió a Gimma? La ciencia. Qué profesional suena esto, ¿verdad? El conocimiento de las estrellas.

Bregg, ¿crees que no hubiéramos volado, de no existir las estrellas? Yo creo que sí. Habríamos querido conocer el espacio, para justificar el todo de alguna manera. Geónidas o cualquier otro nos diría qué mediciones y descubrimientos valiosos se pueden hacer por el camino. No me interpretes mal. No estoy afirmando que las estrellas sean solamente un pretexto… El polo tampoco lo fue; Nansen y Andree lo necesitaban… El Everest fue más necesario para Irving y Mallory que el aire mismo.

Retorno de las estrellas, Stanis?aw Lem

Optimizando el SQL de osCommerce

[spanish]Un apunte sobre la importancia de cómo la forma en que se construye una query en SQL afecta a su rendimiento, algo en lo que creo que mucha gente no se fija y que he sufrido en mis carnes laboralmente más de una vez:

Llevo unos días pegándome con la base de datos de una tienda on-line con osCommerce. Todo funcionaba de maravilla hasta que tras cuatro años de andadura tenemos una BD con más de 1500 categorías y 16000 productos, y la web cada día va un poco más lenta. Usamos osCommerce 2.2 con bastantes modificaciones por lo que ni me he planteado probar la 3, además de que aún es alpha y esto es producción, así que toca ponerese a optimizar. Y después de optimizar el sistema y no conseguir grandes mejoras, toca mirar el código y la BD.

Primero y principal: activar log-slow-queries en la configuración de mySQL. Esto genera un nuevo log con las queries que tardan más de la cuenta en ejecutar (p.def más de 10 segundos, pero es configurable). Con la información de éste log podemos ir viendo qué queries son las que más están penalizando el rendimiento global, e ir optimizando.

Para que os hagáis una idea, esta es la query que saca todos los productos de una categoría:

[code lang=”sql”]
SELECT pd.products_name, pd.products_description, m.manufacturers_name,
p.products_image, p.products_quantity,  p.products_date_added,
p.products_id, p.manufacturers_id, p.products_price,
p.products_tax_class_id, IF(s.status, s.specials_new_products_price, NULL)
as specials_new_products_price, IF(s.status, s.specials_new_products_price,
p.products_price) AS final_price
FROM products_description pd, products p
LEFT JOIN manufacturers m ON p.manufacturers_id = m.manufacturers_id
LEFT JOIN specials s ON p.products_id = s.products_id,
products_to_categories p2c
WHERE
p.products_status = ‘1’ AND p.products_id = p2c.products_id AND
pd.products_id = p2c.products_id AND pd.language_id = ‘3’ AND
p2c.categories_id = ‘114’
ORDER BY p.products_date_added DESC;
[/code]

En primer lugar para quien no conozca osCommerce, hay un mazo de tablas. En este ejemplo seis: categorías, productos, productos por categoría, descripción de producto (para tenerlas en varios idiomas), fabricantes y productos en oferta (specials). Según el mysql-slow.log, esa query estaba tardando del orden de 12 segundos, y también indicaba “Rows_examined: 1903433”. No me extraña que tardara tanto. :-D

¿Por qué tantas “rows_examined”? Repasemos teoría de BD de las clases de la universidad. ;-D Optimizaciones de cada motor de BD aparte, al hacer un select de varias tablas se hace el producto cartesiano de sus tuplas, un “todos con todos”, del que luego se extraen aquellas que coincidan con las condiciones del where (digo lo de optimizaciones aparte porque no me salen los números: 16k productos, 16k descripciones, 1k5 categorías, ¿1903433 combinaciones?). En cambio con los JOIN, a las tuplas que ya se han seleccionado con where Y SÓLO A ESAS, se les añade la información relacionada de otra tabla.

Entonces, ¿por qué usar varias tablas en el where si no es estrictamente necesario? En el caso anterior, queremos extraer toda la información de los productos de una categoría, y hacemos from de la tabla productos. ¿No sería mejor basar la query en products_to_categories, restringiendo a la categoría que nos interesa, y con join añadir el resto de información? Es por lo mismo que a la hora de encadenar varios comandos en shell-script, primero se hace el grep y luego el cut: para restringir cuanto antes el nº de datos a tratar en los siguientes pasos, reduciendo así el tiempo de proceso cada vez más.

Con este cambio, la query queda así:

[code lang=”sql”]
SELECT pd.products_name, pd.products_description, m.manufacturers_name,
p.products_image, p.products_quantity,  p.products_date_added,
p.products_id, p.manufacturers_id, p.products_price,
p.products_tax_class_id, IF(s.status, s.specials_new_products_price, NULL)
AS specials_new_products_price, IF(s.status, s.specials_new_products_price,
p.products_price) AS final_price
FROM products_to_categories p2c
LEFT JOIN products p ON p.products_id=p2c.products_id
LEFT JOIN products_description pd ON p.products_id=pd.products_id AND pd.language_id=’3′
LEFT JOIN manufacturers m ON p.manufacturers_id = m.manufacturers_id
LEFT JOIN specials s ON p.products_id = s.products_id
WHERE p2c.categories_id = ‘114’ AND p.products_status = ‘1’
ORDER BY p.products_date_added DESC
[/code]

El resultado de ambas queries es el mismo, pero mientras que la primera estaba tardando unos 12s en ejecutar, la segunda lo hace en unos pocos milisegundos. :-) He encontrado ya varios casos de queries que se han podido optimizar así, con los mismos resultados de bajar el tiempo de ejecución de +10s a unos pocos ms.

Otro punto importante son los índices: ha habido casos de queries que no se podían optimizar, ya usaban una única tabla en el from, la más restrictiva, y luego algún join. En estos casos lo que he visto es que no había índice en la clave ajena de una de las dos tablas, y añadiendo un íncide a esa columna los tiempos se han reducido igual que con las optimizaciones anteriores, de segundos a milisegundos.
[/spanish]
[english]
An entry about how the way SQL queries are built affects their performance, something I think many people don’t pay attention to:

For the last couple of days I’ve been struggling with the database of an osCommerce-based on-line shop. Everything ran perfectly until after four years of existence we have a DB with over 1500 categories and 16000 products, and the web is getting increasingly slower over time. We use a heavily modified osCommerce 2.2 installation, so moving to osCommerce 3 is out of the question (besides, it’s still alpha and this is a production site). After some hw and os-level optimization that didn’t help to improve performance, I had to start digging into the code and the DB.

First of all: activate the log-slow-queries option on mySQL’s config. This creates a new log with all the queries that take longer than expected to run (def. more than 10 seconds). With this information, we can concentrate on those queries that are hitting the bigger performance penalties and start the optimization process there.

To get you on context, this is the query that extracts all the products from a given category:

[code lang=”sql”]
SELECT pd.products_name, pd.products_description, m.manufacturers_name,
p.products_image, p.products_quantity, p.products_date_added,
p.products_id, p.manufacturers_id, p.products_price,
p.products_tax_class_id, IF(s.status, s.specials_new_products_price, NULL)
as specials_new_products_price, IF(s.status, s.specials_new_products_price,
p.products_price) AS final_price
FROM products_description pd, products p
LEFT JOIN manufacturers m ON p.manufacturers_id = m.manufacturers_id
LEFT JOIN specials s ON p.products_id = s.products_id,
products_to_categories p2c
WHERE
p.products_status = ‘1’ AND p.products_id = p2c.products_id AND
pd.products_id = p2c.products_id AND pd.language_id = ‘3’ AND
p2c.categories_id = ‘114’
ORDER BY p.products_date_added DESC;
[/code]

On the first place for those unfamiliar with osCommerce, there are a heap of tables involved. Six on this example: categories, products, products_to_categories, products_description, manufacturers and specials (products with discounted prices). According to mysql-slow.log, that query was taking around 12 seconds to run. The log also read “Rows_examined: 1903433”. No wonder it was taking that long. :-D

Why so many “rows_examined”? Going back to DB theory on the University, ;-D if memory serves and leaving each particular DB engine’s optmizations aside, when selecting from several tables the DB engine does the cartesian product (product set) of all their tuples, this is, every possible combination of each tuple on one table with every other tuple on the other tables. After that, those combinations matching the WHERE criteria are selected (and I say engine optimizations aside because I don’t get the math here: 16k products, 16k descriptions, 1k5 categories, and just 1903433 combinations?). On the other hand with JOIN the related data is appended to those tuples already selected by the WHERE clause and ONLY TO THOSE.

So, why using more than one table on the where clause if it isn’t strictly necessary? On the previous example, we want to extract all the info of the products on a given category, but we select from the products, products_description and products_to_categories tables. Wouldn’t it be better using only the products_to_categories table on the from clause, reducing the result set to only the category we are interested on, and then get all the products’ information with JOIN? It’s the same principle as when piping data through several commands on a shell-script, using first grep and then cut: first reduce the data set, so that on each following step there is less data to spend CPU cycles with, increasingly reducing the overall processing time.

With this modification, the resulting query looks like:
[code lang=”sql”]
SELECT pd.products_name, pd.products_description, m.manufacturers_name,
p.products_image, p.products_quantity, p.products_date_added,
p.products_id, p.manufacturers_id, p.products_price,
p.products_tax_class_id, IF(s.status, s.specials_new_products_price, NULL)
AS specials_new_products_price, IF(s.status, s.specials_new_products_price,
p.products_price) AS final_price
FROM products_to_categories p2c
LEFT JOIN products p ON p.products_id=p2c.products_id
LEFT JOIN products_description pd ON p.products_id=pd.products_id AND pd.language_id=’3′
LEFT JOIN manufacturers m ON p.manufacturers_id = m.manufacturers_id
LEFT JOIN specials s ON p.products_id = s.products_id
WHERE p2c.categories_id = ‘114’ AND p.products_status = ‘1’
ORDER BY p.products_date_added DESC
[/code]

The result sets of both queries are identical, but while the first one took around 12 seconds to run, the second one only takes a couple of milliseconds. :-) I’ve found several similar cases already on my osCommerce installation, that have gone from more than 10 seconds to milliseconds after rearanging the query in a similar fashion.

Another important point are indexes: I’ve found some queries that couldn’t be further optimized, they already used only one table on the from clause, the more restricting one, and then some joins. On these cases what I’ve found is that some table lacked an index on the foreign key column, and after adding it the execution time has gone down again from seconds to milliseconds.
[/english]