mardi 24 mars 2009

Vive le strip binaire !

Je pense que nous sommes nombreux à utiliser MySQL Sandbox qui est un outil très utile pour pouvoir par exemple tester rapidement une version particulière de MySQL, installer plusieurs versions sur le même serveur, configurer une réplication multi-slaves en quelques commandes etc...
Cependant, cela nécessite de récupérer une archive binaire pour chaque version que vous voulez utiliser, ce qui peut consommer rapidement pas mal d'espace !

Par exemple, j'ai récupéré la version 5.0.67 qui, sous forme d'archive, occupe 113Mo et une fois décompressée près de 398Mo ! Il était donc temps que je prenne quelques initiatives :-)

du -sh mysql-5.0.67-linux-i686.tar.gz
113M mysql-5.0.67-linux-i686.tar.gz

tar xzf mysql-5.0.67-linux-i686.tar.gz
du -sh mysql-5.0.67-linux-i686
398M mysql-5.0.67-linux-i686

Je commence par supprimer les répertoires qui me seront inutiles

rm -rf docs/ sql-bench/ mysql-test/ tests/ man/

Ceci me permet d'économiser 45Mo. Ensuite je strippe les binaires et les bibliothèques

strip bin/* lib/* 2>/dev/null

Et encore 242 Mo d'économisé !

Enfin, je supprime les binaires qui me seront inutiles

rm -f *debug* *test* *ndb*

Cette dernière étape me récupère encore 20Mo. A la fin de cette série d'actions, l'archive décompressée n'occupe plus que 68 Mo soit près de 6 fois moins que la taille de départ.

La question que vous devez vous poser est la suivante : que fait la commande strip ?

elle supprime tous les symbols et numéros de lignes des binaires et bibliothèques, qui peuvent être utiliseés lorsque vous debuggez vos programmes, ce qui n'est pas mon cas.
Blogged with the Flock Browser
Lire la suite...

jeudi 12 mars 2009

Défaillance du Query Cache de MySQL

Il arrive que le Query Cache ne fonctionne pas :-(

J'ai par exemple était confronté à un problème concernant le Query Cache lorsque j'ai voulu optimiser certaines requêtes effectuées sur un serveur. En fait, l'optimiseur de MySQL, en version 4.1.11, ne faisait pas le bon choix d'index selon certains cas, et scinder la requête en 2 sous requêtes unifiées ensuite permettait d'obtenir de meilleurs performances. Attention, ce n'est pas toujours le cas !
En exécutant plusieurs fois les 2 requêtes unitairement le Query Cache fonctionnait correctement comme vous pouvez le voir au temps d'exécution qui était > 2 s.

mysql> SELECT idancetre,max(idmess) mxd FROM messages USE INDEX (idx_tst3) WHERE idancetre not in (1021576,0) AND avalider=0 AND idmess>1021576 GROUP BY idancetre ORDER BY mxd DESC LIMIT 5;
+-----------+---------+
| idancetre | mxd |
+-----------+---------+
| 1993983 | 1995071 |
| 1994149 | 1995069 |
| 1995061 | 1995061 |
| 1994957 | 1995059 |
| 1994250 | 1995058 |
+-----------+---------+
5 rows in set (0.00 sec)


mysql> SELECT idmess,idmess FROM messages WHERE idancetre=0 AND avalider=0 AND idmess>1021576 GROUP BY idmess ORDER BY idmess DESC LIMIT 5;
+---------+---------+
| idmess | idmess |
+---------+---------+
| 1169825 | 1169825 |
| 1169813 | 1169813 |
| 1169795 | 1169795 |
| 1169758 | 1169758 |
| 1169756 | 1169756 |
+---------+---------+
5 rows in set (0.00 sec)


Par contre, la requête UNION composée des 2 requêtes précédentes n'était jamais stockée dans le Query Cache :

mysql> (SELECT idancetre,max(idmess) mxd FROM messages USE INDEX (idx_tst3) WHERE idancetre not in (1021576,0) AND avalider=0 AND idmess>1021576 GROUP BY idancetre ORDER BY mxd DESC LIMIT 5) UNION (SELECT idmess,idmess FROM messages WHERE idancetre=0 AND avalider=0 AND idmess>1021576 GROUP BY idmess ORDER BY idmess DESC LIMIT 5);
+-----------+---------+
| idancetre | mxd |
+-----------+---------+
| 1993983 | 1995071 |
| 1994149 | 1995069 |
| 1995061 | 1995061 |
| 1994957 | 1995059 |
| 1994250 | 1995058 |
| 1169825 | 1169825 |
| 1169813 | 1169813 |
| 1169795 | 1169795 |
| 1169758 | 1169758 |
| 1169756 | 1169756 |
+-----------+---------+
10 rows in set (2.55 sec)


Vous le comprendrez bien, dans ce cas là, il est préférable d'exécuter les 2 requêtes de façon distincte et d'effectuer l'UNION côté applicatif plutôt qu'en une seule et même requête. En fait, dans cette version c'était dû à un BUG référencé chez MySQL et corrigé en 4.1.17, 5.0.17 et 5.1.4

SELECT queries that began with an opening parenthesis were not being placed in the query cache. (Bug#14652)


En 5.0.67, qui contient le bugfix, ce n'est pas la même chose :

mysql> (SELECT idancetre,max(idmess) mxd FROM messages USE INDEX (idx_tst3) WHERE idancetre not in (1021576,0) AND avalider=0 AND idmess>1021576 GROUP BY idancetre ORDER BY mxd DESC LIMIT 5) UNION ALL (SELECT idmess,idmess FROM messages WHERE idancetre=0 AND avalider=0 AND idmess>1021576 GROUP BY idmess ORDER BY idmess DESC LIMIT 5);
+-----------+---------+
| idancetre | mxd |
+-----------+---------+
| 1993983 | 1995071 |
| 1994149 | 1995069 |
| 1995061 | 1995061 |
| 1994957 | 1995059 |
| 1994250 | 1995058 |
| 1169825 | 1169825 |
| 1169813 | 1169813 |
| 1169795 | 1169795 |
| 1169758 | 1169758 |
| 1169756 | 1169756 |
+-----------+---------+
10 rows in set (0.00 sec)


Il est donc très important de vérifier les changement apportés par les versions mineures qui suivent la version que vous utilisez pour savoir quels bugs ont été corrigés, et lesquels peuvent vous concerner.

Blogged with the Flock Browser
Lire la suite...

L'art de blogguer sous Blogger

Nous sommes plusieurs à utiliser ce template sous Blogger et je dois dire que je le trouve assez professionnel :-)
Cependant, je n'appréciais pas plusieurs choses :
- la largeur par défaut
- les coins arrondis
- l'impossibilité d'afficher un résumé des posts sur la page d'accueil

J'ai donc du modifier le template pour pouvoir satisfaire tout ce dont j'ai besoin d'un blog et que Blogger ne m'offre pas par défaut !

En prérequis, il faut bien sûr que vous vous connectiez sur votre dashboard en allant sur https://www.blogger.com/start. Ensuite, vous vous rendez dans la section Layout du blog que vous voulez modifier, puis dans Edit HTML.
Il ne vous reste plus qu'à cocher la case dans la partie Edit Template.

Modifier la largeur par défaut

Recherchez Page Structure dans la zone d'édition car les modifications à faire se trouvent dans les css qui suivent. Il suffit ensuite d'augmenter la valeur du paramètre width pour les IDs outer-wrapper, main-wrap1, main-wrap2, main et les classes main, Blog :

#outer-wrapper {
width:930px;
....
}
#main-wrap1 {
width:680px;
...
}
#main-wrap2 {
width:100%;
...
}
#main {
width:655px;
...
}
.main .widget {
width: 668px;
...
}

.main .Blog {
width: 684px;
...
}

Supprimer les coins arrondis

Le fait de modifier la largeur par défaut nécessite de modifier les images de fond utilisées pour affichée des coins arrondis. J'ai donc préféré supprimer ces coins en retirant les images de fond http://www1.blogblog.com/rounders2/corners_main_bot.gif et http://www1.blogblog.com/rounders2/corners_main_top.gif pour les IDs main-wrap1 et main-wrap2.

Afficher le résumé de certains posts sur la page d'accueil

Pour pouvoir résoudre cette problématique je me suis inspiré des posts http://help.blogger.com/bin/answer.py?hl=en&answer=42215 et http://coderstalk.blogspot.com/2008/06/how-to-create-expandable-post-summaries.html#step-01.
La démarche est assez simple une fois qu'elle est bien cernée. Il faut suivre les étapes suivantes :

- rechercher la balise <div class='post-header-line-1'/> et ajouter le code suivant à la suite :

<style>
<b:if cond='data:blog.pageType == &quot;item&quot;'>
span.fullpost{display:inline;}
<b:else/>
span.fullpost{display:none;}
</b:if>
</style>


- rechercher la balise <div class='post-footer'> et ajouter le code suivant avant cette balise :

<b:if cond='data:blog.pageType != &quot;item&quot;'>
<b:if cond='data:post.labels'>
<b:loop values='data:post.labels' var='label'>
<b:if cond='data:label.name == &quot;more&quot;'>
<a expr:href='data:post.url'><b>Lire la suite...</b></a>
</b:if>
</b:loop>
</b:if>
</b:if>


- se rendre sur le dashboard
- cliquer sur l'option Settings de votre blog
- choisir l'onglet Formatting
- renseigner la partie Post Template avec :

<span class="fullpost">
</span>


Vous pouvez maintenant rédiger ou modifier vos anciens posts pour n'en afficher qu'un résumé sur la page d'accueil en respectant les 2 règles suivantes :

- Mettez la partie que vous voulez voir apparaître sur la page principale en dehors de la balise <span class=fullpost>
- Ajoutez le label more à votre post

Comme Rien ne vaut mieux qu'un petit exemple, voici un exemple de post que vous pouvez utiliser :

salut à tous, ce post n'est qu'un exemple parmis d'autres.
Je voudrais vous en dire plus mais<span class="fullpost">, en fait je n'ai pas grand chose à dire :-)</span>

Bien sûr vous n'oublierez pas d'ajouter le label more à ce post comme indiqué dans la méthode.
Blogged with the Flock Browser
Lire la suite...

jeudi 5 mars 2009

Calcul de la médiane en MySQL

Comme vous avez pu le lire dans mon article précédent où je donne la méthode de calcul de la médiane en SQL cette formule fonctionne correctement mais peut prendre un temps extrêmement longs lorsque le nombre de lignes à étudier augmente.
Ainsi dans cette article j'obtenais le résultat au bout de 5 secondes pour une table qui contenait 4874 lignes. J'ai eu à faire le test pour une table contenant plus de 600 000 lignes et là, après 2 heures de calculs, je n'ai plus eu la patience d'attendre le résultat.
J'ai donc cherché sur le net et trouvé un projet qui fourni une fonction median en UDF. Cependant, elle ne fonctionne pas pour les versions de MySQL > 4.1.0. Je me suis donc intéressé au code et après quelques minutes j'ai pu le faire tourner sur ma version 5.0.32 !
Miracle! En utilisant cette fonction ma requête prend moins d'1 seconde !
Finalement, j'ai bien fait de me pencher vers l'utilisation d'une UDF (user-defined function)

Pour ceux qui désirent utiliser cette méthode, il suffit de suivre la procédure suivante :

cd /tmp
wget http://sites.google.com/site/filesrepository01/Home/udf_median.cc -O udf_median.cc
apt-get install libmysqlclient15-dev
gcc -Wall -I /usr/include/mysql -c udf_median.cc -o udf_median.o
ld -shared -o udf_median.so udf_median.o
cp udf_median.so /usr/lib
rm -f udf_median.*


Il ne vous reste plus qu'à vous connecter à votre serveur MySQL et à créer la fonction median :

mysql test
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 5
Server version: 5.0.32-Debian_7etch8 Debian etch distribution

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> DROP FUNCTION median;
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE AGGREGATE FUNCTION median RETURNS REAL SONAME 'udf_median.so';Query OK, 0 rows affected (0.00 sec)

mysql> SELECT semaine,MEDIAN(duree) FROM t1 GROUP BY semaine;
+---------+---------------+
| semaine | MEDIAN(duree) |
+---------+---------------+
| 1 | 11.50 |
| 2 | 22.00 |
| 3 | 13.00 |
+---------+---------------+
3 rows in set (0.84 sec)

C'est donc plus rapide que nos 5 secondes de mon dernier article et sur une table qui est 125 fois plus volumineuse. Autres chiffres, sur une table de 10000 enregistrements composée de 4 colonnes (int, varchar(255),int,varchar(20)), la 1ère méthode (pur SQL) obtient le résultat en 2 min 55.24 sec, tandis que cette méthode prend 10ms.
Blogged with the Flock Browser

Lire la suite...