img

Je traîne un souci depuis un bon moment maintenant : j’ai quelques conteneurs LXC avec une base de données MariaDB. Ce n’est pas la configuration par défaut, mais c’est une configuration que je porte de machine en machine depuis quelques années et qui n’est pas forcément spécifiquement faite pour faire de la performance.

Mais là n’est pas (encore) la question. De temps en temps, de manière apparemment aléatoire, MariaDB se prend un oom-kill par le système, plante et évidemment ne redémarre pas tout seul. Dans une configuration un peu ric-rac en terme de mémoire (on parle de conteneur LXC qui démarre par défaut en utilisant un peu moins de 10Mio de RAM), c’est quand même assez étonnant.

D’autant plus que de prime abord, tout va bien : l’hyperviseur donne des consommations de RAM (en moyenne comme en pic) tout-à-fait raisonnable, pareil pour les autres métriques ramassées autrement.

Il semblerait donc bien que ce soit un problème extrêmement ponctuel et à moins d’avoir le nez dessus quand ça arrive, ça risque d’être relativement compliqué à diagnostiquer.

Ce qui m’amène donc au sujet du jour : comment MariaDB gère-t-il la mémoire ? Y’a-t-il un minimum ? Un maximum ? Quelles valeurs de configuration peut-on manipuler pour arriver à le limiter sans qu’il se prenne les pieds dans le tapis ?

Le pool commun de mémoire

Il va falloir distinguer deux types de consommation mémoire pour MariaDB. Je vais partir du principe qu’on va pour l’instant rester sur la configuration « par défaut » sans chercher à faire quoique ce soit.

Tu le sors de la boîte donc, tu le poses sur le système, tu le démarres et bim ! 200 Mio de mémoire prise alors que t’as rien fait de particulier.

Alors, il y a peut-être (probablement même), d’autres paramètres, mais en gros, la mémoire de ce bout-là va être décomposé comme suit :

  • le key_buffer_size (utilisé par MyISAM) prend 128Mio de mémoire par défaut
  • le query_cache (il y a plusieurs variables pour le gérer, elles commencent toutes par query_cache_*) prend 1Mio
  • les différents cache d’InnoDB innodb_buffer_pool_size et innodb_log_buffer_size occupent respectivement 128Mio et 16Mio

Bien évidemment, par défaut, je suppose que ces caches sont complètement vides mais ça donne une petite idée de la consommation « à vide » du serveur MariaDB. Il n’ira jamais en dessous de ces valeurs, à moins qu’on ne l’y force.

Premières constations donc :

  • sans aucun ajustement, c’est, en théorie, 273Mio incompressible
  • si l’on a pas du tout de tables InnoDB ou pas du tout de table MyISAM, on peut commencer à bricoler

La documentation officielle nous dit :

  • aucune table InnoDB -> régler key_buffer_size à 20% de la RAM disponible et mettre innodb_buffer_pool_size à 0
  • aucune table MyISAM -> régler innodb_buffer_pool_size à 70% de la RAM disponible et mettre key_buffer_size à une faible valeur mais pas en dessous de 10M

Ça donne déjà une bonne idée de ce que l’on peut faire en auto-hébergement : si l’on peut se passer de l’un ou l’autre des types de base, ça simplifie déjà pas mal la configuration. Tu peux économiser immédiatement entre 118 et 128Mio de RAM en faisant juste ça !

Comme tu peux t’en douter, je me suis donc empressé de convertir toutes les tables de toutes les DB de tous les conteneurs en InnoDB et j’ai rapidement déployé une configuration plus optimale. Ça permet de souffler, mais tu vas voir que ce n’est malheureusement pas tout.

La mémoire par connexion

En effet, MariaDB va aussi allouer de la mémoire à chaque nouvelle connexion. Et quand tu as essentiellement des applis en PHP, tu peux considérer grosso-modo que chaque client qui va aller faire un rendu sur une page, va aller générer une connexion côté base de données (plusieurs si le code n’est pas bien optimisé, mais c’est un autre souci).

Avec notre configuration par défaut, MariaDB a un maximum de 151 connexions (150 configurées + 1 permanente pour root). Mais combien de mémoire exactement consomme une connexion ?

Cela va dépendre essentiellement de quelques paramètres de buffer et de cache attribués à chaque connexion :

  • {sort,read,read_rnd,join}_buffer_size, pour un total de 2,625Mio
  • thread_stack, 0,285Mio (c’est la mémoire allouée à chaque thread)
  • binlog_cache_size, 0.031Mio
  • tmp_table_size prend à lui seul 16Mio

Donc en fait, avec la configuration par défaut, MariaDB va attribuer un peu moins de 20Mio par connexion. Si l’on reprend les 151 connexions par défaut, ça veut dire qu’on peut monter jusqu’à 3Gio tout carrossés.

Bon évidemment, c’est le pire scénario possible. On peut bien évidemment supposer que cela n’arrivera jamais, mais cela reste un potentiel.

On se rend aussi rapidement compte que la principale consommation de mémoire ne vient donc pas du tout des caches et de la configuration de base de MariaDB (ou alors de manière assez marginale) mais essentiellement des connexions, et particulièrement la variable tmp_table_size.

Se pose alors la question : c’est quoi bordel tmp_table_size ? En fait, cela correspond à la taille maximale que MariaDB va allouer en mémoire pour former des tables temporaires (lorsque l’on fait des jointures ou des groupements par exemple). Si cette valeur est dépassée pour une raison ou pour une autre, MariaDB allouera une table temporaire sur le disque (avec ce que l’on peut imaginer comme pénalité pour les performances).

J’ignore si l’on peut vraiment jouer sur ce paramètre pour essayer de gratter de la RAM. Il y a une variable MariaDB qui garde le nombre de tables temporaires formées en mémoire et le débordement correspondant sur disque :

SHOW STATUS LIKE 'Created_tmp_%tables';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| Created_tmp_disk_tables | 0     |
| Created_tmp_tables      | 0     |
+-------------------------+-------+

Pour l’instant et comme illustré ci-dessus, j’y ai toujours vu 0. Je ne sais donc pas trop quoi en conclure (peut-être qu’on pourrait réduire cette valeur à 1Mio, peut-être pas…).

Par contre, je sais que l’on peut regarder la quantité de connexion que subit MariaDB sur une certaine période et la limiter. Dans le pire des cas, le client aura une erreur comme quoi le nombre de connexions est dépassé, mais au moins, ça ne plantera pas tout le process. Et en plus l’abaque est relativement simple : environ 20Mio/connexion.

Mais comment fait-on pour connaître le nombre maximal de connexions ? Et bien, il suffit de regarder dans le statut correspondant :

SHOW STATUS WHERE Variable_name='Max_used_connections';

Ça te donnera une idée de l’usage depuis le dernier redémarrage. Ce n’est évidemment pas parfait puisque si le serveur plante à cause d’un surplus de connexion, ça ne t’aidera pas beaucoup. Néanmoins, ça peut permettre d’ajuster la quantité de RAM vers le haut ou au contraire vers le bas.

Conclusage

Bon ben finalement, c’était pas si compliqué : il y a une certain quantité de mémoire qui est attribué à l’ensemble des processus MariaDB (273Mio par défaut que l’on peut assez facilement réduire en unifiant les moteurs de DB utilisés) et il y a la mémoire utilisée pour chacune des connexions.

On peut donc pré-calculer la quantité de RAM nécessaires en fonction du nombre de connexions. À l’évidence un serveur avec beaucoup de trafics et relativement peu de caches de page va multiplier les connexions à la DB et donc consommer bien plus de RAM. Dans le cas d’un serveur non-dédié (avec un NginX, un PHP, etc… en plus dessus),il faut bien évidemment prendre en compte le fait que les autres services vont aussi consommer de la RAM.

Mais voilà, avec ces premiers chiffres en tête, ça peut donner une petite idée de la quantité maximale théorique que va consommer MariaDB et ça peut permettre d’ajuster le cas échéant.