<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Administration on Carnet de découvertes</title><link>https://fljd.in/tags/administration/</link><description>Recent content in Administration on Carnet de découvertes</description><generator>Hugo -- gohugo.io</generator><language>fr</language><managingEditor>Florent Jardin</managingEditor><lastBuildDate>Mon, 14 Oct 2024 10:30:00 +0200</lastBuildDate><atom:link href="https://fljd.in/tags/administration/index.xml" rel="self" type="application/rss+xml"/><item><title>Poissons et coquillages</title><link>https://fljd.in/2024/10/14/poissons-et-coquillages/</link><pubDate>Mon, 14 Oct 2024 10:30:00 +0200</pubDate><author>Florent Jardin</author><guid>https://fljd.in/2024/10/14/poissons-et-coquillages/</guid><description>&lt;p&gt;En tant que pur produit académique des années 2010, mon langage de script de prédilection a
toujours été le Bash (&lt;em&gt;Bourne Again Shell&lt;/em&gt;). Non sans ignorer qu&amp;rsquo;il ait pu en exister d&amp;rsquo;autres, je
ne me suis jamais vraiment tourné vers d&amp;rsquo;autres shells pour automatiser les tâches du quotidien
dans mon métier de DBA.&lt;/p&gt;
&lt;p&gt;Et pour cause, j&amp;rsquo;ai administré des centaines de serveurs de distributions très variées et il
n&amp;rsquo;était pas bien vu d&amp;rsquo;installer des dépendances systèmes lourdes pour enrichir des scripts Python
ou Perl. Nous apprenions donc à écrire des scripts portables et universels, compatibles partout
où nous déposions nos valises.&lt;/p&gt;
&lt;p&gt;Me suis-je enfermé dans un dogme conservateur, en m&amp;rsquo;interdisant &lt;em&gt;de facto&lt;/em&gt; à me tourner vers des
shells modernes et bien plus aisés à appréhender ?&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="beurk-le-bash"&gt;Beurk, le Bash&lt;/h2&gt;
&lt;p&gt;Ne faisons pas de détour, le Bash c&amp;rsquo;est moche. Puissant, pratique, universel, mais moche.&lt;/p&gt;
&lt;p&gt;Vous pourriez penser que c&amp;rsquo;est un avis parfaitement subjectif, et qu&amp;rsquo;il ne faut pas juger un livre
à sa couverture&amp;hellip; mais avouez-le, une partie de vous pense sensiblement la même chose que moi.
Combien d&amp;rsquo;heures ai-je pu perdre durant ma courbe d&amp;rsquo;apprentissage du langage, à cause d&amp;rsquo;un grand
nombre de subtilités et de pièges de syntaxes ?&lt;/p&gt;
&lt;p&gt;Entre les assignations sans espace autour du signe &lt;code&gt;=&lt;/code&gt;, les accolades &lt;code&gt;{}&lt;/code&gt; pour les variables
contenant un signe &lt;code&gt;_&lt;/code&gt;, les backticks &lt;code&gt;` `&lt;/code&gt; pour les commandes subshells, ou encore les doubles
crochets &lt;code&gt;[[ ... ]]&lt;/code&gt; pour les comparaisons non-POSIX, il y a de quoi s&amp;rsquo;arracher les cheveux.&lt;/p&gt;
&lt;p&gt;Certain·e·s y verront une forme d&amp;rsquo;esthétisme ou de tradition d&amp;rsquo;une culture informatique, mais
personnellement je trouve que c&amp;rsquo;est un langage qui vieillit mal, très mal. Vous ne vous offusqueriez
pas non plus si je devais vous témoigner mon désamour le Perl, n&amp;rsquo;est-ce pas ?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# une comparaison de deux variables&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# une addition et une affectation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;z&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt; &lt;span class="nv"&gt;$x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;$y&lt;/span&gt; &lt;span class="k"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# une boucle for&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; i in &lt;span class="k"&gt;$(&lt;/span&gt;seq &lt;span class="m"&gt;1&lt;/span&gt; 10&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# un switch case&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nv"&gt;$1&lt;/span&gt; in
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 1&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;un&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;deux&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; *&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;autre&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;esac&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# une chaîne en minuscule&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,,&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# une gestion des erreurs de commandes chaînées&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;some_command &lt;span class="p"&gt;|&lt;/span&gt; another_command
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PIPESTATUS&lt;/span&gt;&lt;span class="p"&gt;[0]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; -ne &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PIPESTATUS&lt;/span&gt;&lt;span class="p"&gt;[1]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; -ne &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Une des commandes a échoué&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le &lt;a href="https://en.wikipedia.org/wiki/Bash_%28Unix_shell%29" target="_blank" rel="noopener"&gt;Bash&lt;/a&gt; est apparu l&amp;rsquo;année de ma naissance, en 1989. Il a été conçu en opposition au &lt;a href="https://en.wikipedia.org/wiki/Bourne_shell" target="_blank" rel="noopener"&gt;Bourne Shell&lt;/a&gt;
(&lt;code&gt;sh&lt;/code&gt;) pour apporter des fonctionnalités de programmation plus avancées. Il est devenu le shell par
défaut de la plupart des distributions Linux, et est devenu un standard pour tous les scripts à travers
le monde entier.&lt;/p&gt;
&lt;p&gt;Fort de ses années de succès, et de son emprise sur le monde Unix, le Bash s&amp;rsquo;impose parfois comme la première
porte vers la programmation pour des administrateurices système ou les étudiant·e·s en informatique. Et il
faut accepter que la qualité de ses premiers scripts n&amp;rsquo;est pas toujours au rendez-vous. Souvenez-vous des
longues heures passées sur StackOverflow à trouver la syntaxe la plus lisible ou la plus efficace, car les
réponses se révélaient aussi variées que touffues !&lt;/p&gt;
&lt;p&gt;Et c&amp;rsquo;est l&amp;rsquo;un des plus gros problèmes que je trouve au Bash : ce langage de programmation est exigeant,
excentrique, voire &lt;a href="https://fr.wikipedia.org/wiki/Idiosyncrasie" target="_blank" rel="noopener"&gt;idiosyncratique&lt;/a&gt;, et qui ne pardonne pas les erreurs. Un des &lt;a href="https://dev.to/taikedz/your-bash-scripts-are-rubbish-use-another-language-5dh7" target="_blank" rel="noopener"&gt;paradoxes&lt;/a&gt; que j&amp;rsquo;associe
au Bash est qu&amp;rsquo;il peut ne pas être considéré comme un véritable langage de programmation, réduisant ainsi
la volonté et l&amp;rsquo;effort de l&amp;rsquo;apprendre en profondeur par une large communauté de professionnel·le·s.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://dev.to/taikedz/your-bash-scripts-are-rubbish-use-another-language-5dh7" target="_blank" rel="noopener"&gt;Et si vos scripts Bash sont nuls, utilisez un autre langage.&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="lami-fish-friendly-interactive-shell"&gt;L&amp;rsquo;ami Fish, Friendly Interactive Shell&lt;/h2&gt;
&lt;p&gt;Je suis tombé sur &lt;a href="https://fishshell.com/" target="_blank" rel="noopener"&gt;Fish&lt;/a&gt; par hasard, alors que je questionnais mon usage quotidien sur &lt;a href="https://fr.wikipedia.org/wiki/Z_Shell" target="_blank" rel="noopener"&gt;ZSH&lt;/a&gt;. Sur
mon poste personnel, j&amp;rsquo;ai étendu mon expérience passée sur le Bash avec une configuration ZSH enrichie de
&lt;a href="https://ohmyz.sh/" target="_blank" rel="noopener"&gt;Oh My Zsh&lt;/a&gt;. C&amp;rsquo;était un peu le jour et la nuit dans l&amp;rsquo;interaction avec mes terminaux : la navigation,
les suggestions, les thèmes, les plugins, tout est plus fluide et plus agréable. Sa compatibilité avec
Bash m&amp;rsquo;avait alors évité de tout réapprendre.&lt;/p&gt;
&lt;p&gt;Fish répond au besoin de modernité et de simplicité que je cherche sans vraiment le savoir. Contrairement
à ZSH, un grand nombre de fonctionnalités sont disponibles sans aucune configuration préalable ou plugin
à activer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Assistance à la commande&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Recopie la commande incomplète et en erreur sans besoin de naviguer dans l&amp;rsquo;historique&lt;/li&gt;
&lt;li&gt;Accès à l&amp;rsquo;historique partiel avec les touches directionnelles dès la première frappe au clavier&lt;/li&gt;
&lt;li&gt;Proposition de chemins avec la complétion de la touche Tab, suivie des touches directionnelles&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://fljd.in/img/fr/2024-20-14-fish-01.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Auto-suggestions des chemins et des options d&amp;rsquo;une grande majorité des outils&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mémoire des précédents répertoires parcourus&lt;/li&gt;
&lt;li&gt;Support très complet des commandes Git, notamment le &lt;code&gt;git rebase -i&lt;/code&gt; ❤️&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://fljd.in/img/fr/2024-20-14-fish-02.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Coloration plus poussée des commandes&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;une couleur différente par mot selon la syntaxe employée (variables, chemin, options)&lt;/li&gt;
&lt;li&gt;un rouge prononcé pour les commandes inconnues&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="un-langage-qui-vous-veut-du-bien"&gt;Un langage qui vous veut du bien&lt;/h2&gt;
&lt;p&gt;Pour revenir au sujet principal de cet article, Fish révolutionne notre rapport avec la rédaction
de scripts. Fini les pièges et la syntaxe qui nous ont tenu en otage durant des années. Les choses
deviennent bien plus simples et lisibles.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fish" data-lang="fish"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# une comparaison de deux variables
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# une addition et une affectation
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;z&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;math&lt;/span&gt; &lt;span class="nv"&gt;$x&lt;/span&gt; + &lt;span class="nv"&gt;$y&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# une boucle for
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;i&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;seq&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# un switch case
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nv"&gt;$argv&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;un&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;deux&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; *&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;autre&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# une chaîne en minuscule
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;a&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;string &lt;/span&gt;lower &lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# une gestion des erreurs de commandes chaînées
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;some_command&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nf"&gt;another_command&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;contains &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="nv"&gt;$pipestatus&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Une des commandes a échoué&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;L&amp;rsquo;investissement initial pour perdre ses habitudes acquises avec Bash est modéré, notamment
le &lt;a href="https://fishshell.com/docs/current/language.html#variables-export" target="_blank" rel="noopener"&gt;remplacement&lt;/a&gt; de &lt;code&gt;export&lt;/code&gt; ou de l&amp;rsquo;assignation &lt;code&gt;=&lt;/code&gt; par la méthode &lt;code&gt;set -x&lt;/code&gt; ou &lt;code&gt;set -u&lt;/code&gt;
respectivement. Mais l&amp;rsquo;enrichissement du langage par quelques nouveaux mots-clés permet
d&amp;rsquo;alléger le script avec des syntaxes claires et lisibles (&lt;code&gt;contains&lt;/code&gt;, &lt;code&gt;math&lt;/code&gt; ou &lt;code&gt;string&lt;/code&gt;
présents dans l&amp;rsquo;exemple ci-dessus).&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;ajout de plugins communautaire n&amp;rsquo;est pas en reste, si comme moi, vous en consommiez avec
Oh-My-ZSH. L&amp;rsquo;outil &lt;a href="https://github.com/jorgebucaran/fisher" target="_blank" rel="noopener"&gt;Fisher&lt;/a&gt; s&amp;rsquo;installe dans votre &lt;code&gt;~/.config/fish&lt;/code&gt; et donne accès à un
gestionnaire complet et sobre. Pour ma part, j&amp;rsquo;ai pu rapidement combler mon besoin de charger
les variables d&amp;rsquo;environnement fournies par l&amp;rsquo;agent SSH (&lt;code&gt;ssh-agent&lt;/code&gt;) lors du démarrage de ma
session.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; fisher install danhper/fish-ssh-agent
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;fisher install version 4.4.5
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Fetching https://api.github.com/repos/danhper/fish-ssh-agent/tarball/HEAD
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Installing danhper/fish-ssh-agent
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; /home/florent/.config/fish/functions/__ssh_agent_is_started.fish
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; /home/florent/.config/fish/functions/__ssh_agent_start.fish
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; /home/florent/.config/fish/conf.d/fish-ssh-agent.fish
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Updated 1 plugin/s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pas de mystère, ni d&amp;rsquo;arcanes magiques, votre environnement est enrichi de nouvelles fonctions
et de scripts de démarrage, développés en Fish.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="osons-la-modernité"&gt;Osons la modernité&lt;/h2&gt;
&lt;p&gt;La documentation du projet est très bien fournie. En plus de la page &lt;a href="https://en.wikipedia.org/wiki/Bash_%28Unix_shell%29" target="_blank" rel="noopener"&gt;Tutorial&lt;/a&gt;, vous trouverez
une page dédiée aux &lt;a href="https://fishshell.com/docs/current/fish_for_bash_users.html" target="_blank" rel="noopener"&gt;utilisateurs de Bash&lt;/a&gt; pour vous aider à réussir votre transition. Pour ma
part, j&amp;rsquo;y retrouve des syntaxes bien plus proches du Python, et c&amp;rsquo;est un vrai plaisir.&lt;/p&gt;
&lt;p&gt;Le projet est vivant, avec un rythme de versions régulier. Je me suis surpris moi-même à ne pas en
avoir entendu parler par d&amp;rsquo;autres collègues ou auprès des auteurices de blogs que je suis. Au cours des
dernières années, la communauté francophone a essaimé quelques bons articles qui complètent le
mien.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://lkdjiin.github.io/blog/2016/12/13/changer-de-shell-de-bash-a-fish/" target="_blank" rel="noopener"&gt;Changer de shell, de Bash à Fish&lt;/a&gt; (2016)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ubunlog.com/fr/ligne-de-commande-fish-smart/" target="_blank" rel="noopener"&gt;Fish, une ligne de commande intelligente et simple d&amp;rsquo;utilisation&lt;/a&gt; (2017)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://toptips.fr/comment-passer-de-bash-a-fish-shell-sous-linux/" target="_blank" rel="noopener"&gt;Comment passer de Bash à Fish Shell sous Linux&lt;/a&gt; (2021)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.otso.fr/2024-01-17-passer-zsh-fish-shell" target="_blank" rel="noopener"&gt;Migration de shell de zsh à fish&lt;/a&gt; (2024)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ces temps-ci, je fais évoluer mon expérience d&amp;rsquo;utilisateur de Linux avec une transition vers Wayland
et SwayWM (j&amp;rsquo;y reviendrais dans un autre article). Je ne le cache pas, la tentation de remplacer mes
scripts Bash qui gèrent mes thèmes ou les composants &lt;em&gt;swaybar&lt;/em&gt;, est forte, très forte.&lt;/p&gt;
&lt;p&gt;De quoi occuper mes prochaines longues soirées d&amp;rsquo;hiver.&lt;/p&gt;</description></item><item><title>Les corruptions silencieuses</title><link>https://fljd.in/2021/09/28/les-corruptions-silencieuses/</link><pubDate>Tue, 28 Sep 2021 00:00:00 +0000</pubDate><author>Florent Jardin</author><guid>https://fljd.in/2021/09/28/les-corruptions-silencieuses/</guid><description>&lt;p&gt;Parmi les drames universellement reconnus, les corruptions de données sont des
événements mécaniques ou logiques qui surviennent à des moments imprévisibles.
Tantôt il s&amp;rsquo;agira de l&amp;rsquo;âge avancé des secteurs disques, tantôt il s&amp;rsquo;agira d&amp;rsquo;une
extinction inopinée d&amp;rsquo;un composant électrique ou d&amp;rsquo;une perte de paquet dans les
protocoles de cache.&lt;/p&gt;
&lt;p&gt;Bien que peu de personnes peuvent se vanter d&amp;rsquo;en avoir observé au cours de leur
carrière, les corruptions sont particulièrement dévastatrices lorsqu&amp;rsquo;elles se
sont propagées sur les supports de sauvegardes et détectées bien des jours, voire
des semaines après l&amp;rsquo;incident. Les moteurs de bases de données sont très résilients
face à ces destructions de données, en proposant des mécanismes de journalisation
adaptés. Malgré cela, des précautions sont de mises.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="la-surcorruption"&gt;La surcorruption&lt;/h2&gt;
&lt;p&gt;La détection d&amp;rsquo;une corruption n&amp;rsquo;est jamais très claire, ni très précise. Dans
la majorité des situations, il s&amp;rsquo;agit d&amp;rsquo;un utilisateur qui reporte une erreur
lors de la lecture d&amp;rsquo;une ligne dans la base de données et ne parvient pas à
finaliser son traitement. Avec PostgreSQL, la première source exploitable reste
le journal de l&amp;rsquo;instance, dans lequel l&amp;rsquo;anomalie sera flagrante avec des
messages d&amp;rsquo;erreur de type &lt;code&gt;could not read block xxx in file&lt;/code&gt; ou &lt;code&gt;invalid page (header) in block xxx of relation&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Les corruptions peuvent aussi bien &lt;a href="https://www.enterprisedb.com/blog/how-to-fix-postgresql-index-corruption-deal-repair-rebuild" target="_blank" rel="noopener"&gt;toucher les index&lt;/a&gt; que les tables. Les
premières peuvent provoquer des ralentissements pour certaines requêtes, voire
remonter des données erronées, alors que les secondes sont bien plus
problématiques avec la destruction simple d&amp;rsquo;une partie des données utilisateurs.&lt;/p&gt;
&lt;p&gt;Après un constat désarmant de corruption, la sueur perle probablement sur le
front des équipes techniques, si tant est que l&amp;rsquo;information leur ait été remontée
dans un court laps de temps. La corruption est-elle réversible avec une copie
quelconque de la table dans une instance secondaire, ou dans une sauvegarde ?
La corruption est-elle présente depuis longtemps ? Le système présente-t-il des
messages alarmants sur l&amp;rsquo;état des écritures sur le volume ?&lt;/p&gt;
&lt;p&gt;Le meilleur des conseils que trop peu se permettent, serait de se prévenir d&amp;rsquo;une
surcorruption en &lt;u&gt;arrêtant les écritures le plus tôt possible&lt;/u&gt;.
Le temps d&amp;rsquo;identifier l&amp;rsquo;origine de la corruption, en mobilisant plusieurs équipes
pour éplucher les logs des hyperviseurs, des baies de stockages ou du fournisseur
Cloud, il se peut (statistiquement) qu&amp;rsquo;une autre corruption porte atteinte à la
vie des données saines du système.&lt;/p&gt;
&lt;p&gt;Il est aussi &lt;a href="https://wiki.postgresql.org/wiki/Corruption" target="_blank" rel="noopener"&gt;indispensable&lt;/a&gt; de procéder à une copie bas niveau du répertoire
de données de l&amp;rsquo;instance et de travailler exclusivement sur un exemplaire de ladite
copie, &lt;em&gt;a minima&lt;/em&gt; sur des disques différents, au mieux, sur un serveur secondaire.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="récupérer-ce-qui-peut-lêtre"&gt;Récupérer ce qui peut l&amp;rsquo;être&lt;/h2&gt;
&lt;p&gt;Pour illustrer la complexité que peut devenir la récupération de données saines
sur une base corrompue, j&amp;rsquo;ai malmené une instance jetable en version 13 dont
les données de la table &lt;code&gt;pgbench_accounts&lt;/code&gt; ont été partiellement détruites avec
l&amp;rsquo;outil &lt;code&gt;fallocate&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Élargir la recherche des corruptions est possible en forçant la lecture intégrale
des données contenues dans les tables. Avec PostgreSQL, le plus simple consiste
à exporter les bases avec &lt;code&gt;pg_dump&lt;/code&gt;, de surveiller la sortie d&amp;rsquo;erreurs… et
d&amp;rsquo;espérer qu&amp;rsquo;aucune donnée ne soit perdue.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ pg_dump demo 1&amp;gt; /dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Dumping the contents of table &amp;#34;pgbench_accounts&amp;#34; failed: PQgetResult() failed.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Error message from server:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ERROR: invalid page in block 20 of relation base/16997/17010
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;The command was:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; COPY public.pgbench_accounts (aid, bid, abalance, filler) TO stdout;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;L&amp;rsquo;incertitude s&amp;rsquo;installe à l&amp;rsquo;issue de la commande. L&amp;rsquo;une des pages de la table
&lt;code&gt;pgbench_accounts&lt;/code&gt; présente une malformation, rendant impossible la lecture de
8 ko de données. La récupération des données encore exploitable peut être possible,
notamment à travers l&amp;rsquo;index de clé primaire et une recherche par dichotomie sur
les valeurs disponibles dans le fichier corrompu.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;enable_seqscan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;abalance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;filler&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgbench_accounts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1221&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;abalance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;filler&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgbench_accounts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1281&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Si tant est que cette table soit concernée par une contrainte de clé étrangère,
l&amp;rsquo;ensemble du modèle devient alors partiellement incohérent et les données
orphelines sont destinées à être détruites explicitement pour valider la
contrainte.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgbench_history&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FOREIGN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;REFERENCES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgbench_accounts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- ERROR: insert or update on table &amp;#34;pgbench_history&amp;#34; violates
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- foreign key constraint &amp;#34;pgbench_history_aid_fkey&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- DETAIL: Key (aid)=(1276) is not present in table &amp;#34;pgbench_accounts&amp;#34;.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;DELETE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgbench_history&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;EXISTS&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgbench_accounts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgbench_history&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FOREIGN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;REFERENCES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgbench_accounts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- ALTER TABLE
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="se-protéger-des-corruptions"&gt;Se protéger des corruptions&lt;/h2&gt;
&lt;p&gt;Comme précisé en introduction, la plupart des moteurs de bases de données sont
&lt;a href="https://www.postgresql.org/docs/current/wal-reliability.html" target="_blank" rel="noopener"&gt;résilients&lt;/a&gt; et articulent leurs écritures autours de la journalisation.
Chaque modification est assurée d&amp;rsquo;être écrite sur un stockage non volatile à la
réception du &lt;code&gt;COMMIT&lt;/code&gt; de la transaction. Cela implique une synchronisation des
blocs en mémoire vers le stockage du système comme je l&amp;rsquo;avais illustré dans un
&lt;a href="https://fljd.in/2021/01/19/la-breve-histoire-du-fichier-backup_label/#il-%c3%a9tait-une-fois-la-journalisation"&gt;précédent article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Des précautions sont bien sûr nécessaires, et Craig Ringer avait compilé dans
un &lt;a href="http://blog.ringerc.id.au/2012/10/avoiding-postgresql-database-corruption.html" target="_blank" rel="noopener"&gt;article&lt;/a&gt; en 2012, des recommandations toujours pertinentes pour réduire
le risque de corruptions.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mettre à jour&lt;/strong&gt; votre instance à la dernière version mineure disponible ;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ne pas désactiver&lt;/strong&gt; &lt;code&gt;fsync&lt;/code&gt; et privilégier les paramètres &lt;code&gt;synchronous_commit&lt;/code&gt;
et &lt;code&gt;commit_delay&lt;/code&gt; ;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ne pas tuer&lt;/strong&gt; les processus PostgreSQL et utiliser les fonctions système
&lt;code&gt;pg_cancel_backend&lt;/code&gt; et &lt;code&gt;pg_terminate_backend&lt;/code&gt; pour arrêter une requête longue ;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ne pas supprimer&lt;/strong&gt; le contenu du répertoire de données, à l&amp;rsquo;exception des
traces d&amp;rsquo;activité au format texte ;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ne pas modifier&lt;/strong&gt; le catalogue système (&lt;code&gt;pg_catalog.*&lt;/code&gt;) ;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Conserver les sauvegardes sur de longues périodes&lt;/strong&gt; à raison d&amp;rsquo;une par semaine,
une par mois, voire une par année pour reconstruire les données corrompues
(avec un peu de chance tout de même) ;&lt;/li&gt;
&lt;li&gt;Mettre en place de la réplication et des sauvegardes physiques, avec un politique
de rétention des journaux de transactions sur de longues périodes ;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Éviter les disques en RAID5&lt;/strong&gt; et privilégier le RAID10 pour les disques mécaniques ;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ne pas utiliser&lt;/strong&gt; les systèmes de fichiers exotiques comme (ZFS), BTRFS ou FAT32 ;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ne pas stocker les fichiers&lt;/strong&gt; de l&amp;rsquo;instance sur une clé USB ou un montage
réseau.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="message"&gt;Depuis quelques années, le projet ZFS s&amp;rsquo;est ouvert à la communauté avec OpenZFS
en 2013. En plus des fonctionnalités de &lt;em&gt;Copy on Write&lt;/em&gt;, de &lt;em&gt;Snapshot&lt;/em&gt; ou de
&lt;em&gt;Mirroring&lt;/em&gt;, ce système de fichiers couvre également l&amp;rsquo;aspect des sommes de
contrôle, le rendant bien plus résilient qu&amp;rsquo;à l&amp;rsquo;époque où Craig Ringer écrivait
ses recommandations.&lt;/div&gt;
&lt;p&gt;Malgré toutes ces protections, une autre forme de corruption peut encore survenir.
Une donnée erronée peut être retournée au client sans message d&amp;rsquo;erreur. On parle
alors de &lt;a href="https://wiki.postgresql.org/wiki/Corruption_Detection_and_Containment" target="_blank" rel="noopener"&gt;corruption silencieuse&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Prenons l&amp;rsquo;exemple d&amp;rsquo;une donnée dans la table &lt;code&gt;pgbench_branches&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgbench_branches&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;filler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;florent&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_relation_filepath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pgbench_branches&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- pg_relation_filepath
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- ----------------------
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- base/17100/17179
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;À l&amp;rsquo;aide d&amp;rsquo;un éditeur hexadécimal, je suis libre d&amp;rsquo;émuler une corruption
silencieuse en ciblant précisément une donnée de type &lt;code&gt;text&lt;/code&gt;. Ici, je remplace
quelques octets pour transformer &lt;code&gt;florent&lt;/code&gt; en &lt;code&gt;fl4r%nt&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hexedit base/17100/17179
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;00001F64 00 00 00 00 00 00 00 00 00 00 00 00 ............
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;00001F70 02 00 03 80 02 29 18 00 01 00 00 00 .....)......
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;00001F7C 00 00 00 00 B3 66 6C 34 72 25 6E 74 .....fl4r%nt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;00001F88 20 20 20 20 20 20 20 20 20 20 20 20
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;00001F94 20 20 20 20 20 20 20 20 20 20 20 20
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;00001FA0 20 20 20 20 20 20 20 20 20 20 20 20
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;00001FAC 20 20 20 20 20 20 20 20 20 20 20 20
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pour cette démonstration, ce changement a été fait à froid, c&amp;rsquo;est-à-dire instance
arrêtée et fichiers fermés. Une copie du bloc pouvant encore être dans le cache
mémoire de PostgreSQL, le prochain &lt;code&gt;CHECKPOINT&lt;/code&gt; ou synchronisation préventive
aurait réécrit le bloc sain à l&amp;rsquo;intérieur du fichier.&lt;/p&gt;
&lt;p&gt;Au redémarrage de l&amp;rsquo;instance, le contenu de la table est erroné, mais PostgreSQL
n&amp;rsquo;aura détecté aucune anomalie de corruption dans le bloc de fichier.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgbench_branches&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- -[ RECORD 1 ]---------
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- bid | 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- bbalance | 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- filler | fl4r%nt
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Depuis la version 9.3 de PostgreSQL, publiée en septembre 2013, il est possible
d&amp;rsquo;activer les &lt;a href="https://paquier.xyz/postgresql-2/postgres-9-3-feature-highlight-data-checksums/" target="_blank" rel="noopener"&gt;sommes de contrôles&lt;/a&gt; pour les données d&amp;rsquo;une instance afin de
contrôler l&amp;rsquo;état d&amp;rsquo;un bloc entre son écriture et ses futures lectures, évitant
alors tout risque de corruption silencieuse.&lt;/p&gt;
&lt;p&gt;Cette opération nécessite pour les versions 11 et inférieures, que l&amp;rsquo;instance
soit créée avec ce mode particulier. Depuis la &lt;a href="https://pgpedia.info/d/data-page-checksums.html" target="_blank" rel="noopener"&gt;version 12&lt;/a&gt;, l&amp;rsquo;utilitaire
&lt;code&gt;pg_checksums&lt;/code&gt; permet d&amp;rsquo;activer et de désactiver le mécanisme de sommes de
contrôle sur le répertoire de données d&amp;rsquo;une instance arrêtée, sans besoin de
migrer vers une nouvelle instance comme il était nécessaire dans les versions
précédentes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ pg_checksums --pgdata&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PGENV_ROOT&lt;/span&gt;/pgsql/data --enable
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Checksum operation completed
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Files scanned: &lt;span class="m"&gt;3097&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Blocks scanned: &lt;span class="m"&gt;109156&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pg_checksums: syncing data directory
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pg_checksums: updating control file
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Checksums enabled in cluster
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dans ce nouveau contexte, je réalise à une nouvelle fois un &lt;code&gt;UPDATE&lt;/code&gt; sur la table…&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgbench_branches&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;filler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;florent&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CHECKPOINT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;… et je corromps la donnée de la table &lt;code&gt;pgbench_branches&lt;/code&gt;. Au redémarrage,
PostgreSQL remonte correctement une anomalie de sommes de contrôle sur le bloc
qui contient la donnée corrompue :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pgbench_branches&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- WARNING: page verification failed, calculated checksum 35393 but expected 2501
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- ERROR: invalid page in block 0 of relation base/17100/17179
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Les corruptions sont des événements rarissimes et imprévisibles. Il est
communément admis que les dégâts sont irréversibles dans un grand nombre de
situations, en l&amp;rsquo;absence de contre-mesures suffisantes.&lt;/p&gt;
&lt;p&gt;À moins d&amp;rsquo;avoir une confiance aveugle dans l&amp;rsquo;infrastructure matérielle qui
héberge vos instances, l&amp;rsquo;activation des sommes de contrôle représente à ce jour
le mécanisme le plus complet pour identifier rapidement toutes les formes de
corruptions sur les données de vos bases.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mauvaise nouvelle : ce mécanisme n&amp;rsquo;est pas actif par défaut.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;À vous d&amp;rsquo;intégrer l&amp;rsquo;option &lt;code&gt;--data-checksums&lt;/code&gt; de l&amp;rsquo;utilitaire &lt;code&gt;initdb&lt;/code&gt; lors du
provisionnement de toutes vos nouvelles instances. Enfin, privilégiez une
version 12 ou supérieure, car vous ne regretterez pas le gain de temps que
vous apportera &lt;code&gt;pg_checksums&lt;/code&gt; !&lt;/p&gt;</description></item><item><title>Compiler et patcher avec pgenv</title><link>https://fljd.in/2020/12/21/compiler-et-patcher-avec-pgenv/</link><pubDate>Mon, 21 Dec 2020 00:00:00 +0000</pubDate><author>Florent Jardin</author><guid>https://fljd.in/2020/12/21/compiler-et-patcher-avec-pgenv/</guid><description>&lt;p&gt;Parmi les quelques outils de mon quotidien, il y en a un très sobre et bigrement
efficace répondant au nom de &lt;a href="https://github.com/theory/pgenv" target="_blank" rel="noopener"&gt;pgenv&lt;/a&gt;, un gestionnaire des versions PostgreSQL.
Ce projet est publié sous licence MIT par David E. Wheeler, auteur de l&amp;rsquo;extension
pgTAP dont j&amp;rsquo;avais déjà vanté les mérites dans un &lt;a href="https://fljd.in/2020/05/14/ecrire-ses-tests-unitaires-en-sql"&gt;autre article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Cet outil concerne principalement les contributeur⋅rices au projet PostgreSQL et les
quelques DBA féru⋅es d&amp;rsquo;expérimentations, car &lt;code&gt;pgenv&lt;/code&gt; permet de compiler et
d&amp;rsquo;exécuter toutes les versions majeures et mineures du système de base de données
open-source le plus avancé du monde.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="à-lépreuve-de-la-compilation"&gt;À l&amp;rsquo;épreuve de la compilation&lt;/h2&gt;
&lt;p&gt;PostgreSQL est particulièrement simple à compiler. Avec un poste de travail
sous Unix, GNU/Linux ou BSD et quelques dépendances, à savoir &lt;code&gt;gcc&lt;/code&gt;, &lt;code&gt;make&lt;/code&gt;,
&lt;code&gt;patch&lt;/code&gt; et &lt;code&gt;git&lt;/code&gt;, il est facile d&amp;rsquo;exécuter une instance dans la version cible de son
choix.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git clone git://git.postgresql.org/git/postgresql.git
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;cd&lt;/span&gt; postgresql
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git checkout REL_13_1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PREFIX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/postgres/devel
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ./configure --prefix&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PREFIX&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ make &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make install
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;cd&lt;/span&gt; contrib
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ make &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make install
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dès lors que les librairies et les binaires sont disponibles, il est très aisé
de contruire sa première instance et de s&amp;rsquo;y connecter !&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PREFIX&lt;/span&gt;/bin:&lt;span class="nv"&gt;$PATH&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;LD_LIBRARY_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PREFIX&lt;/span&gt;/lib:&lt;span class="nv"&gt;$LD_LIBRARY_PATH&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PGDATA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/postgres/data
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ initdb --username &lt;span class="k"&gt;$(&lt;/span&gt;whoami&lt;span class="k"&gt;)&lt;/span&gt; --auth&lt;span class="o"&gt;=&lt;/span&gt;peer --data-checksums
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ pg_ctl start --log&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PGDATA&lt;/span&gt;/server.log
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ createdb &lt;span class="k"&gt;$(&lt;/span&gt;whoami&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ psql -tc &lt;span class="s2"&gt;&amp;#34;select version()&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; PostgreSQL 13.1 on x86_64-pc-linux-gnu, compiled by gcc &lt;span class="o"&gt;(&lt;/span&gt;GCC&lt;span class="o"&gt;)&lt;/span&gt; 10.2.0, 64-bit
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ pg_ctl stop
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le faire à la main m&amp;rsquo;a amusé quelques minutes et écrire un script pour automatiser
le déploiement des versions mineures à la demande m&amp;rsquo;a vite traversé l&amp;rsquo;esprit.
Ne réinventons pas la roue et voyons ce que propose &lt;code&gt;pgenv&lt;/code&gt; !&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="un-script-pour-les-compiler-tous"&gt;Un script pour les compiler tous&lt;/h2&gt;
&lt;p&gt;La &lt;a href="https://github.com/theory/pgenv" target="_blank" rel="noopener"&gt;page d&amp;rsquo;accueil&lt;/a&gt; du projet reprend l&amp;rsquo;installation rapide du script dans votre
sous-répertoire &lt;code&gt;~/.pgenv&lt;/code&gt;. Téléchargeons et compilons la version qui nous interesse.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ pgenv available
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Available PostgreSQL Versions
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;================================================
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; PostgreSQL 10
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;------------------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 10.0 10.1 10.2 10.3 10.4 10.5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 10.6 10.7 10.8 10.9 10.10 10.11
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 10.12 10.13 10.14 10.15
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; PostgreSQL 11
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;------------------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 11.0 11.1 11.2 11.3 11.4 11.5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 11.6 11.7 11.8 11.9 11.10
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; PostgreSQL 12
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;------------------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 12.0 12.1 12.2 12.3 12.4 12.5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; PostgreSQL 13
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;------------------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 13beta1 13beta2 13beta3 13rc1 13.0 13.1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Comme pour mon précédent exemple, je réinstalle une version 13.1 avec &lt;code&gt;pgenv&lt;/code&gt;
à l&amp;rsquo;aide de l&amp;rsquo;option &lt;code&gt;build&lt;/code&gt;. Le script déploie également les librairies de &lt;em&gt;contrib&lt;/em&gt;
et la documentation.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PGENV_ROOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/lib/pgenv
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PGENV_ROOT&lt;/span&gt;/pgsql/bin:&lt;span class="nv"&gt;$PATH&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;LD_LIBRARY_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PGENV_ROOT&lt;/span&gt;/pgsql/lib:&lt;span class="nv"&gt;$LD_LIBRARY_PATH&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ pgenv build 13.1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;PostgreSQL, contrib, and documentation installation complete.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pgenv configuration written to file /var/lib/pgenv/.pgenv.13.1.conf
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;PostgreSQL 13.1 built
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On retrouve dans l&amp;rsquo;arborescence &lt;code&gt;$PGENV_ROOT&lt;/code&gt;, la présence de l&amp;rsquo;archive &lt;code&gt;.tar.bz2&lt;/code&gt;
du projet, requise pour l&amp;rsquo;étape de compilation. Le &lt;code&gt;$PREFIX&lt;/code&gt; quant à lui, est
automatiquement positionné sur le répertoire &lt;code&gt;$PGENV_ROOT/pgsql-13.1&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/var/lib/pgenv
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── .pgenv.13.1.conf
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── pgsql-13.1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   ├── bin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   ├── include
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   ├── lib
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   └── share
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── src
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── postgresql-13.1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── postgresql-13.1.tar.bz2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pour être fidèle à ma première partie, je vais configurer correctement les
paramètres de la commande &lt;code&gt;initdb&lt;/code&gt; dans le fichier de configuration dédié à la
version 13.1.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ pgenv config edit 13.1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Path to the cluster log file (mandatory)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;PGENV_LOG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$PGENV_ROOT&lt;/span&gt;&lt;span class="s2"&gt;/pgsql/data/server.log&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Initdb flags&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;PGENV_INITDB_OPTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;--username &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;whoami&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; --auth=peer --data-checksums&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ainsi, lors de la première utilisation de cette version 13.1, &lt;code&gt;pgenv&lt;/code&gt; va lancer
la commande &lt;code&gt;initdb&lt;/code&gt; pour alimenter le répertoire de données avec mon compte
comme propriétaire et démarrer le processus &lt;code&gt;postgres&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ pgenv use 13.1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Using PGENV_ROOT /var/lib/pgenv
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Data page checksums are enabled.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Success. You can now start the database server using:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /var/lib/pgenv/pgsql/bin/pg_ctl -D /var/lib/pgenv/pgsql/data -l logfile start
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;PostgreSQL 13.1 started
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Logging to /var/lib/pgenv/pgsql/data/server.log
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ createdb &lt;span class="k"&gt;$(&lt;/span&gt;whoami&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ psql -tc &lt;span class="s2"&gt;&amp;#34;select version()&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; PostgreSQL 13.1 on x86_64-pc-linux-gnu, compiled by gcc &lt;span class="o"&gt;(&lt;/span&gt;GCC&lt;span class="o"&gt;)&lt;/span&gt; 10.2.0, 64-bit
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="et-avec-ceci"&gt;Et avec ceci ?&lt;/h2&gt;
&lt;p&gt;Comme indiqué en introduction, l&amp;rsquo;intérêt d&amp;rsquo;un tel gestionnaire réside dans sa
capacité d&amp;rsquo;installer plusieurs versions différentes dans la même arborescence
et de basculer de l&amp;rsquo;une à l&amp;rsquo;autre.&lt;/p&gt;
&lt;p&gt;Imaginons que nous souhaitons disposer d&amp;rsquo;une version 10 de PostgreSQL avec le
même genre de configuration que la version 13 précédente. &lt;code&gt;pgenv&lt;/code&gt; supporte un
fichier d&amp;rsquo;environnement global, nommé &lt;code&gt;.pgenv.conf&lt;/code&gt;, que je reconstruis à
partir de mon précédent fichier d&amp;rsquo;instance 13.1.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cp &lt;span class="nv"&gt;$PGENV_ROOT&lt;/span&gt;/.pgenv.13.1.conf &lt;span class="nv"&gt;$PGENV_ROOT&lt;/span&gt;/.pgenv.conf
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ pgenv build 10.15
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ pgenv use latest &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ createdb &lt;span class="k"&gt;$(&lt;/span&gt;whoami&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ psql -c &lt;span class="s2"&gt;&amp;#34;show data_checksums&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; data_checksums
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ----------------
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; on
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; row&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Nous nous retrouvons bien avec un instance dont les sommes de contrôle ont été
activées, grâce à l&amp;rsquo;option &lt;code&gt;PGENV_INITDB_OPTS&lt;/code&gt; citée plus haut.&lt;/p&gt;
&lt;p&gt;Je m&amp;rsquo;étais questionné sur la capacité de &lt;code&gt;pgenv&lt;/code&gt; de lancer simultanément deux
environnements pour mettre en place de la réplication logique, par exemple.
Conclusion, il s&amp;rsquo;agit d&amp;rsquo;une des limites de l&amp;rsquo;outil, puisque ce n&amp;rsquo;est pas
son but premier. Et pour cause, à chaque fois que l&amp;rsquo;on appelle la commande
&lt;code&gt;pgenv use&lt;/code&gt;, le script arrête l&amp;rsquo;instance courante avant de basculer sur la
deuxième.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ pgenv use latest &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Using PGENV_ROOT /var/lib/pgenv
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;PostgreSQL 13.1 stopped
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;PostgreSQL 10.15 started
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Logging to /var/lib/pgenv/pgsql/data/server.log
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;En complément, &lt;code&gt;pgenv&lt;/code&gt; met en place un lien symbolique dans la racine &lt;code&gt;$PGENV_ROOT&lt;/code&gt;
à chaque changement de version courante. Ce lien a été ajouté au préalable
dans la variable &lt;code&gt;$PATH&lt;/code&gt; pour garantir la bonne compatibilité des binaires avec
les données.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/var/lib/pgenv
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── .pgenv.13.1.conf
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── .pgenv.conf
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── pgsql -&amp;gt; pgsql-10.15
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── pgsql-10.15
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── pgsql-13.1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── src
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ce lien symbolique nous oblige à manipuler toutes les autres instances avec des
chemins absolus, une surcharge de leurs paramètres &lt;code&gt;port&lt;/code&gt; ou &lt;code&gt;listen_addresses&lt;/code&gt;
et de faire appel à la bonne version de la commande &lt;code&gt;pg_ctl&lt;/code&gt;. Il est donc possible
de faire de la réplication, mais oubliez &lt;code&gt;pgenv&lt;/code&gt; pour la gestion des processus
d&amp;rsquo;instances.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="dans-la-cour-des-grands"&gt;Dans la cour des grands&lt;/h2&gt;
&lt;p&gt;Nous sommes en décembre 2020 à l&amp;rsquo;heure de la rédaction de cet article, et la
communauté PostgreSQL travaille activement sur le développement de la prochaine
version 14 du logiciel. Chaque année, les contributeur⋅rices du monde entier
se retrouvent en ligne autour du &lt;em&gt;&lt;a href="https://commitfest.postgresql.org/" target="_blank" rel="noopener"&gt;Commitfest&lt;/a&gt;&lt;/em&gt; pour étudier les nouvelles
propositions de fonctionnalités ou de correction de bogues.&lt;/p&gt;
&lt;p&gt;En août dernier, Tatsuro Yamada proposait d&amp;rsquo;&lt;a href="https://www.postgresql.org/message-id/flat/c027a541-5856-75a5-0868-341301e1624b@nttcom.co.jp_1" target="_blank" rel="noopener"&gt;enrichir&lt;/a&gt; les méta-commandes de
l&amp;rsquo;invite &lt;code&gt;psql&lt;/code&gt; afin de lister les &lt;a href="https://www.postgresql.org/docs/12/planner-stats.html#PLANNER-STATS-EXTENDED" target="_blank" rel="noopener"&gt;statistiques étendues&lt;/a&gt; rattachées aux
tables de la base courante. Cette fonctionnalité est donc étudiée à travers les
échanges électroniques et suivie sur &lt;a href="https://commitfest.postgresql.org/31/2801/" target="_blank" rel="noopener"&gt;une page dédiée&lt;/a&gt; du &lt;em&gt;Commitfest&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Le contributeur produit alors un fichier &lt;code&gt;.patch&lt;/code&gt; qu&amp;rsquo;il obtient avec la commande
&lt;code&gt;git diff&lt;/code&gt; et dont le résultat est compatible avec la commande &lt;a href="https://www.man7.org/linux/man-pages/man1/patch.1.html" target="_blank" rel="noopener"&gt;patch&lt;/a&gt;. Ainsi,
n&amp;rsquo;importe quel relecteur peut l&amp;rsquo;intégrer dans son projet et dérouler ses tests sur
la nouvelle instance compilée.&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est là qu&amp;rsquo;intervient une chouette fonctionnalité de l&amp;rsquo;outil &lt;code&gt;pgenv&lt;/code&gt;. Ce dernier
propose d&amp;rsquo;appliquer une série de patchs dans une phase préliminaire dès lors qu&amp;rsquo;on
lui présente un fichier d&amp;rsquo;index pour la version associée, qui contiendra le chemin
absolu des fichiers à parcourir.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/var/lib/pgenv
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── patch
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    ├── 13
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │   └── 0001-Add-dX-command-on-psql-rebased-on-7e5e1bba03.patch
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    └── index
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── patch.13
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Comme on le voit dans mon arborescence, j&amp;rsquo;ai téléchargé la dernière version
communiquée par le développeur et je l&amp;rsquo;ai déclarée dans le fichier &lt;code&gt;index.13&lt;/code&gt;.
Lors de la recompilation de la version concernée, on constate que le patch
est bien pris en compte.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PGENV_DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ pgenv clear
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ pgenv rebuild 13.1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Using PGENV_ROOT /var/lib/pgenv
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;DEBUG&lt;span class="o"&gt;]&lt;/span&gt; Patch index file &lt;span class="o"&gt;[&lt;/span&gt;/var/lib/pgenv/patch/index/patch.13&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;DEBUG&lt;span class="o"&gt;]&lt;/span&gt; Applying patch &lt;span class="o"&gt;[&lt;/span&gt;0001-Add-dX-command-on-psql-rebased-on-7e5e1bba03.patch&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; into &lt;span class="nb"&gt;source&lt;/span&gt; tree /var/lib/pgenv/src/postgresql-13.1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Applied patch 0001-Add-dX-command-on-psql-rebased-on-7e5e1bba03.patch
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;PostgreSQL 13.1 built
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Et la fonctionnalité devient disponible sur l&amp;rsquo;instance !&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;florent=# \dX
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; List of extended statistics
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Schema | Name | Definition | Ndistinct | Dependencies | MCV
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--------+-------+--------------+-----------+--------------+---------
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; public | stts1 | a, b FROM t1 | | defined |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; public | stts2 | a, b FROM t1 | defined | defined |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; public | stts3 | a, b FROM t1 | defined | defined | defined
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; public | stts4 | b, c FROM t2 | defined | defined | defined
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(4 rows)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le retrait des patchs n&amp;rsquo;est pas supporté par &lt;code&gt;pgenv&lt;/code&gt; mais l&amp;rsquo;opération reste
triviale avec la commande &lt;code&gt;patch&lt;/code&gt; et son option &lt;code&gt;--reverse&lt;/code&gt;. Cela nous permet
de conserver les sources sans devoir les télécharger à nouveau !&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="nv"&gt;$PGENV_ROOT&lt;/span&gt;/src/postgresql-13.1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nv"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PGENV_ROOT&lt;/span&gt;/patch/index/patch.13
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="k"&gt;for&lt;/span&gt; f in &lt;span class="k"&gt;$(&lt;/span&gt;cat &lt;span class="nv"&gt;$index&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; patch --reverse -p1 &amp;lt; &lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;patching file doc/src/sgml/ref/psql-ref.sgml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Hunk &lt;span class="c1"&gt;#1 succeeded at 1903 (offset -15 lines).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;patching file src/bin/psql/command.c
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Hunk &lt;span class="c1"&gt;#1 succeeded at 929 (offset 1 line).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;patching file src/bin/psql/describe.c
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Hunk &lt;span class="c1"&gt;#1 succeeded at 4377 (offset -24 lines).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;patching file src/bin/psql/describe.h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;patching file src/bin/psql/help.c
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;patching file src/bin/psql/tab-complete.c
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Hunk &lt;span class="c1"&gt;#1 succeeded at 1479 (offset -21 lines).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Hunk &lt;span class="c1"&gt;#2 succeeded at 3771 (offset -127 lines).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Pour tout vous dire, je ne sais plus me séparer de &lt;code&gt;pgenv&lt;/code&gt; sauf en de rares
exceptions où mes tests nécessitent une distribution GNU/Linux spécifique, comme
CentOS ou Debian. Une machine virtuelle fournie par &lt;a href="https://www.vagrantup.com/docs/boxes" target="_blank" rel="noopener"&gt;Vagrant&lt;/a&gt; est tout aussi
fiable, notamment lorsqu&amp;rsquo;il s&amp;rsquo;agit de déboguer un paquet d&amp;rsquo;installation ou
une dépendance particulière.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ sudo vagrant box list
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;centos/7 (libvirt, 2004.01)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;debian/buster64 (libvirt, 10.4.0)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;debian/stretch64 (libvirt, 9.12.0)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>De Jekyll à Hugo</title><link>https://fljd.in/2020/12/09/de-jekyll-a-hugo/</link><pubDate>Wed, 09 Dec 2020 00:00:00 +0000</pubDate><author>Florent Jardin</author><guid>https://fljd.in/2020/12/09/de-jekyll-a-hugo/</guid><description>&lt;p&gt;Dans l&amp;rsquo;univers des générateurs de sites statiques, la bataille fait rage entre
plusieurs &lt;em&gt;frameworks&lt;/em&gt; depuis quelques années. Vous avez peut-être entendu parler
de &lt;a href="https://github.com/jekyll/jekyll" target="_blank" rel="noopener"&gt;Jekyll&lt;/a&gt;, &lt;a href="https://github.com/vercel/next.js" target="_blank" rel="noopener"&gt;Next.js&lt;/a&gt;, &lt;a href="https://github.com/gatsbyjs/gatsby" target="_blank" rel="noopener"&gt;Gastby&lt;/a&gt; ou &lt;a href="https://github.com/gohugoio/hugo" target="_blank" rel="noopener"&gt;Hugo&lt;/a&gt; ? Je ne cite que les plus
sollicités par la communauté sur GitHub, mais il en existe des centaines.&lt;/p&gt;
&lt;p&gt;Pourquoi cet engouement ? Cet article n&amp;rsquo;en parlera pas.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Pour de louables raisons, je m&amp;rsquo;étais engagé &lt;a href="https://fljd.in/2019/06/12/reprenons-serieusement"&gt;l&amp;rsquo;année dernière&lt;/a&gt; à auto-héberger
mon nouvel espace d&amp;rsquo;expression personnel. J&amp;rsquo;y ai découvert avec intérêt et amusement
de nombreuses technologies que j&amp;rsquo;ai pliées, réinstallées, transformées pour les
adapter à mon besoin. Sans trop rentrer dans les détails, j&amp;rsquo;ai fini par avoir :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;un serveur VPS chez OVH pour un coût de 3,99€ par mois&lt;/li&gt;
&lt;li&gt;une routine mensuelle de renouvellement de certificat avec &lt;a href="https://pypi.org/project/certbot/" target="_blank" rel="noopener"&gt;certbot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;une instance &lt;a href="https://github.com/eclipse-theia/theia" target="_blank" rel="noopener"&gt;Theia&lt;/a&gt; dans un conteneur Docker&lt;/li&gt;
&lt;li&gt;une image Jekyll 4.1.0 que j&amp;rsquo;invoquais dans Docker&lt;/li&gt;
&lt;li&gt;deux dépôts GitHub privés pour les articles (&lt;em&gt;posts&lt;/em&gt;) et les brouillons (&lt;em&gt;drafts&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;le thème &lt;a href="https://github.com/poole/poole" target="_blank" rel="noopener"&gt;Poole&lt;/a&gt;, léger et modulable… que j&amp;rsquo;ai modulé&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Alors que la question de conserver ou non le serveur VPS se pose pour la nouvelle
année qui démarre bientôt (il devient agé, limité, pour ne pas dire sénil), j&amp;rsquo;ai
remis récemment en cause mon attachement pour le &lt;em&gt;framework&lt;/em&gt; &lt;strong&gt;Jekyll&lt;/strong&gt;, si élégant
à l&amp;rsquo;époque.&lt;/p&gt;
&lt;p&gt;Le machin repose sur des dépendances Ruby. Aussi nombreuses (ou presque) qu&amp;rsquo;un
nouveau projet en Node.js. J&amp;rsquo;ai mangé du &lt;code&gt;Gemfile&lt;/code&gt; par le passé, aussi je n&amp;rsquo;ai eu
aucune réelle difficulté à installer les quelques modules nécessaires à la
concrétisation de mon projet. En voici le contenu :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://rubygems.org&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;jekyll&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;~&amp;gt; 4.1.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;classifier-reborn&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# If you have any plugins, put them here!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="ss"&gt;:jekyll_plugins&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;jekyll-paginate-v2&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;jekyll-target-blank&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;~&amp;gt; 2.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;jekyll-time-to-read&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Et c&amp;rsquo;est tout.&lt;/p&gt;
&lt;p&gt;Je n&amp;rsquo;avais besoin que de la pagination et de l&amp;rsquo;estimation du temps de lecture par
article. Mine de rien, ça me semblait un peu grossier d&amp;rsquo;exiger que ces simples
modules ne soient pas intégrés nativement. J&amp;rsquo;ai donc engagé mes recherches et le
portage du présent site sur l&amp;rsquo;une des autres solutions, à savoir &lt;strong&gt;Hugo&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="migre-moi-ça-pti-con"&gt;Migre moi ça, p&amp;rsquo;ti con&lt;/h2&gt;
&lt;p&gt;Et les développeurs de Hugo n&amp;rsquo;y sont pas allé de main morte pour asseoir leur
logiciel dans ce paysage turbulent : ils intègrent un assistant de migration
spécialement pour un projet Jekyll en récupérant ses fichiers &lt;code&gt;.md&lt;/code&gt; et ses
ressources statiques. En soit, la chose n&amp;rsquo;est pas complexe et le résultat est
au rendez-vous. Mais…&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ hugo import jekyll blog blog_hugo
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Importing...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Congratulations! 128 post(s) imported!
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Now, start Hugo by yourself:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cd blog_hugo
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git clone https://github.com/spf13/herring-cove.git themes/herring-cove
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ hugo server --theme=herring-cove
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bien sûr, l&amp;rsquo;assistant propose un thème pour embellir le projet en lieu et place
d&amp;rsquo;une transposition du language &lt;a href="https://jekyllrb.com/docs/liquid/" target="_blank" rel="noopener"&gt;Liquid&lt;/a&gt; sur lequel repose les fichiers
modèles de Jekyll. C&amp;rsquo;est plus simple mais… ce n&amp;rsquo;est pas réellement ce que je
souhaitais.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://fljd.in/img/fr/2020-12-09-hugo-import-from-jekyll.png" alt="Un thème par défaut pas sans défaut"&gt;&lt;/p&gt;
&lt;p&gt;Autant le rendu des articles est impeccable avec ce thème, autant la navigation
est assez pauvre à mon goût. Bien évidemment, c&amp;rsquo;est bien suffisant pour n&amp;rsquo;importe
qui. Mais voilà. Mon thème principal me manquait et malheureusement pour moi,
il n&amp;rsquo;existait pas pour Hugo.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="de-longues-soirées-dhiver"&gt;De longues soirées d&amp;rsquo;hiver&lt;/h2&gt;
&lt;p&gt;J&amp;rsquo;aime les choses simples. Et j&amp;rsquo;aime encore plus les comprendre en profondeur.
C&amp;rsquo;est pour cette raison que je me suis lancé plusieurs heures dans la lecture
assidue de la documentation très riche sur &lt;a href="https://gohugo.io/documentation/" target="_blank" rel="noopener"&gt;gohugo.io&lt;/a&gt;. Les concepts sont
assez différents et le passage n&amp;rsquo;a pas été si évident que cela. Je vous propose
un florilège des astuces qu&amp;rsquo;il m&amp;rsquo;a fallu découvrir pour proposer le portage
intégral de mon site.&lt;/p&gt;
&lt;h3 id="les-permaliens"&gt;Les permaliens&lt;/h3&gt;
&lt;p&gt;Jekyll et Hugo proposent une gestion fine des URL à l&amp;rsquo;aide du paramètre
&lt;code&gt;permalinks&lt;/code&gt; pour reconstruire l&amp;rsquo;adresse d&amp;rsquo;une page ou d&amp;rsquo;un article et favoriser
son référencement. Cependant, leur paramètrage par défaut diffère et il est
nécessaire de retrouver le comportement de &lt;a href="https://jekyllrb.com/docs/permalinks/" target="_blank" rel="noopener"&gt;l&amp;rsquo;un&lt;/a&gt; avec &lt;a href="https://gohugo.io/content-management/urls/#permalinks" target="_blank" rel="noopener"&gt;l&amp;rsquo;autre&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;permalinks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/:year/:month/:day/:slug&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pour ma part, j&amp;rsquo;ai privilégié le dérivé &lt;code&gt;:slug&lt;/code&gt; pour être en mesure de surcharger
la dernière portion de l&amp;rsquo;URL dans les rares cas où le titre était malformé entre
Jekyll et Hugo. Par exemple pour mon article « &lt;a href="https://fljd.in/2020/08/21/tour-d-horizon-de-pgbouncer/"&gt;Tour d&amp;rsquo;horizon de PgBouncer&lt;/a&gt; »
où l&amp;rsquo;apostrophe n&amp;rsquo;était pas remplacée par le tiret et où je ne voulais pas
perdre le référencement, j&amp;rsquo;ai pu contourner le problème avec l&amp;rsquo;ajout d&amp;rsquo;une simple
ligne en en-tête du fichier Markdown.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Tour d&amp;#39;horizon de PgBouncer&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;tour-d-horizon-de-pgbouncer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Il m&amp;rsquo;a également fallu découvrir un paramètre particulier pour ne pas prendre en
compte les accents présents dans les titres lors de la génération du &lt;em&gt;slug&lt;/em&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;removePathAccents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="syndication-automatique-du-contenu"&gt;Syndication automatique du contenu&lt;/h3&gt;
&lt;p&gt;Pour le coup, j&amp;rsquo;ai bénéficié d&amp;rsquo;une simplification dans mon projet grâce au support
natif et avancé des flux RSS. Alors qu&amp;rsquo;avec Jekyll, je devais maintenir mes
fichiers &lt;code&gt;feed.xml&lt;/code&gt; et &lt;code&gt;atom.xml&lt;/code&gt; à la racine du projet, j&amp;rsquo;ai découvert avec Hugo
la notion de &lt;em&gt;&lt;a href="https://gohugo.io/content-management/taxonomies/" target="_blank" rel="noopener"&gt;taxonomie&lt;/a&gt;&lt;/em&gt; qui permet la classification des articles. La
taxonomie regroupe autant les sections, les catégories et les &lt;em&gt;tags&lt;/em&gt;, et je trouve
ça astucieux.&lt;/p&gt;
&lt;p&gt;Imaginons une arborescence très simple avec une page et un article :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;content/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── pages
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   └── a-propos.md
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── posts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── 2020-11-18-quelques-outils-meconnus.md
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sans configuration supplémentaire, Hugo générera le contenu statique pour chacun
des éléments du projet, en consultant notamment les en-têtes pour extraire les
informations &lt;code&gt;categories&lt;/code&gt; et &lt;code&gt;tags&lt;/code&gt; des articles.&lt;/p&gt;
&lt;p&gt;La magie opère avec un ensemble de fichiers &lt;code&gt;index.xml&lt;/code&gt; regroupant les éléments
d&amp;rsquo;une même taxonomie.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;public/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── 2020
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   └── 11
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   └── 18
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   └── quelques-outils-meconnus
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   └── index.html
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── categories
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   ├── index.html
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   ├── index.xml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   └── postgresql
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   ├── index.html
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   └── index.xml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── index.html
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── index.xml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── pages
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   ├── a-propos
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   │   └── index.html
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   ├── index.html
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   └── index.xml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── tags
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── administration
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    ├── index.html
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    └── index.xml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="la-colorisation-syntaxique"&gt;La colorisation syntaxique&lt;/h3&gt;
&lt;p&gt;À ce sujet, j&amp;rsquo;ai eu une petite surprise avec les balises HTML jusqu&amp;rsquo;à ce que je
me penche sur la &lt;a href="https://gohugo.io/content-management/syntax-highlighting/#generate-syntax-highlighter-css" target="_blank" rel="noopener"&gt;documentation&lt;/a&gt;. En effet, par défaut, Hugo transforme un
bloc de code Markdown en série de balises stylisées.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;highlight&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;pre&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;color:#f8f8f2;background-color:#272822;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; -moz-tab-size:4;-o-tab-size:4;tab-size:4&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;code&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;language-sql&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;data-lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;sql&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;color:#66d9ef&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;CREATE&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;color:#66d9ef&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;TABLE&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; people (
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; id BIGINT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;color:#66d9ef&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;GENERATED&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ALWAYS &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;color:#66d9ef&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;AS&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;color:#66d9ef&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;IDENTITY&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; details jsonb,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;color:#66d9ef&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;PRIMARY&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;color:#66d9ef&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;KEY&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; (id)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;code&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Puisque mon thème original proposait une feuille de style au format &lt;a href="https://sass-lang.com/" target="_blank" rel="noopener"&gt;SASS&lt;/a&gt;
spécfiquement pour la colorisation syntaxique, j&amp;rsquo;ai tenté d&amp;rsquo;activer l&amp;rsquo;un des
paramètres de Hugo pour utiliser les classes de style plutôt que le comportement
ci-dessus.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;pygmentsUseClasses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le résultat était inespéré avec les classes reconnus par mes règles CSS.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;highlight&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;pre&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;chroma&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;code&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;language-sql&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;data-lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;sql&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;k&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;CREATE&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;k&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;TABLE&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;people&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;p&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;(&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;id&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;nb&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;BIGINT&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;k&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;GENERATED&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;ALWAYS&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;k&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;AS&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;k&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;IDENTITY&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;p&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;,&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;details&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;jsonb&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;p&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;,&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;k&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;PRIMARY&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;k&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;KEY&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;p&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;(&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;id&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;p&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;)&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;p&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;);&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;code&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="les-balises-html-dans-le-markdown"&gt;Les balises HTML dans le Markdown&lt;/h3&gt;
&lt;p&gt;Mes fichiers Markdown contenaient des balises HTML pour quelques effets visuels,
à savoir des encarts d&amp;rsquo;avertissement &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;, du surlignage &lt;code&gt;&amp;lt;u&amp;gt;&lt;/code&gt; et de très
(trop) nombreux exposants &lt;code&gt;&amp;lt;sup&amp;gt;&lt;/code&gt; pour mes sources d&amp;rsquo;articles. Pour cet aspect,
Hugo est plus restrictif que Jekyll et ignore tout bonnement les balises dans le
rendu de ses pages.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- raw HTML omitted --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pour le coup, je n&amp;rsquo;ai pas cherché s&amp;rsquo;il existait un paramètre de configuration
qui me permettrait de lever la restriction. Ma solution de contournement fut
d&amp;rsquo;utiliser les composants &lt;em&gt;shortcodes&lt;/em&gt; pour mes quelques exceptions et la
&lt;a href="https://gohugo.io/templates/shortcode-templates/" target="_blank" rel="noopener"&gt;documentation&lt;/a&gt; était bien suffisante pour y parvenir sereinement.&lt;/p&gt;
&lt;p&gt;Par exemple pour mes encarts, j&amp;rsquo;ai gagné en facilité de rédaction avec la méthode
&lt;code&gt;markdownify&lt;/code&gt; fourni par le système de rendu, et je n&amp;rsquo;écris plus que du Markdown
dans mes fichiers !&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- layout/shortcodes/message.html --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ .Inner | markdownify }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- content/posts/exemple.md --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{\{&lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nt"&gt;message&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;}\}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Exemple de rendu avec un lien de [documentation][17].
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{\{&lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;message&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;}\}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;Ne pas s&amp;rsquo;attarder sur les &lt;code&gt;\&lt;/code&gt; qui ne servent qu&amp;rsquo;à échapper les accolades.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="en-bref"&gt;En bref&lt;/h2&gt;
&lt;p&gt;Au-delà des quelques paramètrages ou réécriture Markdown qui m&amp;rsquo;ont fait perdre
du temps, Hugo présente bien plus de fonctionnalités natives que ne le fait
Jekyll. Je dresse une liste non exhausitive des apports qui m&amp;rsquo;ont convaincu à
passer le cap.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/templates/pagination/" target="_blank" rel="noopener"&gt;Pagination&lt;/a&gt; simple et intégrée&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/variables/page/#page-variables" target="_blank" rel="noopener"&gt;Temps estimé de lecture&lt;/a&gt; avec la variable &lt;code&gt;.ReadingTime&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Génération des pages &lt;a href="https://forestry.io/blog/hugo-vs-jekyll-benchmark/" target="_blank" rel="noopener"&gt;nettement plus rapide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Portabilité du logiciel avec &lt;strong&gt;zéro&lt;/strong&gt; dépendance système&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pour conclure, je me suis résolu à héberger le projet et le rendu des pages sur
GitHub, en suivant les &lt;a href="https://gohugo.io/hosting-and-deployment/hosting-on-github/" target="_blank" rel="noopener"&gt;instructions&lt;/a&gt; détaillées. La &lt;a href="https://docs.github.com/en/free-pro-team@latest/github/working-with-github-pages/managing-a-custom-domain-for-your-github-pages-site" target="_blank" rel="noopener"&gt;bascule&lt;/a&gt; du nom de
domaine a ainsi été opérée ce midi et je n&amp;rsquo;ai plus à me préoccuper de mon ancienne
instance VPS ni du certificat à renouveller auprès de Let&amp;rsquo;s Encrypt.&lt;/p&gt;</description></item><item><title>Quelques outils méconnus</title><link>https://fljd.in/2020/11/18/quelques-outils-meconnus/</link><pubDate>Wed, 18 Nov 2020 00:00:00 +0000</pubDate><author>Florent Jardin</author><guid>https://fljd.in/2020/11/18/quelques-outils-meconnus/</guid><description>&lt;p&gt;Cette semaine, passait sur &lt;a href="https://twitter.com/CookSoft_TR/status/1328293682731245568/retweets/with_comments" target="_blank" rel="noopener"&gt;mon fil d&amp;rsquo;actualité Twitter&lt;/a&gt; une simple URL
pointant sur le site &lt;a href="https://pgpedia.info" target="_blank" rel="noopener"&gt;https://pgpedia.info&lt;/a&gt;. Non loin d&amp;rsquo;être le seul à l&amp;rsquo;avoir
remarqué, nous en parlions entre collègues pour constater avec surprise que nous
ne connaissions pas cette mine d&amp;rsquo;or d&amp;rsquo;informations sur PostgreSQL. Après y avoir
perdu plusieurs heures, je me suis dit qu&amp;rsquo;un article sur les quelques utilitaires
que j&amp;rsquo;estime méconnus, pourrait être une bonne conclusion de la semaine.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="pg_controldata"&gt;pg_controldata&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Source : &lt;a href="https://pgpedia.info/p/pg_controldata.html" target="_blank" rel="noopener"&gt;https://pgpedia.info/p/pg_controldata.html&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Ce premier outil est un &lt;em&gt;must-have&lt;/em&gt; pour tous les administrateurs de base de
données. Il permet de dresser les principales informations de l&amp;rsquo;instance, qu&amp;rsquo;elle
soit en cours d&amp;rsquo;exécution ou arrêtée. Ces dernières sont en partie extraites du
fichier &lt;a href="https://pgpedia.info/p/pg_control.html" target="_blank" rel="noopener"&gt;pg_control&lt;/a&gt; contenu dans le répertoire &lt;code&gt;PGDATA/global&lt;/code&gt;, dont
notamment, les informations sur les toutes dernières actions du processus
&lt;code&gt;checkpointer&lt;/code&gt;. On y retrouve aussi quelques configurations fixes et variables
de l&amp;rsquo;instance.&lt;/p&gt;
&lt;p&gt;Lorsque j&amp;rsquo;interviens durant un audit, plusieurs lignes m&amp;rsquo;interessent pour orienter
mes analyses. En voici quelques exemples :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Database cluster state&lt;/code&gt; : pour déterminer l&amp;rsquo;état de l&amp;rsquo;instance et savoir si
j&amp;rsquo;interviens sur une instance principale ou secondaire. La liste des états est
précisée dans le fichier &lt;code&gt;src/include/catalog/pg_control.h&lt;/code&gt; (&lt;a href="https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/include/catalog/pg_control.h" target="_blank" rel="noopener"&gt;source&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;DBState&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;DB_STARTUP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;DB_SHUTDOWNED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;DB_SHUTDOWNED_IN_RECOVERY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;DB_SHUTDOWNING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;DB_IN_CRASH_RECOVERY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;DB_IN_ARCHIVE_RECOVERY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;DB_IN_PRODUCTION&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;DBState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;REDO WAL file&lt;/code&gt; et &lt;code&gt;REDO location&lt;/code&gt; : pour connaître le fichier WAL le plus
proche du dernier &lt;em&gt;checkpoint&lt;/em&gt; requis pour la récupération des transactions suite
à un &lt;em&gt;crash&lt;/em&gt;, dans des situations extrêmes où l&amp;rsquo;archivage n&amp;rsquo;est pas en place.
Dans le cas d&amp;rsquo;une restauration, ces éléments peuvent me permettre d&amp;rsquo;identifier
le bon fichier &lt;code&gt;backup_label&lt;/code&gt; à positionner dans le &lt;code&gt;PGDATA&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Data page checksum version&lt;/code&gt; : parfaitement incontournable, cette valeur
m&amp;rsquo;indique si les &lt;a href="https://pgpedia.info/d/data-page-checksums.html" target="_blank" rel="noopener"&gt;sommes de contrôle&lt;/a&gt; sont actives pour l&amp;rsquo;instance. Ce
mécanisme va permettre de suivre l&amp;rsquo;évolution des données d&amp;rsquo;une page en calculant
une somme de contrôle (&lt;em&gt;checksum&lt;/em&gt;) afin de régulièrement s&amp;rsquo;assurer qu&amp;rsquo;aucune
corruption matérielle n&amp;rsquo;ait provoqué un changement de cette page. Par défaut,
l&amp;rsquo;outil &lt;code&gt;initdb&lt;/code&gt; ne l&amp;rsquo;active pas et c&amp;rsquo;est bien dommage !&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="pg_waldump"&gt;pg_waldump&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Source : &lt;a href="https://pgpedia.info/p/pg_waldump.html" target="_blank" rel="noopener"&gt;https://pgpedia.info/p/pg_waldump.html&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Anciennement connu sous le nom de &lt;code&gt;pg_xlogdump&lt;/code&gt;, avant que n&amp;rsquo;ait eu lieu la
campagne de renommage de &lt;code&gt;xlog&lt;/code&gt; en &lt;code&gt;wal&lt;/code&gt; initiée avec PostgreSQL 10, cet utilitaire
permet de parcourir le contenu des journaux de transactions. Jusqu&amp;rsquo;à présent, je
ne m&amp;rsquo;en sers qu&amp;rsquo;à des fins pédagogiques, bien qu&amp;rsquo;il puisse s&amp;rsquo;avérer redoutable
dans un cas de débogage de haute voltige.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;txid_current&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- txid_current
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- --------------
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 1315
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INTO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;VALUES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Cette simple transaction provoque plusieurs transformations dans les pages de
l&amp;rsquo;instance, notamment dans le catalogue de la base qui reçoit les instructions
SQL, que je cache volontairement dans l&amp;rsquo;exemple suivant :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ pg_waldump -p data/pg_wal --start=0/52CA530 --xid=1315
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rmgr: Storage desc: CREATE base/16384/24399
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rmgr: Standby desc: LOCK xid 1315 db 16384 rel 24399
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rmgr: Heap desc: INSERT+INIT off 1 flags 0x00
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; blkref #0: rel 1663/16384/24399 blk 0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rmgr: Transaction desc: COMMIT 2020-11-18 11:48:23.229489 CET
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;L&amp;rsquo;outil fourni également un vue synthétique avec l&amp;rsquo;option &lt;code&gt;--stats&lt;/code&gt; si l&amp;rsquo;on
souhaite connaître la quantité d&amp;rsquo;opérations (en nombre et taille en octets) à
rejouer lors d&amp;rsquo;une restauration ou d&amp;rsquo;une initialisation des données dans le
cadre d&amp;rsquo;une réplication logique.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ pg_waldump -p data/pg_wal --start=0/52CA530 --end=0/52F8EE8 --stats
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Type N Combined size
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;---- - -------------
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Total 243 189643
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="pg_test_fsync"&gt;pg_test_fsync&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Source : &lt;a href="https://pgpedia.info/p/pg_test_fsync.html" target="_blank" rel="noopener"&gt;https://pgpedia.info/p/pg_test_fsync.html&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Alors, celui-là, je ne le connaissais pas avant hier ! Il s&amp;rsquo;avère être un
compagnon appréciable lorsqu&amp;rsquo;on déploit une instance de production sur un système
dont on a peu ou pas connaissance des performances d&amp;rsquo;écriture. Bien qu&amp;rsquo;à l&amp;rsquo;origine,
cet outil ait été conçu pour comparer les différentes méthodes de synchronisation
sur disques et de correctement positionner le paramètre &lt;code&gt;wal_sync_method&lt;/code&gt; pour
l&amp;rsquo;instance, il permet de connaître très facilement le débit du disque qui
contiendra les journaux de transactions.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ pg_test_fsync --filename=data/pg_wal/testfile
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;5 seconds per test
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;O_DIRECT supported on this platform for open_datasync and open_sync.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Compare file sync methods using one 8kB write:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; open_datasync 1705,357 ops/sec 586 usecs/op
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; fdatasync 1624,232 ops/sec 616 usecs/op
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; fsync 1155,152 ops/sec 866 usecs/op
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; fsync_writethrough n/a
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; open_sync 1177,778 ops/sec 849 usecs/op
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Compare file sync methods using two 8kB writes:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; open_datasync 830,826 ops/sec 1204 usecs/op
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; fdatasync 1631,562 ops/sec 613 usecs/op
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; fsync 367,584 ops/sec 2720 usecs/op
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; fsync_writethrough n/a
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; open_sync 115,385 ops/sec 8667 usecs/op
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Compare open_sync with different write sizes:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 1 * 16kB open_sync write 47,843 ops/sec 20902 usecs/op
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2 * 8kB open_sync writes 20,899 ops/sec 47850 usecs/op
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 4 * 4kB open_sync writes 126,572 ops/sec 7901 usecs/op
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 8 * 2kB open_sync writes 180,532 ops/sec 5539 usecs/op
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 16 * 1kB open_sync writes 96,373 ops/sec 10376 usecs/op
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Test if fsync on non-write file descriptor is honored:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; write, fsync, close 1395,592 ops/sec 717 usecs/op
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; write, close, fsync 1432,511 ops/sec 698 usecs/op
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Non-sync&amp;#39;ed 8kB writes:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; write 257485,249 ops/sec 4 usecs/op
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;J&amp;rsquo;ai ainsi appris que les méthodes variaient, selon les &lt;a href="https://www.postgresql.org/docs/13/wal-reliability.html" target="_blank" rel="noopener"&gt;implémentations&lt;/a&gt; de chaque
système. Sous Linux, nous aurons par défault la méthode &lt;code&gt;fdatasync&lt;/code&gt; alors
qu&amp;rsquo;elle sera &lt;code&gt;open_datasync&lt;/code&gt; sous Windows.&lt;/p&gt;
&lt;p&gt;Dans la même veine, il existe un autre outil de &lt;em&gt;benchmark&lt;/em&gt; nommé
&lt;a href="https://www.postgresql.org/docs/13/pgtesttiming.html" target="_blank" rel="noopener"&gt;pg_test_timing&lt;/a&gt;, mais cette fois-ci, pour contrôler que l&amp;rsquo;horloge du système
ne dérive pas lors d&amp;rsquo;une instruction chronométrée telle que la commande &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="pg_verifybackup"&gt;pg_verifybackup&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Source : &lt;a href="https://pgpedia.info/p/pg_verifybackup.html" target="_blank" rel="noopener"&gt;https://pgpedia.info/p/pg_verifybackup.html&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Ce petit dernier est arrivé en octobre de cette année avec la sortie de PostgreSQL
13. La communauté a mis à disposition un nouveau fichier appelé « manifeste de
sauvegarde » (&lt;em&gt;backup manifest&lt;/em&gt;) qui a pour rôle de lister l&amp;rsquo;ensemble des fichiers
contenu dans une sauvegarde physique, ainsi que leur signature par sommes de contrôle.&lt;/p&gt;
&lt;p&gt;À présent, l&amp;rsquo;outil &lt;code&gt;pg_basebackup&lt;/code&gt; créé le fichier &lt;code&gt;backup_manifest&lt;/code&gt; au sein de
son archive, dont la représentation est au format JSON.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;PostgreSQL-Backup-Manifest-Version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Files&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;backup_label&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Size&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;224&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Last-Modified&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2020-11-18 15:25:39 GMT&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Checksum-Algorithm&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;CRC32C&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Checksum&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;fc2f12b1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;postgresql.conf&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Size&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;27981&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Last-Modified&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2020-10-12 10:28:25 GMT&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Checksum-Algorithm&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;CRC32C&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Checksum&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;d8ad53d1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;global/pg_control&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Size&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8192&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Last-Modified&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2020-11-18 15:25:39 GMT&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Checksum-Algorithm&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;CRC32C&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Checksum&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;43872087&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;WAL-Ranges&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Timeline&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Start-LSN&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0/5400028&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;End-LSN&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0/5400100&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Manifest-Checksum&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;f5cf47bdfbfc0641c...5317932c41&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Alors que l&amp;rsquo;outil tier &lt;a href="https://pgbackrest.org/" target="_blank" rel="noopener"&gt;pgbackrest&lt;/a&gt; proposait son propre système de contrôle,
ce nouveau fichier manifeste pourrait permettre à d&amp;rsquo;autres solutions de sauvegardes
comme &lt;a href="https://github.com/dalibo/pitrery/issues/125" target="_blank" rel="noopener"&gt;pitrery&lt;/a&gt; de bénéficier d&amp;rsquo;une vérification à moindre coût.&lt;/p&gt;
&lt;p&gt;En effet, à l&amp;rsquo;aide de l&amp;rsquo;outil &lt;code&gt;pg_verifybackup&lt;/code&gt;, il est possible de s&amp;rsquo;assurer
qu&amp;rsquo;une sauvegarde physique n&amp;rsquo;a pas subi de corruption ou de transformation avant
de la restaurer.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ pg_verifybackup basebackup
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;error: &amp;#34;global/pg_control.moved&amp;#34; is present on disk but not in the manifest
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;error: &amp;#34;global/pg_control&amp;#34; is present in the manifest but not on disk
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ mv basebackup/global/pg_control.moved basebackup/global/pg_control
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ pg_verifybackup basebackup
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;backup successfully verified
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;La page de &lt;a href="https://www.postgresql.org/docs/13/reference-server.html" target="_blank" rel="noopener"&gt;documentation&lt;/a&gt; « &lt;em&gt;PostgreSQL Server Applications&lt;/em&gt; » recense les
utilitaires maintenus par la communauté. L&amp;rsquo;histoire du projet a montré que nombre
d&amp;rsquo;entre eux étaient issus d&amp;rsquo;une contribution avant d&amp;rsquo;y être intégrés et démocratisés.&lt;/p&gt;
&lt;p&gt;Le site &lt;a href="https://pgpedia.info" target="_blank" rel="noopener"&gt;https://pgpedia.info&lt;/a&gt; est un excellent complément à la documentation car
il retrace avec fidélité les changements survenus pour chaque aspect, méthode,
outil présent dans le projet PostgreSQL. Ajoutez-le à vos favoris !&lt;/p&gt;</description></item></channel></rss>