Après avoir basculé vers un bus de données rabbitMQ, nous décidons de supprimer son clustering, source potentielle de blocages en version 1.8, et qui posait, d'après un expert, des soucis à la base de données mnesia sur laquelle il s'appuie.

Notre espoir était grand que cette source d'instabilité éliminée, l'architecture du bus serait enfin stable. Que neni !

De vils fantômes hantent les server.xml : des contextes tomcat inutilisés mais encore déployés leurrent la VIP (Virtual IP). En voulant équilibrer la charge des 14 serveurs (6 vivants et 8 fantômes) elle envoie 5 vivants sur un bus et 1 seul sur l'autre. Résultat : le bus surchargé s'effondre, nouvel incident.

Une fois l'équilibrage de charge retrouvé la tranquillité des serveurs est troublée par une pénurie de mémoire. Le swap ne pardonne pas. Nous séparons les bus de leurs consommateurs sur des machines dédiées. Ces consommateurs java écrivent les évènements dans la base mysql. Chacun chez soi, la consommation mémoire devient exemplaire. Notre confiance n'a jamais été aussi grande.

Nouvelle illusion.

Quand SQL affame les ports TCP

Un vendredi soir où je suis d'astreinte, entre 21:35 et 21:53, j'observe 7944 exceptions :

java.sql.SQLException: Unable to connect to any hosts due to exception:
java.net.SocketException: java.net.BindException: Address already in use

Address already in use ? Étrange, cette erreur se produit notamment quand un port est occupé. Nous observons également via netstat un grand nombre de sockets dans l'état TIME_WAIT, état dans lequel la socket vient d'être fermée, mais n'a pas encore libéré ses ressources. Tous les ports sont-ils occupés ? Quand j'en avais parlé au responsable technique de la plateforme, il était sceptique quand à cette hypothèse : les ports dans l'état TIME_WAIT peuvent être réutilisés par de nouvelles connexions. Sauf que cette option n'est pas positionnée par défaut... En prod, la configuration du système est la suivante :

  • /proc/sys/net/ipv4/tcp_tw_reuse : l'option pour réutiliser les ports est désactivée
  • /proc/sys/net/ipv4/tcp_fin_timeout : timeout de 60 secondes
  • /proc/sys/net/ipv4/ip_local_port_range : 28000 ports disponibles (32768 à 61000)

Calcul rapide : a 450 connexions par secondes, si chacune bloque en TIME_WAIT pendant 60 secondes, on atteint les 28000 ports (450 x 60) :


Et les nouvelles connexions sql engendrent l'exception Address already in use.

Nos consommateurs ouvrent en effet une nouvelle connexion pour chaque évènement (5 millions par jours). La solution : intercaler un pool de connections sql entre consommateur et base de donnée. Le pool réutilise toujours la même connexion là où le consommateur en recréait une pour chaque requête :

En 2010, la mise en production du bus, avait fait monté la charge cpu des mysql. Nous l'avions attribué à un changement du modèle de données alors qu'il s'agissait des ouvertures / fermetures de connexions. Des tir de bench après l'ajout du pool montrent l'amélioration du temps d'insertion en base (rouge sans pool, vert avec pool) :

Le record de traffic a été récemment pulvérisé, validant ainsi cette hypothèse sur une volumétrie de 2 millions d'évènements par heure.

Références : Des précisions sur les connexions TCP dans l'état TIME_WAIT, clipart