Cloud et exploitation15 min

L'erreur du premier changement de la journée n'est presque jamais de la malchance

Par · fondateur et PDG de Hábil et Fernando Mendez Carmona · Ingénieur backend chez Hábil ·

Une erreur qui surgit parfois, au premier changement du jour, vient souvent d'une connexion endormie. La prouver, la corriger, éviter les données corrompues.

Une erreur qui survient « de temps en temps, au premier changement de la journée » n'est presque jamais de la malchance. C'est souvent une connexion qui s'est endormie, un cache qui a gardé quelque chose qui ne sert plus ou une horloge dont personne ne se souvient. Et on peut le démontrer avant de le corriger.

Imaginez un matin ordinaire dans une boutique en ligne. La personne qui s'occupe du catalogue arrive, ouvre le système et fait le premier changement de la journée : elle corrige la description d'un produit et l'enregistre. Tout semble normal. Une demi-heure plus tard, quelqu'un des ventes écrit, inquiet : ce produit apparaît dans le moteur de recherche de la boutique sans prix et sans stock. Ou pire, il n'apparaît plus.

Personne n'a touché au prix. Personne n'a rien supprimé. La personne du catalogue rouvre le produit et l'enregistre à nouveau sans rien changer. Le produit revient complet. On note cela comme « un détail » et la journée continue. La prochaine fois que cela arrive, des jours ou des semaines plus tard, quelqu'un le « répare à la main » une fois de plus.

Dans le cas que nous racontons ici, ceux qui le vivaient estiment avoir cohabité longtemps avec cette panne. Quand quelqu'un l'a enfin comptée, en test, elle est apparue plusieurs fois en un seul mois. Et elle avait toujours la même forme : un seul produit, le premier modifié dans la journée.

Cet article s'adresse à celui ou celle qui dirige les opérations ou la technologie, et à l'équipe qui maintient les systèmes. Il couvre quatre points :

  1. Ce qui se passait réellement.
  2. Pourquoi ces erreurs survivent des années.
  3. Comment on la démontre et on la corrige : reproduire la panne volontairement, corriger la cause, empêcher qu'un échec écrive des données corrompues et valider en comparant l'ancien et le nouveau.
  4. Quoi demander lors de votre prochain comité et par où commencer cette semaine.

Ce qui se passait réellement

Le moteur de recherche d'une boutique en ligne ne lit presque jamais directement dans la base de données où vit le catalogue. Il lit dans un index de recherche : une copie du catalogue organisée pour trouver vite. Entre les deux, un processus copie chaque changement. Il peut être monté, par exemple, avec MariaDB comme base, Logstash comme processus de copie et Elasticsearch comme index. Ce processus travaille toute la journée : chaque minute, il demande « qu'est-ce qui a changé ? » et porte les changements jusqu'à l'index.

Pour assembler chaque produit, le processus doit faire une seconde requête à la base : récupérer ses variantes, c'est-à-dire les versions d'un même produit, chacune avec son prix et son stock. Pour ne pas ouvrir une nouvelle connexion à chaque fois, il en garde une ouverte et la réutilise.

Le problème commence la nuit. Personne ne modifie de produits, donc cette connexion reste immobile pendant de longues heures. Et la base de données, par conception, ferme les connexions inactives depuis longtemps. Dans MariaDB et MySQL, pour les connexions de programmes (pas celles d'une personne en console), ce délai s'appelle wait_timeout et, si personne ne le change, il vaut 28 800 secondes : huit heures [1][2]. Le manuel de MySQL le dit sans détour : par défaut, le serveur ferme la connexion au bout de huit heures s'il ne s'est rien passé [3].

La base ferme son côté, mais le processus ne s'en aperçoit pas. Il continue de garder la connexion comme si elle était vivante. Le lendemain matin arrive le premier changement, le processus utilise cette connexion morte et la requête des variantes échoue. Le client de la base ne découvre que la connexion n'existe plus qu'au moment où il essaie de l'utiliser [3]. En Java, avec le pilote de MySQL (le composant logiciel qui parle à la base au nom du programme), le même événement apparaît comme « Communications link failure ». Le pilote lui-même suggère la cause : une période sans utilisation plus longue que le wait_timeout du serveur [4].

Jusqu'ici, ce serait une erreur que l'on relance, et c'est tout. Ce qui la rend coûteuse, c'est ce qui se passe ensuite.

L'échec qui écrit par-dessus la bonne donnée

Quand la requête des variantes échoue, un filtre comme jdbc_streaming de Logstash n'arrête pas le produit. Il lui ajoute une étiquette d'erreur et place la valeur par défaut dans le champ des variantes. Si personne n'a configuré cette valeur, le champ devient une liste contenant un seul élément vide : le produit se retrouve sans données de variantes. Et le filtre le laisse poursuivre son chemin [7]. Le code du filtre le montre clairement ; la documentation ne le mentionne qu'en passant, en décrivant le cache (cache_expiration) [5].

Le produit, désormais sans variantes, arrive dans l'index de recherche. Et l'index l'enregistre avec le même identifiant que celui du bon produit. Ce détail, qui est normalement une qualité, est ici le dommage. Avec l'action d'enregistrement habituelle (index) sur un index ordinaire, enregistrer un document avec un identifiant qui existe déjà met à jour le document entier et incrémente sa version [15][16]. Il ne reste pas un produit cassé à côté du bon ; il reste le cassé à la place du bon.

Dans le cas, le deuxième changement de la journée n'échouait plus : le plus probable est qu'après l'erreur, la connexion morte ait été écartée et une autre ouverte. Cela dépend du pilote et du gestionnaire de connexions, pas du filtre ; il convient de le confirmer dans le journal. Voilà pourquoi la modification suivante « le réparait » et pourquoi l'erreur ne touchait que le premier.

Trois suspects habituels : une connexion, un cache ou une horloge

Le cas a des noms propres, mais le schéma, non. Quand une erreur apparaît « de temps en temps » et presque toujours à la même heure de la journée, ou après un week-end, il convient de chercher quelque chose qui a une minuterie.

Une connexion endormie. La coupure peut venir de trois endroits, et le client ne s'en aperçoit que lorsqu'il réutilise la connexion :

  • La base de données, comme dans ce cas : huit heures par défaut dans MariaDB et MySQL [1][2].
  • Le réseau. Chez Amazon Web Services (AWS), par exemple, la porte de sortie vers internet d'un réseau privé (NAT Gateway) considère comme expirée une connexion qui reste 350 secondes ou plus sans activité. À qui essaie de continuer à l'utiliser, elle répond par un paquet de réinitialisation TCP (RST), qui coupe la connexion [11]. L'équilibreur de charge réseau d'AWS, qui répartit les connexions entre plusieurs serveurs, coupe lui aussi à 350 secondes par défaut. Pour les connexions TCP, cette valeur est configurable entre 60 et 6 000 secondes [12]. Un pare-feu, l'équipement qui filtre le trafic entre deux réseaux, peut faire de même sans prévenir personne. L'Internet Storm Center du SANS a documenté en 2007 un cas de ce type : un pare-feu supprimait les sessions restées une heure sans utilisation entre un serveur d'applications et une base Oracle [13].
  • Le système d'exploitation. Le mécanisme qui maintient les connexions en vie (keepalive TCP) dépend du système d'exploitation et de la façon dont chaque connexion a été ouverte. Dans le cas du SANS, il démarrait au bout de deux heures, trop tard pour le pare-feu [13].

Un cache. Un cache est une mémoire à court terme : il garde une réponse pour ne pas avoir à la redemander. S'il garde une réponse vide ou erronée, il la répète jusqu'à son expiration. Un filtre comme jdbc_streaming, par exemple, avec son cache activé (c'est le cas par défaut), peut le faire. Face à une erreur passagère, il garde le résultat vide pour ces paramètres jusqu'à l'expiration de son délai, cinq secondes par défaut [5][7].

Une horloge. Beaucoup de processus de copie décident quoi lire à partir d'une heure : « apporte-moi ce qui a changé depuis la dernière fois ». Cette heure s'appelle un filigrane (watermark) [6], et elle cache un piège que nous verrons plus bas : elle ne dit pas toujours ce que l'on croit.

Pourquoi ces erreurs survivent des années

Un dirigeant peut se demander, à juste titre, comment une erreur que tout le monde a vue peut durer aussi longtemps. La réponse n'est presque jamais la négligence. C'est que ce type d'erreur a quatre traits qui le protègent :

  • Elle se corrige toute seule. La modification suivante remet le produit en état. Une erreur qui se corrige seule détruit sa propre preuve : quand quelqu'un de la technologie vient regarder, il n'y a plus rien à voir.
  • Personne ne la reproduit. Pour la voir, il faut attendre une nuit entière sans activité et faire un changement juste après. Personne ne fait cela lors d'une journée de travail normale. Ainsi, l'erreur n'apparaît jamais quand quelqu'un la cherche.
  • On la répare à la main et on l'oublie. Réenregistrer le produit prend une minute. Sans ticket, pas de comptage, et sans comptage, pas de priorité.
  • Le symptôme apparaît loin de la cause. Celui qui voit le problème, ce sont les ventes, dans le moteur de recherche. La cause se trouve dans un accord de délais entre deux machines que personne n'avait écrit. Les ventes ne savent pas que cette connexion existe, et celui qui l'a configurée ne voit pas le moteur de recherche.

Comment on la démontre et on la corrige

La tentation, quand quelqu'un soupçonne enfin la cause, est de changer la configuration et d'attendre. Si l'erreur cesse d'apparaître, on la considère comme résolue. Le problème est que « elle a cessé d'apparaître » est exactement ce qui se passait déjà entre une panne et la suivante. Une panne intermittente se démontre avant de se corriger. Voici les quatre étapes que nous suivons.

1. La reproduire volontairement

Si la panne dépend d'une horloge, on raccourcit l'horloge. Dans un environnement de test, l'équipe abaisse le wait_timeout de la base à, disons, 60 secondes. Il y a un détail que beaucoup de gens négligent : chaque connexion prend cette valeur au moment où elle s'ouvre [1][2]. Abaisser la valeur générale n'affecte pas les connexions qui existent déjà. C'est pourquoi le plus pratique est de redémarrer le processus de copie, pour que sa connexion naisse avec la valeur courte.

Ensuite, on attend quelques minutes, on modifie un produit et on regarde. Dans le journal du processus apparaît l'étiquette d'erreur du filtre ; dans l'index, le produit sans variantes. Un test de quelques minutes remplace une attente de huit heures.

Le test qui convainc tout le monde est celui en temps réel. Lors d'un test contrôlé, après plus de 10 heures sans activité, la première modification a échoué et la suivante a réussi. C'était le symptôme exact que voyait le métier, provoqué volontairement et à heure fixe.

2. Corriger la cause, pas le symptôme

Il existe trois réponses standard à une connexion qui s'endort, et ce sont les mêmes dans presque toutes les technologies : la valider avant de l'utiliser, la renouveler avant qu'elle n'expire ou la garder éveillée avec un trafic périodique.

Dans un filtre comme jdbc_streaming de Logstash, par exemple, la première est une option : jdbc_validate_connection, désactivée par défaut [5]. En l'activant, le processus vérifie la connexion avant de l'utiliser. Si elle est morte, il l'écarte et en ouvre une autre [8][9].

Voici la nuance que presque personne ne lit. L'option qui l'accompagne, jdbc_validation_timeout, est documentée comme « à quelle fréquence valider » et vaut 3 600 secondes par défaut [5]. En réalité, elle fonctionne autrement : la bibliothèque qui gère les connexions ne vérifie que celles qui sont restées inutilisées plus longtemps que ce délai [9]. Une connexion qui a passé la nuit immobile est inactive depuis bien plus d'une heure. Donc, avec la valeur par défaut, elle est bien vérifiée, et la coupure de huit heures de la base est couverte.

Mais la coupure peut venir du réseau et être plus courte, comme les 350 secondes d'un équilibreur de charge ou d'un NAT. Là, une connexion restée dix minutes sans utilisation ne serait pas vérifiée et arriverait morte. Contre cette coupure, il faut une autre mesure : abaisser ce seuil en dessous de la coupure, ou maintenir la connexion en vie avec un trafic plus fréquent que la coupure [11].

En Java, le principe est le même. HikariCP, un gestionnaire de connexions très utilisé, recommande que la durée de vie maximale d'une connexion soit de quelques secondes plus courte que toute limite de la base ou de l'infrastructure. Il propose aussi de les maintenir en vie avec une vérification périodique [10]. Et le pilote de MySQL pour Java déconseille son option de reconnexion automatique [4] : se reconnecter à l'aveugle n'est pas la réponse officielle.

3. Empêcher qu'un échec écrive des données corrompues

Corriger la connexion évite l'erreur de chaque matin. Cela n'évite pas la prochaine erreur que personne n'a imaginée. C'est pourquoi le deuxième changement compte autant que le premier : si une partie du produit n'a pas pu être récupérée, on n'écrit rien.

Avec Logstash, par exemple, on y parvient avec une condition après le filtre. Si le produit porte l'étiquette d'erreur de la requête (_jdbcstreamingfailure), il est écarté et n'arrive pas à l'index [7]. Le produit précédent, complet, reste où il était.

Il faut distinguer deux cas qui se ressemblent. Le produit qui a échoué porte deux étiquettes : celle d'erreur et celle de « la valeur par défaut a été utilisée » (_jdbcstreamingdefaultsused) [7]. Un produit qui n'a réellement pas de variantes ne porte que la seconde. C'est pourquoi la condition ne regarde que celle d'erreur : le produit sans variantes continue d'être copié, volontairement, et seul celui qui a échoué en les interrogeant est arrêté.

Cette décision a un coût, et il faut le dire. Si la requête ne demande que ce qui est postérieur au filigrane et que le filigrane a déjà avancé, le changement écarté ne se récupère pas tout seul. C'est ainsi que cela fonctionnait dans le cas ; dans votre système, confirmez-le avec la requête réelle. Le produit garde sa version précédente jusqu'à ce que quelqu'un le modifie de nouveau ou que l'index soit rechargé. Pour un dirigeant, la règle se comprend sans code : mieux vaut le prix d'hier que pas de prix du tout. Un produit avec la donnée d'hier se vend ; un produit sans prix disparaît.

La leçon de fond, pour l'équipe technique : utiliser l'identifiant du produit comme identifiant dans l'index rend inoffensive la répétition d'un chargement, parce que le résultat final est le même [15]. Cette même propriété est celle qui fait des dégâts quand le document arrive incomplet. Qu'un chargement puisse être répété sans dommage ne le rend pas sûr : il faut aussi qu'il n'écrive pas quand quelque chose manque.

4. Vérifier l'horloge et valider par rapport à l'ancien

Le processus de copie décide quoi copier à partir d'un filigrane : « apporte-moi ce qui a changé après la dernière heure que j'ai vue » [6].

Le piège est que l'heure enregistrée comme « dernière modification » n'est pas toujours l'heure à laquelle le changement a été enregistré. Deux termes aident. Une instruction est un ordre donné à la base, comme « change ce prix ». Une transaction est un paquet d'instructions qui s'enregistre en bloc ou pas du tout ; la valider (commit), c'est le moment où son changement est enregistré et devient visible pour les autres.

Dans MySQL, NOW() renvoie l'heure à laquelle l'instruction a commencé, pas l'heure à laquelle elle a été validée [14]. Supposez que la colonne de dernière modification soit remplie avec NOW(), et qu'un gros changement mette du temps à s'enregistrer : il commence à 10 h 00 et est validé à 10 h 03. Ses lignes indiquent « 10 h 00 ». Pendant ce temps, le processus de copie a déjà lu des changements plus récents et déplacé son filigrane au-delà. Quand le gros changement est enfin validé, ses lignes portent une heure antérieure au filigrane, et la lecture suivante ne les demande plus. Elles se perdent en silence. Ce risque appartient à la même famille de limites que signale l'équipe de Debezium à propos de la capture de changements par requête périodique : elle peut perdre des changements intermédiaires et ne voit pas les suppressions [17].

Il y a deux façons de fermer cette porte :

  • Une marge de temps. « Je ne lis que ce qui a au moins une minute d'ancienneté. » Le filigrane n'avance plus sur des secondes qui peuvent encore recevoir des changements. Cela réduit le risque, mais ce n'est pas une garantie : une transaction qui met plus d'une minute à être validée peut encore se perdre.
  • Lire le journal de la base au lieu d'une colonne horodatée. C'est ce que l'on appelle la capture de changements par journal (CDC, pour Change Data Capture). Un exemple est Debezium lisant le journal binaire de MariaDB ou de MySQL. Ce journal enregistre les opérations dans l'ordre où elles ont été validées, y compris les suppressions [17][18]. Bien configuré, cela ferme cette fenêtre en particulier, en échange de davantage de composants à configurer et à exploiter.

Dans le cas que nous racontons, on a choisi la marge d'une minute, avec sa limite écrite.

Reste la question que tout dirigeant devrait poser : comment savons-nous que la correction n'a rien cassé de ce qui fonctionnait avant ? La réponse du cas a été une comparaison complète. L'ancienne requête et la nouvelle ont été exécutées sur tout le catalogue, en test, et ont donné exactement le même résultat. La nouvelle requête n'a changé que le moment où elle lit. Toute différence aurait été une nouvelle erreur. Il n'y en a eu aucune.

Et ensuite, de nouveau le test de la nuit. Lors d'un test contrôlé, après plus de 16 heures sans activité, la première modification est passée sans erreur : le cas exact qui échouait auparavant. En outre, les produits restés cassés ont été réparés, et à la clôture de la revue il n'en restait aucun de cassé en test.

Cinq questions pour votre prochain comité

  1. Quelles erreurs « se corrigent toutes seules » dans nos systèmes ? Demandez aux ventes, au service client et à qui gère le catalogue, pas seulement à la technologie. Les meilleures pistes se trouvent souvent chez ceux qui réparent à la main.
  2. L'une d'elles se produit-elle toujours à la même heure, le lundi, ou après une nuit sans utilisation ? Si la réponse est oui, il y a une horloge derrière.
  3. Savons-nous combien de temps chaque connexion entre nos systèmes tient sans utilisation, et qui la coupe en premier : la base, le réseau ou le cloud ? Si personne ne l'a écrit, personne ne l'a décidé.
  4. Quand un processus ne peut pas récupérer toute l'information, que fait-il ? Laisse-t-il ce qu'il y avait ou publie-t-il quelque chose d'incomplet ?
  5. Quand la technologie dit « c'est réglé », comment l'a-t-elle démontré ? A-t-elle provoqué la panne avant la correction et l'a-t-elle provoquée de nouveau après, ou a-t-elle seulement cessé de la voir ?

Si deux réponses ou plus sont « je ne sais pas », le problème n'est pas une question de chance : il manque une mesure.

Par où commencer cette semaine

Il n'y a pas besoin d'un projet. Il faut une liste. Demandez aux équipes qui exploitent vos systèmes de noter, pendant deux semaines, chaque fois que quelque chose « s'est corrigé tout seul » ou « a été réparé à la main » : ce que c'était, à quelle heure et ce qui a été fait. Au bout des deux semaines, triez la liste par heure. Si plusieurs entrées se regroupent en tout début de matinée ou après un week-end, vous tenez votre premier suspect. Il suffit d'un après-midi à votre équipe technique pour commencer à le reproduire dans un environnement de test.

Ce que fait Hábil

Quand une panne se répète, l'Équipe SWAT de Hábil cherche la cause de fond, pas un redémarrage de plus : elle la reproduit, la corrige et vous remet la preuve de la façon dont elle a été vérifiée, pour que vous puissiez la revoir.

La première conversation est sans frais : son objectif est de vous aider à situer quelle panne se répète, depuis quand et comment on peut la démontrer. Écrivez-nous sur WhatsApp.

Références

  1. MariaDB, Server System Variables, wait_timeout (28 800 s par défaut ; la valeur de session est initialisée à l'ouverture de la connexion, à partir de la valeur globale ou de interactive_timeout). https://mariadb.com/docs/server/server-management/variables-and-modes/server-system-variables#wait_timeout
  2. MySQL 8.4 Reference Manual, Server System Variables, wait_timeout (28 800 s ; la valeur de session est copiée de la valeur globale au début de chaque connexion). https://dev.mysql.com/doc/refman/8.4/en/server-system-variables.html#sysvar_wait_timeout
  3. MySQL 8.4 Reference Manual, MySQL server has gone away (fermeture après huit heures sans activité ; erreurs CR_SERVER_GONE_ERROR et CR_SERVER_LOST). https://dev.mysql.com/doc/refman/8.4/en/gone-away.html
  4. MySQL Connector/J, fichier de messages (branche release/9.x, commit 1363d9b, consulté le 20-oct-2026) du pilote (« Communications link failure » ; suggestion concernant wait_timeout ; autoReconnect déconseillé). https://github.com/mysql/mysql-connector-j/blob/1363d9bf5412c9adb246756a8334ee2d584a3c8f/src/main/resources/com/mysql/cj/LocalizedErrorMessages.properties
  5. Elastic, Jdbc_streaming filter plugin (jdbc_validate_connection, jdbc_validation_timeout, tag_on_failure, default_hash ; dans cache_expiration, le fait que face à des erreurs passagères le cache garde des résultats vides et que le default_hash est appliqué). https://www.elastic.co/docs/reference/logstash/plugins/plugins-filters-jdbc_streaming
  6. Elastic, Jdbc input plugin (filigrane avec :sql_last_value, tracking_column, schedule ; recommandation de valider les connexions entre les exécutions). https://www.elastic.co/docs/reference/logstash/plugins/plugins-inputs-jdbc
  7. logstash-integration-jdbc 5.6.6 (commit f7586e6, consulté le 20-oct-2026), code du filtre et de son gestionnaire de requêtes (en cas d'erreur : étiquette d'erreur, étiquette de valeur par défaut, default_hash et l'événement continue ; mise en cache du résultat vide). https://github.com/logstash-plugins/logstash-integration-jdbc/blob/f7586e67912852d5c5a6ec0f4db5f4a4f30e7806/lib/logstash/filters/jdbc_streaming.rb · https://github.com/logstash-plugins/logstash-integration-jdbc/blob/f7586e67912852d5c5a6ec0f4db5f4a4f30e7806/lib/logstash/plugin_mixins/jdbc_streaming/statement_handler.rb
  8. logstash-integration-jdbc 5.6.6 (commit f7586e6), code du module de connexion (active le validateur de Sequel avec jdbc_validation_timeout). https://github.com/logstash-plugins/logstash-integration-jdbc/blob/f7586e67912852d5c5a6ec0f4db5f4a4f30e7806/lib/logstash/plugin_mixins/jdbc_streaming.rb
  9. Sequel, extension connection_validator (valide au moment de sortir la connexion du pool, seulement si elle est inactive depuis plus longtemps que le seuil). https://sequel.jeremyevans.net/rdoc-plugins/files/lib/sequel/extensions/connection_validator_rb.html
  10. HikariCP, README (maxLifetime, keepaliveTime : vérification périodique avec Connection.isValid() ou une requête de test sur les anciens pilotes). https://github.com/brettwooldridge/HikariCP
  11. AWS, Troubleshoot NAT gateways, connexions qui expirent après 350 s sans activité et réponse avec RST. https://docs.aws.amazon.com/vpc/latest/userguide/nat-gateway-troubleshooting.html#nat-gateway-troubleshooting-timeout
  12. AWS, Update the idle timeout for your Network Load Balancer (350 s par défaut ; configurable de 60 à 6 000 s uniquement sur les écouteurs TCP ; en TLS, fixé à 350 s). https://docs.aws.amazon.com/elasticloadbalancing/latest/network/update-idle-timeout.html
  13. SANS Internet Storm Center, Bojan Zdrnja, « Gone in 3600 seconds: story about TCP Keep-Alives » (8 nov. 2007). https://isc.sans.edu/diary/3623
  14. MySQL 8.4 Reference Manual, Date and Time Functions, NOW() (heure de début de l'instruction). https://dev.mysql.com/doc/refman/8.4/en/date-and-time-functions.html#function_now
  15. Elastic, Elasticsearch output plugin (document_id pour écraser les documents ayant le même identifiant). https://www.elastic.co/docs/reference/logstash/plugins/plugins-outputs-elasticsearch
  16. Elastic, Index API (indexer avec un identifiant existant met à jour le document entier et incrémente sa version). https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-index
  17. Gunnar Morling, « Five Advantages of Log-Based Change Data Capture », blog de Debezium (19 juil. 2018). https://debezium.io/blog/2018/07/19/advantages-of-log-based-change-data-capture/
  18. Debezium, connecteurs MySQL et MariaDB (lecture du journal binaire dans l'ordre de validation). https://debezium.io/documentation/reference/stable/connectors/mysql.html · https://debezium.io/documentation/reference/stable/connectors/mariadb.html

Votre activité fait face à ces défis ?

Parlons de votre cas

Vous préférez l'e-mail ? Écrivez-nous à hola@habil.mx