Il est 02:14. L’application apparaît comme « up » dans le tableau de bord, mais chaque requête qui touche la base renvoie un 500 poli et une ligne de log très impolie : Too many open files. Vous augmentez une limite, redémarrez, et ça « marche ». Pendant trois jours. Puis ça recommence, lors de la paie, de la clôture trimestrielle, ou de tout autre rituel que votre entreprise utilise pour invoquer le chaos.
C’est l’un de ces pannes qui ressemble à une question de trivia OS et qui est en réalité un problème d’architecture système. MariaDB et PostgreSQL y parviennent différemment, pour des raisons différentes, avec des réglages différents. La solution n’est que rarement « mettre nofile à un million et passer à autre chose. » Ce n’est pas une solution. C’est un pari.
Ce que signifie réellement « Too many open files » (et pourquoi c’est trompeur)
Sous Linux, « Too many open files » se traduit généralement par EMFILE : le processus a atteint sa limite de descripteurs de fichiers par processus. Parfois c’est ENFILE : le système a atteint sa limite globale de descripteurs. Parfois ce n’est ni l’un ni l’autre et vous regardez un plafonnement au niveau applicatif qui est enregistré comme « open files » parce que les ingénieurs sont optimistes et nommer les choses est difficile.
Un descripteur de fichier (FD) est une poignée vers une « chose » ouverte : un fichier régulier, un répertoire, une socket Unix domain, une socket TCP, un pipe, un eventfd, une surveillance inotify, une instance epoll. Les bases de données utilisent tout cela. Si vous ne pensez qu’aux « fichiers de table », vous diagnostiquererez le mauvais problème et vous le réparerez mal.
Deux vérités opérationnelles importantes :
- L’épuisement des FDs n’est rarement un problème lié à un seul réglage. C’est une interaction entre limites OS, valeurs par défaut de systemd, configuration de la base, comportement des connexions et forme de la charge.
- L’épuisement des FDs est un symptôme. La cause racine est généralement : trop de connexions, trop d’objets relationnels (tables/index/partitions), ou un réglage de cache qui a transformé « réutiliser les fichiers ouverts » en « tout garder ouvert pour toujours ».
Autre point : vous pouvez « réparer » EMFILE en augmentant les limites jusqu’à ce que le serveur puisse ouvrir suffisamment de fichiers pour progresser, puis transférer l’échec ailleurs : pression mémoire, épuisement d’inodes, agitation du cache de dentry du noyau, ou complexité opérationnelle pure. L’objectif n’est pas descripteurs infinis. L’objectif est une utilisation contrôlée des ressources.
Une citation à garder sur un post‑it : « Hope is not a strategy. » — General Gordon R. Sullivan. En exploitation, ce n’est pas un simple slogan mais un outil de diagnostic.
Comment les descripteurs de fichiers sont consommés dans de vrais serveurs de base de données
Si vous déboguez ça en production, vous avez besoin d’un modèle mental de ce qui tient effectivement des FDs ouverts. Voici la liste non exhaustive qui importe.
Connexions : la fabrique silencieuse de FDs
Chaque connexion cliente consomme au moins un FD côté serveur (la socket), plus quelques éléments internes. Avec TLS, vous ajoutez un surcoût CPU ; avec un pooling mal fait, vous ajoutez du churn de connexions et des pics. Si vous avez 5 000 connexions actives parce que « microservices », vous n’êtes pas moderne — vous payez juste un loyer par socket.
Fichiers de données, index et fichiers de relation
Les bases de données évitent de rouvrir constamment les fichiers. Les caches existent en partie pour garder des FDs afin que le cache de pages OS fasse son travail et que la base évite le coût d’un appel système. Mais les caches peuvent être surdimensionnés ou mal réglés.
- MariaDB/InnoDB : plusieurs espaces de tables, logs de redo, logs d’undo, tables temporaires, fichiers .ibd par table quand
innodb_file_per_table=ON. - PostgreSQL : chaque fork de relation (main, FSM, VM) correspond à des fichiers ; les grandes relations sont segmentées en plusieurs fichiers ; les fichiers temporaires apparaissent sous
base/pgsql_tmpou dans les répertoires temporaires par tablespace.
Fichiers temporaires et comportement de débordement sur disque
Sorts, hachages, agrégations volumineuses et certains plans de requête débordent sur disque. Cela signifie des fichiers temporaires. Assez de requêtes parallèles et vous obtenez une petite tempête de descripteurs ouverts.
Réplication et workers d’arrière-plan
Les threads de réplication, WAL senders/receivers, threads d’E/S et workers d’arrière-plan tiennent des sockets et des fichiers ouverts. Ce n’est généralement pas le plus gros consommateur, mais dans un cluster chargé avec plusieurs réplicas, ça s’accumule.
Logs, slow logs, audit logs et « ajoutons plus d’observabilité »
Les logs sont des fichiers. Certaines configurations de journalisation ouvrent plusieurs fichiers (patterns de rotation, logs d’audit séparés, logs d’erreur, logs généraux). Si vous taillez les logs avec des outils qui ouvrent des descripteurs supplémentaires ou si vous exécutez des sidecars qui font la même chose, vous pouvez contribuer à la pression FD. Ce n’est pas typiquement le principal coupable, mais c’est dans le bilan.
Blague #1 : « Too many open files » est la façon qu’a le serveur de dire qu’il est émotionnellement indisponible pour l’instant.
MariaDB vs PostgreSQL : comportement sous pression de FDs
Modes d’échec de MariaDB (InnoDB) : le cache de tables face à la réalité du système de fichiers
La douleur FD la plus courante de MariaDB provient de l’utilisation des fichiers de table/index et du comportement du cache de tables combinés à une forte concourrence. Historiquement, les serveurs de la famille MySQL s’appuyaient sur des caches de tables (table_open_cache, table_definition_cache) pour réduire le churn d’ouverture/fermeture. C’est bien — jusqu’à ce que ça ne le soit plus.
Ce qui arrive dans le « mauvais » cas :
- Vous avez beaucoup de tables, ou beaucoup de partitions (qui sont effectivement des objets semblables à des tables), ou beaucoup de schémas.
- Vous avez réglé
table_open_cacheélevé parce que quelqu’un a dit que ça améliore les performances. - La charge touche de nombreuses tables distinctes à travers de nombreuses sessions.
- MariaDB essaie de les garder ouvertes pour satisfaire les hits du cache.
- Le processus atteint
RLIMIT_NOFILE(par processus), ou la limite interne d’ouverture de fichiers du serveur, et commence à échouer des opérations.
InnoDB ajoute ses propres angles :
innodb_open_filesfournit une cible pour le nombre de fichiers InnoDB qu’il peut garder ouverts, mais elle est bornée par les limites OS et les autres utilisateurs de fichiers dans le processus.- L’utilisation de tables temporaires (sur disque) peut faire monter en flèche les FDs.
- Les outils de sauvegarde (logiques ou physiques) peuvent ajouter de la charge et ouvrir des handles.
Modes d’échec de PostgreSQL : connexions et surcharge par session
PostgreSQL utilise un modèle process-per-connection (avec des nuances comme les background workers). Cela signifie que chaque connexion est son propre processus avec sa propre table de FDs. La bonne nouvelle : l’épuisement FD par processus est moins probable si chaque backend utilise peu de FDs. La mauvaise nouvelle : trop de connexions signifie trop de processus, trop de sockets, trop de mémoire, trop de changements de contexte, et un fort usage global des ressources.
PostgreSQL rencontre souvent « too many open files » dans ces scénarios :
- Grand nombre de connexions avec une limite FD basse pour le postmaster/backends sous systemd.
- Grand nombre de relations avec des patterns de requête touchant de nombreuses relations dans une seule session (pensez aux tables partitionnées avec scans larges).
- Création massive de fichiers temporaires à cause des sorts/hachages et de la requête parallèle, aggravée par un
work_memtrop bas (plus de débordements) ou une parallélisation trop élevée (plus de débordements concurrents). - Autovacuum et maintenance sur de nombreuses relations, plus la charge utilisateur. Beaucoup d’ouvertures de fichiers.
PostgreSQL a aussi un comportement subtil mais réel : même si vous augmentez la limite FD OS, vous pouvez rester limité par des attentes internes ou par d’autres limites OS (comme le nombre maximal de processus, les réglages de mémoire partagée, ou des plafonds de cgroup). EMFILE est rarement seul.
La différence pratique qui change la solution
MariaDB a tendance à atteindre l’épuisement de FDs à cause des fichiers de tables ouverts et des caches. La solution est généralement une combinaison de bon LimitNOFILE, bon open_files_limit, et un dimensionnement sensé du cache de tables — plus la gestion de l’explosion des tables/partitions.
PostgreSQL a tendance à atteindre l’épuisement de FDs via le comportement des connexions et le churn des fichiers temporaires. La solution est souvent : pooling de connexions, réduction du nombre de connexions, augmentation appropriée des limites OS, et tuning mémoire/parallélisme pour réduire les tempête de débordements.
Faits intéressants et contexte historique (qui comptent vraiment)
- Les descripteurs Unix ont été conçus comme une abstraction unificatrice pour « tout est un fichier », élégant jusqu’à ce que votre base traite tout comme « ouvert et ne jamais lâcher ».
- Les Unix anciens avaient de très faibles limites FD par défaut (souvent 64), et l’habitude de valeurs conservatrices n’a jamais complètement disparu — les valeurs par défaut de systemd posent toujours problème aux serveurs modernes.
- Le modèle process-per-connection de PostgreSQL est un choix architectural ancien qui échange simplicité et isolation contre un surcoût plus élevé en très haute concurrence.
- Les réglages de cache de tables de MySQL viennent d’un monde où les opérations de métadonnées du système de fichiers étaient coûteuses et « garder ouvert » était un gain mesurable.
- Le système de fichiers
/procde Linux a rendu l’introspection des FDs beaucoup plus facile ; avant cela, diagnostiquer une fuite de FD ressemblait plus à de l’archéologie. - Les cgroups et les conteneurs ont changé la donne : vous pouvez avoir des limites élevées sur l’hôte mais des limites faibles dans le conteneur ; le processus voit le monde réduit et échoue là.
- Les systèmes de fichiers modernes ont rendu les open/close moins coûteux qu’avant, mais « moins coûteux » n’est pas « gratuit » lorsqu’on le multiplie par des milliers de requêtes par seconde.
- La réplication a augmenté les schémas d’usage des FDs dans les deux écosystèmes, ajoutant plus de sockets et d’activité de fichiers journaux — surtout dans les topologies multi-réplicas.
Mode d’intervention rapide pour le diagnostic
C’est la partie à suivre quand vous êtes en astreinte, à moitié réveillé, et que votre cerveau tente de négocier un cessez-le-feu avec la réalité.
Premier point : confirmez quelle limite vous atteignez (processus vs système)
- Vérifiez la source de l’erreur : logs de la base, logs système et logs applicatifs. Déterminez si le processus de la base échoue à ouvrir des fichiers, ou si ce sont les clients qui n’arrivent pas à se connecter.
- Vérifiez la limite par processus : inspectez le
Max open filesdu processus de base depuis/proc. S’il est bas (souvent 1024/4096), vous avez probablement trouvé la cause immédiate. - Vérifiez la pression globale sur les handles système :
/proc/sys/fs/file-nr. Si le système est proche du max, augmenter la limite par processus n’aidera pas sans augmenter la capacité globale et trouver le consommateur.
Deuxième point : identifiez qui tient les FDs
- Comptez les FDs ouverts par PID et identifiez les principaux consommateurs. Si c’est la BDD, poursuivez. Si c’est un sidecar, un agent de logs ou un agent de sauvegarde, vous avez un incident différent.
- Classifiez les types de FDs : s’agit‑il surtout de sockets (connexions) ou de fichiers réguliers (tables, fichiers temporaires, logs) ? Cela indique quels réglages de la base importent.
Troisième point : déterminez si c’est un « pic » ou une « fuite »
- Pic : les FDs montent pendant un pic de trafic ou un job batch, puis redescendent. Correctif : capacité et contrôle de la concurrence.
- Fuite/croissance persistante : les FDs montent progressivement et ne redescendent jamais. Correctif : identifiez ce qui reste ouvert (cache trop grand, bug, connexions coincées, fuite d’un outil).
Quatrième point : arrêter l’hémorragie en sécurité
- À court terme : augmentez les limites seulement si vous êtes sûr que le noyau a de la marge et que vous n’induiserez pas de pression mémoire. Préférez un redémarrage contrôlé avec des limites corrigées plutôt que de bidouiller ulimit au hasard.
- Réduisez la concurrence : bridez les jobs batch, réduisez le nombre de workers applicatifs ou activez le pooling. Une base incapable d’ouvrir des fichiers ne peut pas servir de requêtes.
Tâches pratiques : commandes, sorties et décisions (12+)
Ce sont les tâches qui transforment « je pense » en « je sais ». Chacune inclut une commande, un extrait de sortie réaliste, ce que cela signifie et ce que vous décidez ensuite.
Task 1: Confirm the DB process and PID
cr0x@server:~$ ps -eo pid,comm,args | egrep 'mariadbd|mysqld|postgres' | head
1287 mariadbd /usr/sbin/mariadbd
2140 postgres /usr/lib/postgresql/16/bin/postgres -D /var/lib/postgresql/16/main
2142 postgres postgres: checkpointer
Sens : Vous avez MariaDB sur PID 1287 et PostgreSQL postmaster sur PID 2140 (plus des workers). Sachez lequel échoue ; ne « corrigez » pas les deux sans raison.
Décision : Choisissez le(s) PID que vous allez inspecter dans les étapes suivantes. Si l’erreur est dans l’application, confirmez quel endpoint BDD est utilisé.
Task 2: Check per-process max open files (the one that usually bites)
cr0x@server:~$ cat /proc/1287/limits | egrep -i 'open files|max processes'
Max open files 1024 1048576 files
Max processes 127636 127636 processes
Sens : La limite soft est 1024 ; la hard est 1048576. MariaDB vit sur un régime d’amaigrissement.
Décision : Corrigez l’unité de service ou les limites PAM pour que la BDD démarre avec une limite soft sensée (par ex. 65535 ou plus selon le dimensionnement). N’augmentez pas seulement la hard et n’oubliez pas la soft.
Task 3: Count current open FDs for a PID
cr0x@server:~$ ls -1 /proc/1287/fd | wc -l
1008
Sens : Le processus est proche du plafond 1024. EMFILE est imminent ou déjà en cours.
Décision : Remédiation immédiate : réduire la charge et préparer un redémarrage avec des limites corrigées. Trouvez aussi ce qui consomme les FDs (tâches suivantes).
Task 4: Identify what types of FDs are open (files vs sockets)
cr0x@server:~$ ls -l /proc/1287/fd | awk '{print $11}' | sed -e 's/.*socket:.*/socket/' -e 's/.*pipe:.*/pipe/' -e 's/.*anon_inode:.*/anon_inode/' | sort | uniq -c | sort -nr | head
612 socket
338 /var/lib/mysql/db1/orders.ibd
42 anon_inode
16 pipe
Sens : Majoritairement des sockets et des fichiers de table InnoDB. Ce n’est pas juste « trop de tables » ou juste « trop de connexions ». C’est les deux.
Décision : Enquêter sur les comptes de connexions et les réglages du cache de tables en parallèle. Corriger un seul côté risque de simplement déplacer le goulot d’étranglement.
Task 5: Check system-wide file handle usage (global pressure)
cr0x@server:~$ cat /proc/sys/fs/file-nr
38144 0 9223372036854775807
Sens : Les handles alloués au niveau système vont bien ; la limite globale est effectivement énorme. C’est un problème par processus, pas global.
Décision : Concentrez-vous sur les limites systemd/PAM et la config de la BDD, pas sur le kernel fs.file-max.
Task 6: Inspect systemd service limits (the hidden culprit)
cr0x@server:~$ systemctl show mariadb -p LimitNOFILE -p LimitNPROC -p TasksMax
LimitNOFILE=1024
LimitNPROC=127636
TasksMax=4915
Sens : systemd définit explicitement LimitNOFILE=1024. Vous pouvez éditer /etc/security/limits.conf toute la journée ; systemd restera prioritaire pour les services.
Décision : Ajoutez un override systemd avec un LimitNOFILE plus élevé et redémarrez le service. Considérez aussi TasksMax si vous êtes sur PostgreSQL avec beaucoup de backends.
Task 7: Apply a systemd override for MariaDB or PostgreSQL
cr0x@server:~$ sudo systemctl edit mariadb
# (opens editor)
cr0x@server:~$ sudo cat /etc/systemd/system/mariadb.service.d/override.conf
[Service]
LimitNOFILE=65535
Sens : Vous avez défini une nouvelle limite FD au niveau du service. C’est la couche correcte pour les services.
Décision : Rechargez systemd et redémarrez MariaDB pendant une fenêtre contrôlée. Puis re-vérifiez /proc/<pid>/limits.
Task 8: Reload systemd and validate the new limit is live
cr0x@server:~$ sudo systemctl daemon-reload
cr0x@server:~$ sudo systemctl restart mariadb
cr0x@server:~$ systemctl show mariadb -p LimitNOFILE
LimitNOFILE=65535
Sens : Le service démarre maintenant avec un plafond FD plus élevé.
Décision : Si vous voyez encore EMFILE, ce n’est pas « la limite trop basse » — c’est « la charge consomme trop de FDs ». Continuez le diagnostic.
Task 9: MariaDB—check current open file and table cache settings
cr0x@server:~$ mariadb -e "SHOW VARIABLES WHERE Variable_name IN ('open_files_limit','table_open_cache','table_definition_cache','innodb_open_files');"
+------------------------+--------+
| Variable_name | Value |
+------------------------+--------+
| innodb_open_files | 2000 |
| open_files_limit | 65535 |
| table_definition_cache | 4000 |
| table_open_cache | 8000 |
+------------------------+--------+
Sens : MariaDB est autorisée à ouvrir beaucoup de fichiers, et elle est configurée pour garder beaucoup de tables ouvertes. Cela peut être approprié — ou terriblement optimiste — selon le nombre de tables et la mémoire.
Décision : Comparez à la réalité : nombre de tables/partitions, pattern de charge et usage des FDs. Si vous ouvrez 30k fichiers en état stable, 65k peut être correct ; si vous êtes à 60k et en croissance, repensez l’architecture.
Task 10: MariaDB—estimate table count and partition explosion
cr0x@server:~$ mariadb -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema NOT IN ('mysql','information_schema','performance_schema','sys');"
18432
Sens : Dix-huit mille tables (ou partitions représentées comme tables dans les métadonnées) est beaucoup. Des caches de tables réglés à 8000 peuvent thrasher ou garder des milliers ouverts selon le pattern d’accès.
Décision : Si c’est une stratégie de partitionnement devenue incontrôlée, envisagez de consolider les partitions, d’utiliser moins de schémas, ou de déplacer les données d’archivage hors de la BDD chaude. Si c’est légitime, dimensionnez les limites et les caches délibérément et surveillez.
Task 11: PostgreSQL—check max connections and active sessions
cr0x@server:~$ sudo -u postgres psql -c "SHOW max_connections; SELECT count(*) AS current_sessions FROM pg_stat_activity;"
max_connections
-----------------
800
(1 row)
current_sessions
------------------
742
(1 row)
Sens : Vous êtes près du plafond de connexions configuré. Chaque connexion est un processus. Même si les limites FD sont élevées, c’est un signe de pression sur les ressources.
Décision : Si l’app ouvre des centaines de connexions inactives, mettez en place un pooling (PgBouncer en transaction mode est le choix adulte habituel) et réduisez max_connections à un nombre que vous pouvez supporter.
Task 12: PostgreSQL—check per-backend FD usage quickly
cr0x@server:~$ for p in $(pgrep -u postgres -d ' ' postgres); do printf "%s " "$p"; ls -1 /proc/$p/fd 2>/dev/null | wc -l; done | sort -k2 -n | tail
3188 64
3191 68
3201 71
3210 74
3222 91
Sens : Les backends ne consomment pas individuellement énormément de FDs (quelques dizaines chacun), mais multipliés par 700 sessions vous obtenez tout de même beaucoup de sockets et de handles internes répartis entre les processus.
Décision : Si le postmaster ou un sous-système partagé atteint une limite, augmentez le LimitNOFILE du service. Si le système est globalement surchargé, corrigez d’abord la stratégie de connexions.
Task 13: PostgreSQL—find temp file pressure (spills)
cr0x@server:~$ sudo -u postgres psql -c "SELECT datname, temp_files, temp_bytes FROM pg_stat_database ORDER BY temp_bytes DESC LIMIT 5;"
datname | temp_files | temp_bytes
-----------+------------+--------------
appdb | 18233 | 429496729600
postgres | 0 | 0
template1 | 0 | 0
template0 | 0 | 0
(4 rows)
Sens : Beaucoup de fichiers temporaires et des centaines de Go écrits depuis la réinitialisation des statistiques. Cela corrèle avec le churn FD et les tempêtes d’E/S disque lors de requêtes lourdes.
Décision : Identifiez les requêtes qui provoquent des débordements, ajustez work_mem prudemment et/ou réduisez la concurrence/la parallélisation. Moins de débordements = moins de fichiers temporaires et moins de handles ouverts.
Task 14: See who else is consuming FDs (top processes)
cr0x@server:~$ for p in $(ps -e -o pid=); do n=$(ls -1 /proc/$p/fd 2>/dev/null | wc -l); echo "$n $p"; done | sort -nr | head
18421 1287
2290 1774
1132 987
640 2140
Sens : MariaDB est le principal consommateur de FDs (18421). Le postmaster PostgreSQL est bien plus bas. L’incident est probablement lié à MariaDB, pas à « l’hôte ».
Décision : Concentrez la correction. Si un log shipper ou un proxy est deuxième, inspectez-le aussi — parfois le « problème BDD » vient d’un sidecar défaillant.
Task 15: Check kernel messages for FD-related failures
cr0x@server:~$ sudo dmesg -T | tail -n 10
[Wed Dec 31 02:13:51 2025] mariadbd[1287]: EMFILE: too many open files
[Wed Dec 31 02:13:52 2025] mariadbd[1287]: error opening file ./db1/orders.ibd (errno: 24)
Sens : Confirmation claire : errno 24 (EMFILE). Ce n’est pas une erreur de stockage ; c’est une limite de FD.
Décision : Traitez comme un problème de capacité/configuration. Ne perdez pas de temps sur des contrôles de système de fichiers sauf si vous voyez des erreurs d’E/S.
Trois mini-récits du terrain en entreprise
Mini-récit 1 : L’incident causé par une mauvaise hypothèse
Ils ont migré un monolithe en « services », ont gardé la même base MariaDB, et ont célébré la première semaine de tableaux verts. La nouvelle équipe services avait une habitude : chaque service gardait un pool chaud de connexions « pour la performance ». Personne n’a coordonné ; chacun faisait ce qui fonctionnait localement.
En fin de mois, un job batch a touché un large ensemble de tables. Pendant ce temps, les services faisaient leur chose habituelle — plus des rafales de retries parce que la latence a grimpé. MariaDB a commencé à lancer « Too many open files ». L’ingénieur d’astreinte a supposé que c’était une limite du noyau et a augmenté fs.file-max. L’erreur a continué.
Le véritable limitateur était LimitNOFILE=1024 défini par systemd pour le service MariaDB. Et même après l’avoir augmenté, le serveur est resté dans la zone dangereuse parce que le nombre de connexions avait doublé, faisant monter les sockets FD. La « mauvaise hypothèse » était de croire que le tuning au niveau hôte annulerait les limites au niveau service, et que les pools de connexions sont gratuits.
Ils ont corrigé correctement : définir explicitement LimitNOFILE, dimensionner les caches MariaDB à des valeurs réalistes, et introduire un vrai layer de pooling côté applicatif. Ils ont aussi créé une règle : les tailles de pool doivent être budgétisées comme la mémoire — parce que ça l’est, et aussi comme descripteurs de fichiers.
Mini-récit 2 : L’optimisation qui s’est retournée contre eux
Une autre entreprise utilisait PostgreSQL et subissait un problème de latence chronique lors de requêtes analytiques. Un ingénieur bien intentionné a augmenté les réglages de requête parallèle et ajusté quelques knobs du planner. Le premier benchmark était excellent. Tout le monde a applaudi, discrètement.
Puis la charge réelle est arrivée : de nombreux utilisateurs reporting concurrents, chacun lançant une requête qui débordait sur disque. Les workers parallèles ont multiplié le nombre de créateurs de fichiers temporaires. Les fichiers temporaires ont explosé. L’E/S disque a bondi. Et, oui, l’usage de FDs a augmenté parce que chaque worker ouvrait son propre ensemble de fichiers.
L’échec n’était pas systématique « trop de fichiers ouverts » tout le temps. C’était intermittent : quelques sessions échouant, certaines requêtes qui accrochaient, et l’application qui timeoutait. La timeline de l’incident est devenue un bazar parce que le symptôme ressemblait d’abord à « stockage lent », puis à « mauvais plans », et enfin à « instabilité OS aléatoire ».
L’optimisation a échoué parce qu’elle a augmenté la concurrence au pire endroit : à l’intérieur du moteur, lors d’opérateurs lourds en débordement. La solution a été de réduire la parallélisation, augmenter prudemment work_mem pour le rôle reporting, et imposer des limites de connexion pour ce tiers. Les performances se sont améliorées et les pics FD ont cessé d’être un événement.
Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise
Une équipe avait une norme opérationnelle peu excitante : chaque hôte de base avait un budget FD documenté, avec des alertes à 60% et 80% de la limite effective par service. Ils enregistraient aussi « top FD consumers » comme métrique périodique, pas seulement en cas d’incident.
Ça ressemblait à de la bureaucratie jusqu’à ce qu’une mise à jour d’application tierce déploie un changement subtil : elle ouvrait une nouvelle connexion par requête quand un certain feature flag était activé. Le nombre de connexions a monté progressivement pendant une semaine. Pas encore d’incident — juste une augmentation lente des sockets.
L’alerte 60% s’est déclenchée en heures ouvrables. Ils ont enquêté sans pression, vu la tendance, et tracé la source au feature flag. Ils l’ont rollbacké, puis mis en place PgBouncer et limité la création de connexions dans l’app.
Rien n’a pris feu. Personne n’a dû expliquer une panne évitable à la finance. C’était le rapport d’incident le moins excitant qu’ils aient jamais rédigé, ce qui est le plus grand compliment qu’on puisse faire à une pratique SRE.
La vraie solution : dimensionnement, limites et réglages qui comptent vraiment
« Augmenter ulimit » est l’aspirine. Parfois il faut de l’aspirine. Mais si vous prenez de l’aspirine tous les jours, vous ne traitez pas la maladie.
Étape 1 : Fixer des limites OS/service sensées (bonne couche, persistance correcte)
Pour les déploiements Linux modernes, la vérité est : systemd est la source de la réalité pour les services. Définissez LimitNOFILE dans un override drop-in pour le service de base. Vérifiez après redémarrage via /proc/<pid>/limits.
Choisissez un nombre intentionnel :
- Petits serveurs (instance unique, schéma modéré) : 65535 est une base courante.
- Gros MariaDB avec beaucoup de tables/partitions ou forte concurrence : 131072+ peut être raisonnable.
- PostgreSQL avec pooling et connexions contrôlées : vous n’avez peut-être pas besoin de valeurs énormes, mais ne laissez pas à 1024. C’est une auto-sabotage.
Aussi : évitez de mettre « infini » parce que vous le pouvez. Chaque FD a un coût noyau. Des limites énormes cachent les fuites jusqu’à ce qu’elles deviennent des catastrophes.
Étape 2 : Réduire la demande réelle en FDs
Voici où MariaDB et PostgreSQL divergent en pratique.
MariaDB : arrêtez d’accumuler les tables comme en 2009
MariaDB peut garder des milliers de tables ouvertes si vous le lui dites. Si votre schéma contient des dizaines de milliers de tables/partitions, « garder beaucoup ouvert » devient un risque structurel.
Que faire :
- Redimensionnez
table_open_cacheettable_definition_cache. Plus grand n’est pas toujours mieux. Si vous n’avez pas assez de mémoire pour garder les métadonnées et handlers chauds, vous ne ferez que thrash différemment. - Alignez
open_files_limitetinnodb_open_files. Ne laissez pas l’un petit et l’autre énorme. C’est ainsi que naît une confiance trompeuse du type « ça devrait marcher ». - Surveillez l’explosion des partitions. Des milliers de partitions sont pratiques jusqu’à ce qu’elles deviennent un problème de descripteurs et de planification de requêtes.
PostgreSQL : corrigez d’abord les connexions, puis les débordements
Le gain le plus simple pour PostgreSQL n’est pas un réglage FD. C’est le pooling de connexions. Si vous avez des centaines à milliers de sessions clientes directement contre Postgres, vous traitez la base comme un serveur web. Elle ne l’est pas.
Que faire :
- Utilisez un pooler (PgBouncer est le choix courant) et réduisez
max_connectionsà un nombre soutenable. - Corrigez les tempêtes de retries. Si les clients se reconnectent agressivement sur des erreurs transitoires, ils peuvent créer des rafales de sockets qui poussent les FDs au‑delà de la limite.
- Réduisez les débordements temporaires. Les débordements créent des fichiers temporaires ; ces fichiers consument des FDs pendant leur durée de vie. Tunez la mémoire par classe de charge et réduisez la fan‑out des workers parallèles si cela crée plus de débordements concurrents que vous ne pouvez gérer.
Blague #2 : Mettre LimitNOFILE à un million, c’est comme acheter un placard plus grand au lieu de jeter votre collection de goodies de conférence.
Étape 3 : Vérifier que vous n’avez pas simplement déplacé le goulot d’étranglement
Après avoir augmenté les limites FD et réduit la demande, vérifiez les modes d’échec suivants :
- Pression mémoire : plus de connexions et de caches signifient plus de RSS. Surveillez le swap comme une chouette ; swapper une base de données est du cosplay de performance.
- CPU et changements de contexte : trop de backends PostgreSQL peuvent faire fondre le CPU sans qu’une seule requête soit « mauvaise ».
- Disque et usage d’inodes : une forte utilisation de fichiers temporaires peut consommer rapidement inodes et espace disque, surtout sur de petites partitions racine.
- Limites noyau au‑delà de nofile : max processes, limite pids des cgroups, épuisement des ports éphémères (côté client), et réglages de backlog réseau.
Erreurs fréquentes : symptôme → cause racine → correctif
Cette section est volontairement directe. La plupart des incidents EMFILE sont auto-infligés, juste pas par la personne qui tient actuellement le pager.
Mistake 1: « Nous avons augmenté fs.file-max, pourquoi ça n’a pas marché ? »
Symptôme : « Too many open files » persiste après l’augmentation de /proc/sys/fs/file-max.
Cause racine : La limite par processus/service (RLIMIT_NOFILE) est toujours basse, souvent définie par systemd.
Fix : Définissez LimitNOFILE dans l’override de l’unité systemd, redémarrez la BDD, validez via /proc/<pid>/limits.
Mistake 2: « Nous avons mis ulimit dans /etc/security/limits.conf ; toujours cassé »
Symptôme : Les sessions shell montrent un ulimit -n élevé, mais le service ne l’a pas.
Cause racine : Les limites PAM s’appliquent aux sessions de login ; les services systemd ne les héritent pas de la même façon.
Fix : Configurez le service systemd. Traitez les limites PAM comme pertinentes pour les sessions interactives, pas pour les daemons.
Mistake 3: « Nous avons augmenté table_open_cache ; maintenant on a EMFILE » (MariaDB)
Symptôme : MariaDB affiche des erreurs en ouvrant des tables ; les logs montrent errno 24 ; le compte FD continue d’augmenter.
Cause racine : Cache de tables trop grand pour le schéma/la charge ; le serveur tente de garder trop de handlers de tables ouverts.
Fix : Réduisez table_open_cache à une valeur mesurée, augmentez LimitNOFILE pour correspondre aux besoins réalistes, et traitez le nombre de tables/partitions.
Mistake 4: « Postgres peut gérer 2000 connexions, c’est ok »
Symptôme : Échecs de connexion aléatoires, forte charge, parfois EMFILE, parfois juste des timeouts.
Cause racine : Trop de processus backend ; l’usage de FDs et la surcharge mémoire augmentent avec les sessions ; des pics poussent les limites.
Fix : Ajoutez du pooling, réduisez max_connections, et imposez des budgets de connexion par service.
Mistake 5: « La BDD fuit des FDs » (quand c’est en réalité des temp file storms)
Symptôme : Le nombre de FDs monte lors de certaines requêtes/batches, puis redescend plus tard.
Cause racine : Les fichiers temporaires et la parallélisation créent des pics transitoires de FDs.
Fix : Identifiez les requêtes à débordement ; ajustez mémoire/parallélisme ; planifiez les batches ; limitez la concurrence.
Mistake 6: « C’est le stockage » (quand c’est en réalité des descripteurs)
Symptôme : Les requêtes échouent à ouvrir des fichiers ; on suspecte la corruption ou des disques lents.
Cause racine : errno 24 (EMFILE) n’est pas une erreur d’E/S ; c’est une limite de FD.
Fix : Confirmez errno via les logs/dmesg ; vérifiez /proc limits ; ajustez les réglages du service et de la base.
Mistake 7: « On a réparé en redémarrant »
Symptôme : Le redémarrage résout temporairement le problème ; il revient sous charge.
Cause racine : Le redémarrage réinitialise l’usage des FDs et les caches ; la demande sous-jacente est inchangée.
Fix : Faites le travail de dimensionnement : limites + stratégie de connexions + sanity des caches de tables/schémas + monitoring.
Listes de contrôle / plan pas à pas
Checklist A: Stabilisation d’urgence (15–30 minutes)
- Confirmez s’il s’agit de MariaDB ou PostgreSQL qui lance EMFILE (logs + PID).
- Vérifiez
/proc/<pid>/limitspour Max open files. - Comptez les FDs ouverts :
ls /proc/<pid>/fd | wc -l. - Classifiez les types de FDs : sockets vs fichiers de tables vs fichiers temporaires.
- Si la limite du service est basse, appliquez un override systemd (
LimitNOFILE), planifiez un redémarrage contrôlé. - Freinez : réduisez la concurrence des workers applicatifs, mettez en pause les jobs batch lourds et désactivez les tempêtes de retries si possible.
- Après le redémarrage, validez que la limite est appliquée et que l’usage des FDs se stabilise en dessous de 60% de la limite.
Checklist B: Cause racine et correctif durable (même jour)
- Documentez l’usage des FDs à l’état idle, au pic normal et au pire pic.
- Pour MariaDB : inventaire des tables/partitions ; révision de
table_open_cache,open_files_limit,innodb_open_files. - Pour PostgreSQL : mesurez les connexions dans le temps ; identifiez quels clients créent le plus de sessions ; déployez du pooling.
- Vérifiez les statistiques de fichiers temporaires et les requêtes lentes ; corrélez les pics FD avec les calendriers de batch.
- Mettez en place des alertes sur l’usage des FDs par PID BDD et sur les comptes de connexions.
- Réalisez un test de charge contrôlé pour valider la solution sous une concurrence et un footprint de schéma réalistes.
Checklist C: Prévention (ici gagnent les adultes)
- Créez un budget de descripteurs par environnement : dev, staging, production.
- Faites respecter des budgets de connexion par service. Aucune exception sans revue.
- Suivez la croissance du schéma (tables, partitions, indexes) comme métrique de capacité de première classe.
- Intégrez les overrides systemd dans la gestion de configuration, pas dans le savoir tribal.
- Testez le basculement et le comportement au redémarrage avec vos limites choisies pour garantir une récupération rapide.
FAQ
1) Est‑ce que « Too many open files » est toujours la faute de la base de données ?
Non. C’est souvent déclenché par la BDD, mais ça peut être un proxy (comme HAProxy), un log shipper, un agent de sauvegarde, ou même le serveur applicatif qui épuise ses propres FDs et le signale mal.
2) Quelle est la différence entre EMFILE et ENFILE ?
EMFILE signifie que le processus a atteint sa limite FD par processus. ENFILE signifie que le système a atteint sa limite globale de handles de fichiers. La plupart des incidents BDD sont des EMFILE.
3) Pourquoi systemd ignore mes changements dans /etc/security/limits.conf ?
Les limites PAM s’appliquent généralement aux sessions de login. Les services systemd utilisent leurs propres limites sauf configuration contraire. Corrigez l’unité avec LimitNOFILE.
4) Quelle est une valeur raisonnable de LimitNOFILE pour MariaDB ?
Commencez par 65535 si vous ne savez pas. Puis dimensionnez en fonction : connexions (sockets), tables/partitions ouvertes, fichiers temporaires et FDs auxiliaires. Si vous avez d’énormes nombres de partitions, vous pourriez avoir besoin de 131072 ou plus — mais demandez-vous pourquoi vous avez autant de partitions.
5) Quelle est une valeur raisonnable de LimitNOFILE pour PostgreSQL ?
Souvent 65535 suffit comme base. Le vrai gain est de contrôler les connexions et réduire les temp-file storms. Si vous avez besoin d’un nombre massif de FDs pour Postgres, vous avez probablement une concurrence non contrôlée ou un churn extrême de relations.
6) Puis‑je juste augmenter max_connections pour corriger les erreurs de connexion ?
Vous pouvez, mais c’est ainsi que vous échangez « connexion refusée » contre « serveur en feu ». Pour PostgreSQL, utilisez le pooling et gardez max_connections dans une fourchette que votre mémoire et CPU peuvent supporter.
7) Pourquoi je vois beaucoup de sockets dans les listes de FDs ?
Parce que chaque connexion cliente est une socket FD. Si les sockets dominent, concentrez‑vous sur le nombre de connexions, le pooling et le comportement de retry. Si ce sont des fichiers réguliers qui dominent, concentrez‑vous sur le comportement du cache de tables, le footprint du schéma et le churn des fichiers temporaires.
8) Augmenter les limites FD a‑t‑il des inconvénients ?
Oui. Des limites plus élevées facilitent la tâche d’une fuite ou d’une charge runaway pour consommer plus de ressources noyau avant d’échouer. Vous échouerez plus tard, potentiellement plus sévèrement, et le rayon d’impact peut augmenter. Augmentez les limites, mais réduisez aussi la demande et surveillez.
9) Comment savoir si c’est une fuite ou un pic ?
Si l’usage des FDs monte régulièrement et ne redescend pas après la baisse de charge, suspectez une fuite ou un comportement de cache qui garde tout ouvert indéfiniment. Si ça pique pendant un batch ou un surge de trafic puis revient à la normale, c’est un pic de capacité/concurrence.
10) Les partitions comptent-elles vraiment pour les FDs ?
Oui. Dans les deux écosystèmes, les partitions augmentent le nombre d’objets semblables à des relations. Plus d’objets peut signifier plus de métadonnées, plus de handles ouverts et plus d’overhead de planification/maintenance. Le partitionnement est un outil, pas une personnalité.
Prochaines étapes pratiques
Si vous êtes en plein incident : appliquez le mode d’intervention rapide, corrigez la limite FD au niveau du service, et freinez la concurrence. Cela vous donne de l’air.
Puis faites le travail d’adulte :
- Mesurez l’usage des FDs par type (sockets vs fichiers) et par état steady-state vs pic.
- MariaDB : redimensionnez les caches de tables et confrontez la croissance du schéma/partitionnement ; alignez
open_files_limitetinnodb_open_filesavec les limites OS. - PostgreSQL : mettez en place du pooling, réduisez
max_connections, et limitez les débordements temporaires en ajustant mémoire/parallélisme et en corrigeant les pires requêtes. - Surveillez l’usage des FDs et mettez des alertes avant d’atteindre le précipice. Le précipice n’est pas une opportunité d’apprentissage ; c’est un générateur d’indisponibilité.