[ N O C T E R N I T Y ] Une contribution à la pollution générale de l'internet

25Oct/110

Destruction massive

La première étape des travaux dans la maison n'entrait pas vraiment dans la catégorie des "réparations". En effet, la plupart des travaux prévus impliquaient d'arriver au plâtre brut sur les murs et les plafonds, d'enlever les revêtements de sol, etc. Cette tâche nous a occupés de juillet à octobre 2009. Comme ça serait très répétitif de présenter ce genre d'activités dans toutes les pièces la maison, je vais présenter quelques endroits emblématiques.

L'entrée est un bon exemple de ce que nous avions à affronter dans la plupart des pièces du rez-de-chaussée.

La tapisserie, c'est facile de l'enlever, mais il y a quelque chose de bien bien pire : des dalles de polystyrène collées sur tous les plafonds. Non seulement elles sont difficiles à arracher à cause de la positions dans laquelle on se trouve pour le faire, mais encore on laisse généralement des bouts de peau et donc du sang sur le plafond au bout de quelques heures. Qui plus est, c'est impossible d'enlever les plots de colle sans arracher une partie du plâtre du plafond. Au final, nous avons des plafonds qui ressemblent à des champs de petits trous ronds.

Nous avons terminé avec :

  • d'une part, des murs et des plafonds à peu près "corrects", à l'exception des trous qu'il faudra reboucher
  • d'autre part, plein de déchets qu'il nous fallait évacuer à la déchéterie régulièrement. En fait, on y allait si souvent qu'on était quasiment à tu et à toi avec les employés !

Un autre endroit intéressant était le coin salle à manger / salon. Si vous vous rappelez les photos des origines, ces pièces avaient non seulement les merveilleuses dalles de polystyrène au plafond, mais aussi du lambris sur les murs. Manu s'est bien amusé à les arracher ainsi que... les plots de colle... les travaux en cours donnent une bonne idée de l'étendue des dégâts.

Au final, ça nous a permis d'atteindre ce genre d'état :

Pour finir, la situation au premier étage... Nous avions :

  • Les éternelles dalles de polystyrène au plafond, bien sûr, qu'il était impossible de décoller sans emporter au passage une partie du papier des plaques de plâtre,
  • Des moquettes au sol. Elles étaient plutôt faciles à enlever, même si une partie était cachée sous un parquet flottant..
  • Des moquette murales. C'était vraiment l'horreur parque qu'une espèce de couche de papier restait collée aux plaques de plâtre, avec toute la difficulté qu'on peut imaginer pour l'enlever...

Par exemple, nous sommes passés par ce genre d'état dans le couloir du premier étage :

...ou dans une des chambres :

Et après bien des efforts, nous avons atteint ce stade à peu près final :

  • Dans le couloir :

  • Dans la chambre :

Pour être honnête, c'était une activité franchement déprimante : en effet, nous étions en train de détruire ce qui était en état plus ou moins décent ou du moins habitable, pour atteindre le squelette de la maison. Mais la situation allait bientôt s'améliorer, ce que je raconterai dans un prochain post.

20Oct/110

Incompétence criminelle

J'ai du boulot à faire, des posts plus importants à écrire, mais pour l'instant, cela devra attendre. J'ai besoin de me passer les nerfs, en ce moment.

Pour l'instant, je me cogne une mission d'administration système au cours de laquelle je dois inspecter et (éventuellement, lol) réparer quelques serveurs tournant dans des VMs.

D'abord, la documentation. Attendez, je ne peux pas commencer par ça, il n'y a rien. Une liste d'adresses IP, quelques mots de passe tellement faibles qu'ils font peur. Une liste de commandes shell, hors de tout contexte. Une liste de tâches effectuées, ou du moins je pense qu'il s'agit de cela (la liste contenant des entrées telle que "Accéder à phpmyadmin" suivie par "Changement de niveau d'init"). Circulez, y a rien à voir.

La liste des machines virtuelles inclut une passerelle SSH, depuis laquelle je suis supposé me connecter aux autres VM. Bah, ça me semblait une bonne chose, pour pouvoir accéder à tout ce dont j'avais besoin. Bien entendu, comme la "documentation" ne liste pas vraiment les adresses associées aux machines, c'est peu pratique. Surtout que l'ICMP est totalement bloqué (depuis où? mystère), ce qui n'aide pas. Et une bonne partie des VMs n'écoutent pas sur le port 22. Le port a-t-il été modifié? Est-il bloqué par des règles iptables? Pas la moindre foutue idée. En plus, il était possible de se connecter directement en root sur la passerelle, avec un mot de passe tout pourri (pas "test" mais presque).

Pour l'instant, je n'ai pu trouver que 3 des 11 VMs que je dois inspecter (et cela inclut la passerelle sus-mentionnée). Chaque hôte comprend une installation complète de Gnome, et un assortiment plus ou moins aléatoire de trucs inutiles (laptop-detect? Heu, mon gars, c'est une VM!). Bien sûr, comme la ... personne ... qui a fait ça a décidé d'utiliser Ubuntu Server, il n'est même pas possible de virer dbus, parce qu'upstart en dépend; malgré tout, j'ai réussi à enlever quelques gigaoctets de merdier sur les serveurs auxquels j'ai accédé.

Je m'attends, alors que je continue à y travailler, à encore plus de mauvaises surprises. En attendant, mon opinion, c'est que la personne qui a commis ces actes indescriptibles devrait sérieusement réfléchir à ne plus faire d'administration système et à se reconvertir dans, oh, je sais pas, le balayage de sols. Ou quelque chose comme ça.

16Oct/110

La maison : les origines

Jusqu'à présent, tous les posts que nous avons écrits à propos de la maison concernaient le jardin. Forcément, c'était facile : je les avais déjà écrits pour un forum de jardinage et tout ce qu'il y avait à faire, c'était de les copier et de les traduire en anglais. Un jour ou l'autre, il fallait bien qu'on commence à parler des travaux dans la maison. Mais avant de s'y mettre pour de bon, il est quand même logique de présenter de quoi on est partis.  Donc, voici les photos qu'on a prises au printemps 2009, alors que les anciens propriétaires habitaient encore la maison. L'idée est de donner un aperçu des l'étendue des travaux que l'on avait à faire...

J'espère être en mesure de poster quelques plans pour accompagner les photos à un moment ou à un autre. Pour le moment, nous n'avons que des versions papier et je vais devoir les scanner...

Au sujet des réparations que nous avions l'intention d'effecture, nous avons décidé de faire appel à un maître d'œuvre pour nous conseiller, puis nous assister dans le choix des entreprises et enfin assurer le bon déroulement du projet. Au final, voici (en gros) ce que nous avions prévu:

  • terrassement:
    • installation d'une cuve de récupération d'eaux de pluie
  • menuiserie:
    • au sous-sol, installation de portes isolées
    • au rez-de-chaussée, changement de la porte-fenêtre de la cuisine, qui n'était pas du même modèle que les autres
    • au premier étage, installation de nouvelles portes
  • plâtrerie:
    • au sous-sol, isolation des plafonds et création d'une nouvelle pièce (qui devrait devenir la salle serveurs)
    • au rez-de-chaussée, suppression du mur entre l'entrée et le salon, et ajout de placards muraux dans l'entrée et les 3 chambres
    • au premier étage, réfection des bandes sur les parties verticales pour améliorer l'isolation et modifications des murs existants
  • électricité:
    • remplacement complet de l'installation électrique
    • ajout de câbles RJ45 dans toutes les pièces (pour réseau, téléphonie ou encore télévision)
    • installation d'une armoire de brassage dans la future salle serveurroom
  • plomberie:
    • remplacement complet de l'installation de chauffage (chaudière, radiateurs et tuyauterie)
    • installation d'une salle de bain supplémentaire au premier étage
    • connexion de la maison au réseau de gaz de ville
    • mise en place du réseau pour la récupération d'eaux de pluie
    • remplacement des pompes à eau utilisées dans la cave
  • carrelage:
    • au sous-sol, carrelage de la salle serveur
    • au rez-de-chaussée, remplacement des carreaux beurk dans l'entrée, le salon, la salle à manger, la cuisine et le couloir
    • au premier étage, carrelage des toilettes et de la salle de bain
  • peinture:
    • réparation et peinture sur les murs et plafonds des deux cages d'escaliers

Nous avons demandé les devis en Juillet 2009, nous les avons signé en Septembre et les travaux ont réellement commencé en Octobre. Comme vous pouvez le deviner, cela implique pas mal de récits!

15Oct/110

Champ de Mines v3.0

Jusqu'à aujourd'hui, je n'avais pas eu le temps de raconter la suite de l'aventure du terrassement. Maintenant qu'on approche de la fin de l'épisode, il commence à être temps.

En attendant que notre ami le plombier vienne sauver notre circuit de récupération d'eau, je me suis occupée tout d'abord de rajouter du sable pour alléger la terre et de de replanter les massifs qui avaient souffert des travaux.

Au niveau du massif sous le balcon, le gaura et la sauge arbustive avaient bien souffert de leur stage en seau à cause de la pluie et donc de l'eau stagnante dans le seau. Le gaura, dont les racines étaient complètement pourries, a été diagnostiqué comme mort. j'ai laissé une chance à la sauge. J'en ai aussi profité pour faire quelques remaniements afin d'y mettre un des rosiers et de céanothe du massif de la cuve, de rajouter quelques nouvelles plantes et d'éviter que des branches viennent trop sur l'allée. Mi-septembre, ça avait l'air déjà correct.

Pour le massif de la cuve, j'ai remis la plupart des plantes déjà présentes et j'en ai rajouté quelques unes.Toujours mi-septembre, le massif a repris partiellement visage humain, même si une partie reste encore plutôt vide (à droite) et si j'ai de grosses inquiétudes pour le rosier rampant qui s'est fait arracher brutalement par les terrassier . Il faut aussi dire que c'est tellement mouillé qu'il est quasiment impossible de casser les mottes pour mieux aplanir : ça colle vraiment trop.

A ce jour, je suis plutôt rassurée : le rosier rampant et la sague refont des feuilles, donc ils ne sont pas morts. Et tout à l'air de bien se porter :

  • Massif sous le balcon

  • Massif de la cuve

 

OK, j'avoue, il y a tout plein d'adventices qu'il faudrait que je chasse, et les lecteurs attentifs auront noté qu'au niveau de la cuve, j'ai encore un hortensia en seau et un tas de terre dans le fond. L'opération "sauvons le circuit d'eau de pluie" s'est avérée épique : Le plombier est repassé 3 fois :

  • La première, il s'est rendu compte que la fuite était à l'extérieur, après avoir fait un trou dans la contre-cloison de du local technique. Et pour l'extérieur, en creusant, il a découvert que le regard de la descente de gouttière, changé en décembre par les terrassiers, fuyait. Sans doute une autre conséquence de la chute du terrassier :( De plus, il n'avait pas de tuyaux du bon diamètre et les tentatives de réparation en mode bricole ont provoqué quelques geysers. D'après Manu, il a évité de peu d'être inondé de la tête aux pieds,
  • La seconde, il a réparé le plus gros mais il y avait encore un trou plus loin dans le circuit qu'il n'avait pas vu, n'ayant pas testé,
  • La troisième fois, enfin, notre système a pu être remis en route.

La conclusion, c'est qu'on a gardé notre trou dans le sol, en attendant que les terrassiers, qui sont prévenus, viennent réparer le regard.Le passage de la mini-pelle a aussi pas mal explosé les allées bétonnées et il faudra qu'on prévoit de les remplacer plus tôt qu'on en avait l'intention...

Qui a dit que c'était simple, de faire faire l'étanchéité des fondations ?

14Oct/110

Installation de Debian GNU/Linux sur NAS LaCie

Depuis l'installation récente d'Azathoth's version 6, le vieux NAS LaCie qu'était Azathoth version 5 traînait sur une étagère, inutile. Il fallait bien que j'en fasse quelque chose. Cela dit, le système par défaut fourni avec le NAS n'offre pas beaucoup de possibilités de personnalisation, donc j'ai décidé d'essayer d'y installer une Debian GNU/Linux.

Le NAS avec son capot enlevé

Un "détail" important au sujet de ce vieux NAS - il utilise une carte mère Intel EM-7210, avec un processeur Intel 80219. Coup de chance, cette carte est plus ou moins supportée par Debian.

La carte mère EM-7210 du NAS

Cependant, il est nécessaire d'accéder au gestionnaire de démarrage RedBoot installé sur le système, ce qui n'est possible qu'en utilisant un câble série dont le connecteur se trouve directement sur la carte mère.

Connecteur IDC10 pour cable série sur la carte mère

Construction du câble

Le câble série qui permet d'accéder à la console du système doit être branché sur un connecteur IDC10. Il est sans doute possible d'utiliser un câble null-modem et l'un de ces connecteurs que l'on trouvait dans les "vieux" PC, mais je ne possède plus ni l'un, ni l'autre. Il a donc fallu que je construise mon câble. Ci-dessous, les connexions à effectuer:

DE9 femelle IDC10 femelle
2 5
3 3
5 9

Et si vous avez besoin d'une image pour vous repérer du côté IDC10 (j'ai eu besoin de chercher, et je ne suis probablement pas le seul dans ce cas):

Broches du connecteur IDC10 de la carte mère

Je n'avais pas utilisé de fer à souder depuis 16 ans, donc j'étais un peu inquiet à ce sujet. Au final, la soudure n'a posé aucun problème ; par contre, les fils que j'ai utilisés devaient être un tout petit peu trop gros pour mon connecteur IDC10 et je l'ai cassé... J'ai donc récupéré des connecteurs audio de façade dans une tour qui n'a pas d'audio du tout et les souder sur mes fils pour compenser. De plus, comme je n'ai pas de ports RS232 sur les machines qui sont ici, un adaptateur RS232<->USB a été utilisé.

Je devrais avoir honte

Paramétrage de Minicom

L'étape suivante était de se connecter à la console du NAS, en utilisant  Minicom (n'importe quel émulateur de terminal série conviendrait, mais j'ai l'habitude de celui-ci). Les paramètres requis sont:

  • taux de transfert: 115200 bps
  • bits de données: 8
  • parité: aucune
  • bits d'arrêt: 1

Il est également nécessaire de s'assurer que le contrôle du flux, aussi bien logiciel que matériel, est désactivé.

Démarrage de l'installeur Debian

Pour pouvoir lancer l'installeur Debian, il est nécessaire d'entrer dans le gestionnaire de démarrage du NAS, ce qui se fait en appuyant sur Ctrl+C dans la console avant que le script de démarrage ne commence à s'exécuter (il faut aller vite, il n'y a qu'une seconde entre l'apparition du message et le démarrage). Cela vous amène à une invite de commande.

Il vous faudra par ailleurs télécharger un noyau et une image disque pour l'architecture concernée. Les deux se trouvent sur les serveurs Debian (il vous faudra prendre zImage et initrd.gz).

Vous allez devoir charger ces images sur le NAS au moyen du câble série. Commencez par dire au gestionnaire de démarrage que vous voulez charger l'image disque:

load -v -r -b 0x1800000 -m ymodem ramdisk.gz

Le gestionnaire de démarrage se mettra alors à attendre que vous chargiez le fichier correspondant (le fichier initrd.gz mentionné ci-dessus) en utilisant YMODEM [lien en Anglais, il n'y a que 3 lignes sur la version française]. Dans Minicom, cela se fait en pressant Ctrl+A suivi de S, puis en sélectionnant "ymodem" dans la liste, et enfin en sélectionnant le fichier à envoyer.

(Attendez. Attendez encore. Attendez encore plus.)

Une fois que le transfert est terminé, vous devrez lancer celui du noyau. La commande, cette fois-ci, est:

load -v -r -b 0x1008000 -m ymodem zImage

et bien entendu le fichier à envoyer est zImage.

(Attendez. Attendez encore. Attendez encore plus.)

Quand les deux fichiers auront été envoyés, vous serez prêt pour le démarrage de l'installeur. La commande ci-dessous le lancera.

exec -c "console=ttyS0,115200 rw root=/dev/ram mem=256M@0xa0000000" -r 0x01800000
Fin de l'installation

J'ai sauté la description de l'installation de la Debian, parce que cela n'a rien de spécifique du tout.  Cependant, une fois l'installation terminée, il restera une opération à effectuer, sans quoi le système ne démarrera pas. Il vous faut éditer le script de démarrage.

Pour cela, entrez de nouveau dans le gestionnaire de démarrage (en appuyant sur Ctrl+C) lorsque l'installeur relancera la machine, puis saisissez la commande suivante:

fconfig boot_script_data

L'invite deviendra >> pour indiquer que vous éditez la configuration. Vous devrez saisir le script de démarrage ci-dessous:

fis load -b 0x01800000 ramdisk.gz
fis load -b 0x01008000 zImage
exec -c "console=ttyS0,115200 rw root=/dev/ram mem=256M@0xa0000000" -r 0x01800000

N'oubliez pas de quitter l'éditeur en ajoutant une ligne vide après le script. RedBoot vous demandera de confirmer les modifications, puis écrira la nouvelle configuration sur la mémoire flash.

Quelques notes
  1. J'avais initialement enlevé les disques durs pour éviter de les allumer et éteindre de manière répétée. Cependant, si vous faites cela puis essayer de les connecter alors que l'installeur Debian tourne déjà, ils ne seront pas détectés, et vous devrez redémarrer.
  2. Créer les partitions prend beaucoup de temps, et ne parlons même pas de la resynchronisation du RAID.
4Oct/110

Tonte de gazon plutôt épique

Ju et moi-même préférons que notre gazon soit un peu haut, donc la tonte n'a lieu qu'une fois par mois en temps normal. Ces derniers temps, cependant, nous avons tous deux été occupés à autre chose, et par ailleurs il faisait extrêmement chaud ; par conséquent, le gazon a été un petit peu abandonné depuis environ deux mois. Aujourd'hui, il y avait des nuages et une brise assez fraîche, donc je me suis dit que j'allais m'en occuper.

Gardez à l'esprit que:

  • la pelouse n'est pas si grande (400m², plus ou moins),
  • quelque chose comme entre un quart et un tiers de la pelouse ressemble à ceci:
    (je ne sais que blâmer; peut-être le sol toujours aussi charmant, ou les racines du cerisier, ou la vague de chaleur récente, ou encore une combinaison de tout ceci),
  • j'en avais déjà tondu environ un quart il y a deux semaines car j'avais besoin de paillis.

Et le résultat?

... Sachant que oui, j'ai piétiné les deux tas afin de les tasser un peu.

3Oct/110

Concert à Rennes – Samael et autres

Donc, parfois, il arrive qu'on sorte. On avait acheté des billets pour un concert de Samael à Rennes Samedi dernier - ce concert incluait également 5 autres groupes que nous ne connaissions pas. Au final, ça valait le coup.

Alors que l'Antipode [site inaccessible au moment de la rédaction de ce post] n'est pas si petit en comparaison à d'autres salles où nous sommes allés, comme par exemple l'Ubu (également à Rennes), ce n'est pas non plus gigantesque. Il y tiendrait peut-être 500 personnes, mais ce n'était pas plein à craquer non plus. Quoi qu'il en soit, notre expérience est que les concerts de métal dans ce type de salles sont toujours très appréciables.

Le premier groupe que nous avons vu était Six Reasons to Kill - du death metal, plutôt bon. Malheureusement pour eux, comme ils étaient les premier à jouer, le public était plutôt endormi, et la salle ne s'était pas encore remplie. Ils étaient suivis par Dead Shape Figure. Je ne suis pas très fan de thrash metal, et ils n'ont pas été une exception à cette "règle". Cela dit, en termes de spectacle, c'était plutôt bon. Le public comatait toujours un peu, et ils ont bien contribué à le réveiller.

Le groupe suivant était Noctem. La première chose que l'on a remarqué à leur sujet était... Hé bien, le cliché. Ils sont arrivés sur scène avec des armures de cuir (possiblement fausses), au point que Julie me sorte: "Regarde, des Klingon!" Honnêtement, à les voir, nous nous attendions à du black metal bien basique (et bien ennuyeux). Ce n'était heureusement pas le cas - ce qu'ils ont joué était un peu hybride, avec des rythmiques assez brutales parfois proches du thrash, parfois beaucoup plus similaires à du black metal, mais aussi des passages lents et lourds qui faisaient beaucoup penser à du death mélodique. Et ils exploitaient finalement plutôt bien le cliché sus-cité (le chanteur qui vomit du faux sang qui sent la fraise? Ouaip.)

Ensuite il y avait Keep of Kalessin, qui joue également une musique très similaire à du black metal (mais pas strictement cela). Bien qu'il s'agisse de l'un des groupes que j'ai le moins apprécié, cela restait du bon spectacle. Après cela, nous avons vu Melechesh. J'aurais quelques difficultés à essayer de classifier leur style - à mes oreilles, cela ressemblait à du death metal avec inspirations orientales très marquées et un peu de black metal mélangé dedans. Bah. Quoi qu'il en soit, j'ai vraiment apprécié. Qui plus est, nous avons ri un bon coup: l'un des guitaristes de Dead Shape Figure était dans le public avec une barquette de frittes et essayait de nourrir son collègue qui, lui, était sur scène (et bizarrement, ça ne fonctionnait pas très bien).

Enfin, le dernier groupe à monter sur scène était Samael. Cela fait des années que nous en écoutons, et nous les avions déjà vu en concert, comme première partie de Paradise Lost, il y a deux ans (et nous les avions préférés à ces derniers). Ils ont joué pas mal de pistes du dernier album, ainsi que quelques chansons plus vieilles. Ce n'est pas comme si j'avais quoi que ce soit contre l'un ou l'autre :) Bonne musique et, de manière peu surprenante, bon spectacle. La seule chose dont je pourrais éventuellement me plaindre est le fait qu'ils arrivaient assez tard et que, par conséquent, nous commencions à fatiguer.

Le jour suivant, j'avais des courbatures bien hargneuses au cou, et j'étais aphone, donc c'était un bon concert ;-> Et long, en plus: il y en avait grosso modo pour 5h. Mon seul regret est d'avoir oublié d'amener mon téléphone - pas de photos, du coup.

24Sep/110

Arbres ordonnés avec PostgreSQL, dernier post

Enfin, j'espère que ce soit le dernier ;-p

J'ai récemment produit quelques posts au sujet de mes tentatives pour faire fonctionner une structure d'arbre avec ordonnancement par l'utilisateur dans une base PostgreSQL. Alors que l'approche initiale fonctionne bien avec des mises à jour d'une seule ligne, essayer de ré-ordonner l'ensemble de l'arbre déclenche un comportement incohérent, même après ce correctif. À cause de cela, j'ai abandonné l'idée d'utiliser des déclencheurs et ai récrit le code de ré-ordonnancement sous la forme de procédures stockées.

Un peu de contexte, chaque cas étant différent:

  • l'arbre n'est modifié que rarement, donc une performance médiocre du ré-ordonnancement ne pose pas vraiment de problème;
  • en revanche, il est très vraisemblable que l'arbre soit modifié de manière concurrente, quand il l'est;
  • toutes les mises à jour sont effectuées au travers de procédures stockées qui verrouillent l'intégralité de l'arbre avant toute opération, puis appellent systématiquement la fonction de ré-ordonnancement quand elles ont fini;
  • chaque transaction ne procède au plus qu'à une seule mise à jour de l'arbre.

L'approche que j'ai choisie est de calculer les chemins de tous les noeuds et feuilles de l'arbre à chaque modification, en stockant le résultat dans une table temporaire, puis de réordonner la "vraie" table en se basant sur ces valeurs. Ce n'est pas la manière de faire la plus efficace, mais elle a l'avantage de fonctionner dans tous les cas qui me sont nécessaires.

J'ai commencé par supprimer la colonne contenant le chemin dans la table principale, ainsi que les divers déclencheurs qui y correspondaient.

ALTER TABLE objects
        DROP COLUMN object_ordering_path CASCADE;

DROP TRIGGER objects_ordering_bi ON objects;
DROP TRIGGER objects_ordering_bu ON objects;
DROP TRIGGER objects_ordering_au ON objects;

DROP FUNCTION objects_ordering_bi( );
DROP FUNCTION objects_ordering_bu( );
DROP FUNCTION objects_ordering_au( );

L'étape suivante était de créer une fonction capable de calculer le chemin de tous les objets connus. Cette fonction doit inspecter chaque objet par ordre de profondeur croissante, afin d'être sûre que le chemin vers l'objet parent a été calculé avant d'essayer de déterminer celui d'un objet fils. Elle utilise une table à deux champs (l'identificateur et le chemin d'un objet).

CREATE OR REPLACE FUNCTION objects_compute_paths( )
                RETURNS VOID
                STRICT VOLATILE
                SECURITY INVOKER
        AS $objects_compute_paths$
DECLARE
        o_id INT;
        o_parent INT;
        o_ordering INT;
        o_path TEXT;
BEGIN
        FOR o_id , o_parent , o_ordering IN
                SELECT o.object_id , o.object_id_parent , o.object_ordering
                        FROM objects o
                                INNER JOIN objects_tree ot
                                        ON ot.object_id_child = o.object_id
                        GROUP BY o.object_id , o.object_id_parent , o.object_ordering
                        ORDER BY MAX( ot.ot_depth )
        LOOP
                IF o_parent IS NULL THEN
                        o_path := '';
                ELSE
                        SELECT INTO o_path
                                        object_ordering_path || '/'
                            FROM objects_ordering
                            WHERE object_id = o_parent;
                END IF;
                o_path := o_path || to_char( o_ordering , '000000000000' );
                INSERT INTO objects_ordering VALUES ( o_id , o_path );
        END LOOP;
END;
$objects_compute_paths$ LANGUAGE plpgsql;

Enfin, la fonction de ré-ordonnancement a été remplacée; la nouvelle version crée la table temporaire, appelle la fonction décrite ci-dessus afin de remplir cette table, puis l'utilise afin de réordonner les entrées de la table principale.

CREATE OR REPLACE FUNCTION objects_reorder( )
                RETURNS VOID
                STRICT VOLATILE
                SECURITY INVOKER
        AS $objects_reorder$
BEGIN
        -- Create and fill temporary table
        CREATE TEMPORARY TABLE objects_ordering (
                object_id               INT NOT NULL PRIMARY KEY ,
                object_ordering_path    TEXT NOT NULL
        ) ON COMMIT DROP;
        PERFORM objects_compute_paths( );

        -- Move all rows out of the way
        UPDATE objects SET object_ordering = object_ordering + (
                        SELECT 1 + 2 * MAX( object_ordering ) FROM objects );

        -- Re-order objects
        UPDATE objects o1 SET object_ordering = 2 * o2.rn
                FROM ( SELECT object_id , row_number() OVER(
                                ORDER BY object_ordering_path ) AS rn
                        FROM objects_ordering ) o2
                WHERE o1.object_id = o2.object_id;
END;
$objects_reorder$ LANGUAGE plpgsql;

Note: il n'y a plus de colonne object_ordering_path dans la table principale, mais comme les objets sont toujours triés dans un ordre qui correspond à leur position dans l'arbre, il suffit de trier en utilisant la colonne entière object_ordering dans les requêtes.

18Sep/110

Champ de mines v2.0

Évidemment, comme c'est souvent le cas avec les artisans, nos amis les terrassiers ne sont pas venus chez nous à la date prévue. Comme nous somme les rois de la loi de Murphy, nous avons subi un énorme orage le 22 août (70 mm d'eau en quelques heures quand même), avant leur passage. En conséquence :

  • Nous avons encore passé une matinée enchantée à éponger dans le sous-sol et à virer des seaux d'eau dans la rue. Opération concours de tee-shirts mouillés garantie ! Je crois que sans l'aide du voisin d'en face, nous aurions été submergés !
  • J'ai plein de plantes qui se sont retrouvées dans des seaux plein d'eau et impossibles à éponger. Elles n'ont pas aimé :(

Deux jours après, les terrassiers sont arrivés. Mais comme leur secrétaire ne m'avait pas indiqué une zone à dégager assez large, des plantes ont subi un arrachage sauvage ou se sont fait enterrer sous la terre sortie de la tranchée.

Au bout d'un jour de boulot, j'ai :

  • Un gros tas de terre dans l'allée,

  • Un début de tranchée sous le balcon,

  • Une tranchée le long de la cuve de récupération d'eau et le tas de terre correspondant...

Le lendemain, mes gentils terrassiers mettent en place un drain et une couche d'étanchéité le long de la façade. Évidemment, c'est la cata pour traverser le muret et rejoindre le puisard des eaux usées : leur perforatrice n'est pas assez longue, ne passe pas à cause de l'angle du mur, etc... Ils en ont bavé, les pauvres !

Comme les terrassiers subissent notre influence Murphy, il pleut. La terre colle et c'est l'horreur de creuser à la main. Ils louent donc une mini-pelle plus petite que les leurs et qui puisse rentrer dans le jardin, surtout pour reboucher. D'ailleurs, le rebouchage, ça donne ça :

  • La mini-pelle à l'attaque dans mon beau massif. Sniff, c'était joli, avant, hein ?

  • La tranchée le long de la cuve de récupération d'eau en cours de rebouchage,

Pour faire un peu plus Murphy, l'un des terrassiers s'est cassé la figure sur le tuyau qui va de la cuve à la pompe qui alimente le circuit des robinets. Ils l'ont remplacé, mais impossible d'extraire de l'eau de la cuve : la pompe ne se réamorce pas et si on essaie avec l'eau de la ville, l'eau coule sous la dalle de la salle serveurs. Le tuyau doit être déboîté dans la contre-cloison de la salle serveurs... On a appelé le plombier mais l'ouvrier qui est venu n'a pas osé aller ouvrir la cloison sans savoir où passent les tuyaux exactement. On attend que celui qui a fait l'installation soit disponible pour réparer la chose...

11Sep/110

Déclencher un comportement erratique avec … des déclencheurs

Hier, je suis tombé sur quelque chose de très étrange sous PostgreSQL (9.1rc).

Un peu de contexte, pour commencer: la base de donnée sur laquelle le problème s'est produit contient des tables qui représentent un arbre dont les éléments sont ordonnés, quelque chose de très similaire à ce que ce post de depesz décrit; de plus, la plupart des opérations sur la structure de l'arbre sont effectuées via des procédures, lesquelles effectuent en général un réordonnancement lorsqu'elles ont fini.

La table principale utilisait un déclencheur qui ressemblait à peu près à ceci:

CREATE OR REPLACE FUNCTION objects_bu( )
                RETURNS TRIGGER
                SECURITY DEFINER
        AS $objects_bu$
BEGIN
        -- D'autres vérifications ici

        IF OLD.object_ordering = NEW.object_ordering
            AND OLD.object_id_parent IS NOT DISTINCT FROM NEW.object_id_parent
        THEN
                RETURN NEW;
        END IF;

        IF NEW.object_id_parent IS NULL
        THEN
                NEW.object_ordering_path := to_char( NEW.object_ordering , '000000000000' );
        ELSE
                SELECT object_ordering_path || '/' || to_char( NEW.object_ordering , '000000000000')
                            INTO NEW.object_ordering_path
                        FROM objects
                        WHERE object_id = NEW.object_id_parent;
        END IF;

        UPDATE objects
                SET object_ordering_path = regexp_replace(
                        object_ordering_path , '^' || OLD.object_ordering_path, NEW.object_ordering_path )
                WHERE object_id IN (
                                SELECT object_id_child FROM objects_tree
                                        WHERE object_id_parent = NEW.object_id AND ot_depth > 0
                        );

        RETURN NEW;
END;
$objects_bu$ LANGUAGE plpgsql;

CREATE TRIGGER objects_bu
        BEFORE UPDATE ON objects FOR EACH ROW
                EXECUTE PROCEDURE objects_bu( );

Dans la plupart des cas, ce déclencheur fonctionne: il fait ce qu'il est sensé faire, et la colonne contenant le chemin d'un noeud est mise à jour correctement. Cependant, si une opération qui déclenche l'exécution de ce code est suivie par une autre mise à jour sur la même ligne dans la même transaction (par exemple un appel à la fonction de réordonnancement), tout part en sucette.

Par exemple, si la table contient 3 entrées, dont deux qui sont des enfants de la dernière, et que l'on essaie d'inverser ces deux enfants en mettant à jour l'index d'ordonnancement du premier pour qu'il soit égal à celui du second plus 1, puis que l'on essaie de réordonner les objets, il apparaîtra que la mise à jour de l'index d'ordonnancement a échoué. Cela ne se produira pas systématiquement - parfois, cela fonctionnera, mais dans la plupart des cas, non. En y regardant de plus près, on se rend compte que:

  • l'inversion elle-même est toujours effectuée correctement,
  • la première mise à jour du code de réordonnancement va parfois affecter toutes les lignes (ce qu'elle est supposée faire), mais la plupart du temps elle ne changera que les deux lignes n'ayant pas été modifiées par le déclencheur,
  • parce que la première mise à jour a échoué silencieusement et que l'objet mis à jour a gardé son index d'ordonnancement modifié par l'inversion, la seconde requête de réordonnancement fera ce qu'elle est supposée faire et triera les objets en se basant là-dessus. L'objet que l'on souhaitait modifier restera donc à la position dans laquelle il était.

Tout ce bazar semble être causé par la dernière mise à jour faite par le déclencheur. D'une manière ou d'une autre, cette opération rend PostgreSQL un peu perdu. Pour corriger cela, il suffit de déplacer cette mise à jour vers un second déclencheur, exécuté après la mise à jour de la ligne elle-même. Par ailleurs, il est plus logique de procéder ainsi.

Ce dont je me plains ici, ce n'est pas le fait que faire des mises à jour sur une table depuis le déclencheur "avant mise à jour" sur une table ne fonctionne pas, c'est un peu bébête de toute façon. En revanche, il me semble que PostgreSQL devrait se comporter de manière systématique par rapport à cela - soit faire que cela fonctionne, soit causer une erreur, et non se comporter aléatoirement et en silence.

Je n'ai pas encore rapporté ce problème, car il semble très similaire au bogue #6123 et je sais qu'il y a du travail en cours sur ce point. Quoi qu'il en soit, j'ai trouvé que le comportement qui en résulte est suffisamment étrange pour mériter d'être mentionné.