Mysql et MariaDB

Mysql et MariaDB jpp

J'ai rajouté un "chapeau" à d'anciens articles sur Mysql/PariaDB "Maître/esclave" en faisant une version plus "moderne" sur le même sujet : création d'un cluster MariaDB "Maître/Esclave", mais en allant un peu plus loin.
Note : la plupart des éléments cités dans ces articles s'appliquent aussi bien à MariaDB qu'à Mysql proprement dit.

Bonne lecture ... et bons tests.

Un petit test de vitesse comparée entre HDD et SSD ici.

Mysql maitre / esclave (2013)

Mysql maitre / esclave (2013) jpp

Article original de 2013.

Cette notion de maître / esclave permet diverses améliorations dont la principale est la sécurité, mais on peut y ajouter aussi des améliorations de performance qui seront précisées dans un autre article.

Dans cet exemple on part de deux machines (virtuelles) identiques avec des bases Mysql « vierges » . 
Il faut d'abord vérifier la présence du paquet "libaio1" et dans un premier temps créer les mêmes utilisateurs initiaux avec les mêmes droits. 
On passe ensuite aux opérations "sérieuses" :

Sur l'esclave modifier le fichier « my.cnf » et modifier le « server-id », 'jamais deux serveurs de même ID dans le même réseau) : 
Ancien  : server-id = 1 
Nouveau : server-id = 2

Redémarrer le service.

Sur le maître : 
Vérifier dans le fichier "my.cnf" que le « binlog » est bien activé  (Ligne dé-commentée) : 
log_bin = /var/log/mysql/mysql-bin.log

Redémarrer le service.

Sur la machine maître créer un utilisateur spécifique pour la réplication :

CREATE USER 'replicator'@'%' IDENTIFIED BY 'replicateur'; 
GRANT REPLICATION SLAVE ON *.* TO 'replicator'@'%';

Vérifier la connexion de l'esclave vers le maître avec votre utilisateur de réplication avec la commande suivante (sur l'esclave) : 
mysql –host=maitre –user=replicator –password=replicateur

qui doit fournir une connexion correcte.

On peut alors, toujours sur l'esclave, le « lier » à son maître :

CHANGE MASTER TO MASTER_HOST='mysql1', MASTER_USER='replicator',

MASTER_PASSWORD='replicateur'; START SLAVE;

1) Les premiers tests (simples).

- Vérifier le fonctionnement sur l'esclave :

SHOW SLAVE STATUS;

qui doit vous retourner un rang donnant le "point" de la synchronisation notamment le numéro du fichier "mysql-bin" en cours et d'autres valeurs de la position courante.

- Créer une DATABASE.

Sur le maître créer une base :

CREATE DATABASE VALID ;

Sur l'esclave vérifier l'existence de la base :

USE DATABASE VALID ;

Une fois ce test passé on peut continuer avec

- Créer table :

Sur le maître on crée une table dans notre DB VALID :

CREATE TABLE TOTO ( zone char(6)) ;

On vérifie sur l'esclave :

DESC TOTO ;

Qui doit vous retourner la description de la table TOTO.

On peut ensuite, pour faire bien, insérer des choses dans notre table sur le maître.

insert into TOTO ( zone) values ( "un" ); 
insert into TOTO ( zone) values ( "deux" );

Dans notre base esclave :

select * from TOTO; 
retourne :

+------+ 
| zone | 
+------+ 
| un   | 
| deux | 
+------+ 
2 rows in set (0.00 sec)

Ce qui est exceptionnel !!

2) Des tests plus évolués.

Que se passe-t-il maintenant si : 
- on stoppe l'esclave 
- on fait une insertion sur le maître :

insert into TOTO (zone) values (« trois »).

- on redémarre l'esclave et on effectue le « select » précédent , miracle il nous retourne bien : 
+-------+ 
|  zone | 
+-------+ 
| un    | 
| deux  | 
| trois | 
+-------+ 
3 rows in set (0.00 sec)

3) Remarques finales.

La procédure est ici très simple avec du matériel simple (virtuel ! ), dans la réalité il faudrait prévoir :

  • deux liens réseau dont un spécifique à la réplication et qui serait un plus pour limiter au maximum la latence du système.
  • de se servir de la base répliquée pour faire des sauvegardes tranquillement, sana perturber la machine "maître".
  • de se servir de la machine "esclave"  pour lancer de "gros" ordres SQL de statistiques qui perturberaient plus ou moins fortement le serveur principal.

Il reste très simple d'effectuer cette mise en réplication et cela est assez performant.

Mysql: 5.6.30,5.7.16,SSD

Mysql: 5.6.30,5.7.16,SSD jpp

Ayant un petit serveur "libre", le malheureux chauffait tellement qu'il se plantait régulièrement et que j'ai du le remplacer vite fait (voir article). Lorsque je l'ai démonté pour voir ce qu'il avait dans le ventre j'ai touché le radiateur et j'ai entendu un petit "clic", depuis cet engin ne chauffe plus ! J'ai donc décidé de l'utiliser avec quelques "vieux" SSD qui traînaient dans un coin pour faire quelques tests avec Mysql. 
J'ai utilisé une base disponible sur une autre machine et qui possède deux tables un peu volumineuses : 10 millions de rangs pour l'une et un peu plus de 100 000 pour l'autre, le tout "pesant" environ 4GO. 
La machine d'origine que j'ai prise pour référence possède : 
CPU        CoreI5 à 3.4Ghz série 5 
RAM       16 Go 
Disques  2 disques 1To à 7200 tours en Raid 1 
Mysql      5.6.30 
La machine "ressuscitée" dispose de : 
CPU        CoreI3 à 3.4GHz série 4 
RAM        16 Go 
Disques   3 SSD assez anciens mais encore vaillants de 32 à 128Go. 
Mysql      5.6.30 puis 5.7.16 
J'ai essayé 2 configurations : 
Standard, tout sur le même disque (SSD), répertoires /var/lib/mysql et /var/log/mysql 
Optimisée système sur un disque, DATA (ex /var/lib/mysql) sur un SSD et LOG (ex /var/log/mysql) sur un autre SSD, bien qu'avec des ordres "SELECT" le log ne soit pas sollicité. 
Remarques : Toutes les tables sont en InnoDB et la valeur de "innodb_buffer_size" est fixée à 2048M. 

La suite bientôt ... c'est fait et c'est ICI. 
 

Mysql : Maitre/esclave (2016)

Mysql : Maitre/esclave (2016) jpp

Mysql : mise en cluster maître/esclave. 
Vous disposez d'une belle application fonctionnant sur une non moins belle base de données Mysql qui contient tout ce qui est nécessaire à votre application. 
Mais que se passera-t-il si votre base est indisponible (crash base, disque ayant rendu l'âme, prise de pied dans la prise de courant ....) ? 
Votre belle application ne fonctionnera plus du tout ... 
La solution : Clusteriser votre base de données en mode maître/esclave. 
Oui, mais ma base existe déjà, comment faire ? Je n'ai pas envie de "dumper" toutes mes tables .... !!! et de les recharger. 
C'est cette histoire que je vais vous conter. 
Vous ne touchez pas à votre serveur original (il marche, ne pas le déranger), et vous préparez une merveilleuse machine, identique pour bien faire, et faites-y une installation complète de Mysql de préférence dans la même version, ici c'est du 5.6 ! Cela marche aussi très bien avec MariaDB. 
Si la structure des répertoires de DATA n'est pas "standard" il vaut mieux refaire à l'identique, cela simplifiera toute la maintenance. 
Un peu de vocabulaire pour la suite :

  • Serveur1 : votre serveur "actuel"
  • Serveur2 : le petit nouveau.

L'opération consiste à :

  • Créer les utilisateurs nécessaires à la réplication (Serveur1).
  • Stopper Mysql sur le Serveur1
  • Sauvegarder l'intégralité des fichiers de la BDD, si vous utilisez LVM (ou BTRFS) vous pouvez faire un snapshot du support disque de votre base, et la relancer immédiatement. Sinon "tar" fonctionne encore ...
  • Modifier le fichier de config de Serveur1
  • Stopper Mysql sur Serveur2, et bien nettoyer les répertoires de Mysql, y compris les fichiers de "/var/log/mysql"
  • Restaurer les données de Serveur1 sur Serveur2.
  • Détruire un petit fichier sur Serveur2 (/var/lib/mysql/auto.cnf)
  • Reparamétrer Serveur2 en esclave, et en "read-only", c'est plus prudent.
  • Redémarrer Serveur2

Note : Il existe des moyens de faire une sauvegarde "on line" ce qui évite de stopper le serveur, mais dans ce cas il faut enregistrer la position dans le "binlog" pour permettre à l'esclave de repartir juste après la dernière transaction enregistrée.

Vous pouvez alors passer Serveur2 en mode "esclave" et Serveur2 sera maintenu à jour par rapport à Serveur1.

Un peu de détail pour arriver à ce Nirvana : 
1) Créer les utilisateurs pour la réplication sur Serveur1 :

* 
    Users pour replication 
*/ 
create user         'replic_usr'@'mysql1'       identified by 'Mon_Secret'; 
grant all on *.* to     'replic_usr'@'mysql1'       with grant option;

create user         'replic_usr'@'192.168.2.62' identified by 'Mon_Secret'; 
grant all on *.* to     'replic_usr'@'192.168.2.62' with grant option; 
/* 
*/ 
create user         'replic_usr'@'mysql2'       identified by 'Mon_Secret'; 
grant all on *.* to     'replic_usr'@'mysql2'       with grant option;

create user         'replic_usr'@'192.168.2.63' identified by 'Mon_Secret'; 
grant all on *.* to     'replic_usr'@'192.168.2.63' with grant option;

Note : 
Vous avez un Mysql installé sur Serveur2, vérifiez que le user "replic_usr" peut bien se connecter sur Serveur1 : 
mysql -user=replic_usr --password=Mon_Secret -D mysql 
Si tout va bien vous pouvez alors stopper Mysql sur Serveur2. 
2) Une fois ceci réalisé stopper Mysql sur serveur 1. 
Sauvegardez /var/lib/mysql et ici, en plus /DATA/D1 et /DATA/D2 pour compliquer un peu.

3) Pendant ce temps modifier votre fichier "mysqld.cnf" et ajoutez-y  :

### pour Master 
server-id               = 1 
log-bin                 = mysql-bin 
binlog-format           = MIXED 
log-slave-updates       = true 
gtid-mode               = on 
enforce-gtid-consistency= on 
master-info-repository  = table 
report-host             = mysql1

4) Stoppez Mysql sur Serveur2. 
5) Transportez vos copies sur Serveur2 et restaurez les "en place". 
6) Supprimer le fichier "auto.cnf" dans /var/lib/mysql. 
6) Modifiez le fichier "mysqld.cnf", comme pour Serveur1 sauf "server-id" et "report-host":

### pour Slave   
server-id                = 2 
read-only                = 1 
log-bin                  = mysql-bin 
binlog-format            = MIXED 
log-slave-updates        = true 
gtid-mode                = on 
enforce-gtid-consistency = 1 
master-info-repository   = table 
report-host              = mysql2

7) C'est presque fini : 
Redémarrez Mysql sur Serveur1 et verifiez dans le fichier "error.log" la présence du message : 
/usr/sbin/mysqld: ready for connections. 
Passons maintenant au Serveur2 : 
Nettoyer certains fichier que vous venez de restaurer dans /var/lib/mysql : 
- mysql-bin* 
Son fichier de configuration est correct, bien vérifier que le fichier "auto.cnf" (il contient un UUID) "/var/lib/mysql/auto.cnf" est effacé avant de redémarrer le serveur qui va s'empresser de le recréer avec un nouvel "UUID" différent de celui de Serveur1. 
On peut alors "asservir" notre futur esclave :

mysql -u root -p -D Le_mot_de_passe_qui_va_bien 
change MASTER  to MASTER_USER='replic_usr', 
    MASTER_PASSWORD='Mon_Secret', 
    MASTER_HOST='192.168.2.62';

\q

Qui devrait répondre gentiment : 
Query OK, 0 rows affected, 2 warnings (0,31 sec) 
Puis : 
start slave; 
qui répond (gentiment lui aussi) : 
Query OK, 0 rows affected (0,11 sec)

Verifications : 
Nos données sont-elles toujours sur le maître :

mysql> show tables; 
+--------------------+ 
| Tables_in_test_del | 
+--------------------+ 
| ANAL_FIR_ARC       | 
| ANAL_FIR_TEST      | 
| TEST_LOG           | 
+--------------------+ 
3 rows in set (0,00 sec)

Et sur l'esclave ?

mysql> show tables; 
+--------------------+ 
| Tables_in_test_del | 
+--------------------+ 
| ANAL_FIR_ARC       | 
| ANAL_FIR_TEST      | 
| TEST_LOG           | 
+--------------------+ 
3 rows in set (0,00 sec)

Rien ne s'est perdu en route, nos petites données sont maintenant en sécurité ! 
Maintenant créons une table sur le maître :

create table toto ( zone varchar(32) ); 
Query OK, 0 rows affected (0,36 sec) 
mysql> show tables; 
+--------------------+ 
| Tables_in_test_del | 
+--------------------+ 
| ANAL_FIR_ARC       | 
| ANAL_FIR_TEST      | 
| TEST_LOG           | 
| toto               | 
+--------------------+ 
4 rows in set (0,00 sec)

Et sur l'esclave :

mysql> sow tables; 
+--------------------+ 
| Tables_in_test_del | 
+--------------------+ 
| ANAL_FIR_ARC       | 
| ANAL_FIR_TEST      | 
| TEST_LOG           | 
| toto               | 
+--------------------+ 
4 rows in set (0,00 sec)

Allez on s'en refait un coup, sur le maître :

mysql> insert into toto (zone) values ('Ah oui, comme cela est bon'); 
commit;

Et sur l'esclave :

mysql> select * from toto; 
+----------------------------+ 
| zone                       | 
+----------------------------+ 
| Ah oui, comme cela est bon | 
+----------------------------+ 
1 row in set (0,00 sec)


A bientôt pour la suite de ces passionnantes aventures dans le monde de Mysql.

MariaDB : maître maître

MariaDB : maître maître jpp

J'ai décidé d'installer deux serveurs MariaDB (cela ne change que peu de Mysql) pour tester une configuration multi-maîtres. Pour cela il faut d'abord installer deux machines (virtuelles) identiques. J'ai choisi d'installer une Debian "Stretch" qui devrait prochainement passer en "stable". 
Description des machines :

  • CPU        2
  • RAM        4096GB
  • Disque système 16GB    ext4
  • Disque DATA    32GB    LVM, partition "VGDATA/MYSQL" de 24GB montée sur /DATA/mysql.

Il restera ainsi 8GB pour effectuer des "snapshots" destinés à la sauvegarde (article à suivre). 
Pour faciliter l'installation (IP et nom viennent "automagiquement) j'ai créé ces machines dans le serveur dhcp et je les ai insérées dans mon serveur DNS. 
Installation des machines à partir d'une image de cd "debian-testing-amd64-xfce-CD-1.iso". 
Pas de serveur WEB, serveur SSH plus quelques paquets indispensables. 
apt-get install system-config-lvm iproute2 net-tools vim-gtk 
et enlevé "network-manager" que je n'apprécie pas, je préfère le bon vieux fichier "/etc/network/interfaces" et gérer moi même /"etc/resolv.conf". 
Ensuite j'ai installé MariaDB client et serveur en "standard" (je précise la version 10.1) : 
apt-get install mariadb-client-10.1 mariadb-server-10.1 

Quelques modifications dans le fichier /etc/mysql/mysqld.conf.d/50-server.cnf : 
Mettre la BDD "en ligne" : 
bind-address                   = 0.0.0.0 
Dans le "bloc" InnoDB (par exemple) ajout de : 
innodb-buffer-pool-size         = 1024M 
innodb_buffer_pool_instances    = 2 
innodb-file-per-table           = 1 
innodb_additional_mem_pool_size = 8M 
innodb_log_file_size            = 512M 
innodb_log_buffer_size          = 8M 
innodb_read_io_threads          = 2 
innodb_write_io_threads         = 2 
innodb_stats_persistent         = 1 
innodb_stats_persistent_sample_pages = 512

Positionner la variable "server-id" à 1 pour le premier serveur et = 2 pour le second.

Ajouter dans le bloc "[mariadb-10.1]" : 
innodb_flush_log_at_trx_commit = 2 
event_scheduler                = on 
local_infile                   = 0 
plugin-load=validate_password.so 
skip-symbolic-links            = 1 
innodb_flush_method            = O_DIRECT 
binlog_row_image               = minimal 
# 
concurrent_insert              = AUTO 
sync_binlog                    = 1 
innodb_use_native_aio          = 1 
Sans oublier d'activer le "bin-log" nécessaire au maître 
log_bin                        = /var/log/mysql/mysql-bin.log 
# et le format de log, je préfère "mixed" à "row" ou "statement" 
binlog_format                  = mixed 

Et pour terminer et éviter des problèmes dus aux tables avec identifiant en "auto-increment" : 
Mettre chacune des deux machines (ou plus) sur des séries différentes. Ici l'une générera des identifiants pairs, l'autre des identifiants impairs réduisant ainsi à zéro les risques de collision. 
Sur le serveur 1 : 
auto_increment_increment  = 1 
Sur le serveur 2 : 
auto_increment_increment  = 2 
et sur les deux machines : 
auto_increment_offset     = 2 
La machine 1 génère ainsi la série 1,3,5,7 .... 
la machine 2 la série 2,4,6,8 .... 
Après ces quelques modifications on redémarre Mariadb pour valider nos modifications. 
Il faut ensuite configurer le volume destiné aux données (LVM). 
C'est pour cela que j'ai installé le peu gourmand "system-config-lvm", 
Créer un volume "MARIADBDATA1" (partition 1 du second disque "/dev/xvdb1"), dans ce volume créer un LV "DATA" de 24GB conservant ainsi 8GB pour des snapshots. 
Ce LV est créé en "ext4" et monté sur /DATA/mysql que l'on aura créé auparavant sans oublier un "chmod mysql:mysql /DATA/mysql". 
Ensuite arrêter MariaDB et copier le contenu cd /var/lib/mysql dans le répertoire /DATA/mysql (après montage du disque bien sûr !) et remplacer le répertoire (renommé par sécurité !) d'origine par un lien /var/lib/mysql --> /DATA/mysql on peut alors redémarrer MariaDB.

Les deux machines ont été installées "à la main" (pas en copiant l'image disque) afin de bien vérifier la procédure d'installation. 
Ces deux machines ont une structure identique, le report des données présentes dans le S1 est facilité :

  1. On arrête les deux services "mysql"
  2. On copie "bêtement les fichiers du répertoire /DATA/mysql de S1 dans celui de S2 (tar)
  3. On relance le service et on resynchronise les deux "masters" entre eux.

MariaDB : maître maître : sauvegarde

MariaDB : maître maître : sauvegarde drupadmin

On a un beau système, performant, efficace, je vous laisse imaginer différentes manières de s'en servir mais on peut, par exemple :

  • Spécialiser un serveur dans l'écriture, L'autre dans la lecture, par exemple pour la création de ces gros rapports dont raffolent certaines personnes et qui "écroulent" le serveur principal.
  • Spécialiser une des machines en écriture, l'autre en lecture. On peu faire ce type d'utilisation à l'aide d'adresses IP virtuelles (1 pour l'écriture, l'autre pour la lecture), un système de fail-over permet en cas d'incident sur l'un des serveurs bascule les 2 VIP sur le serveur valide..

​Une Sauvegarde quasi "ON LINE". 

Sauvegarde non testée --> DANGER, n'oubliez de tester une restauration ...

MariaDB : maître/maître mise en place

MariaDB : maître/maître mise en place jpp

Passons maintenant à la pratique du passage en "maître / maître" !

Le principe du maître / maître est très simple, chaque machine est à la fois le maître et l'esclave de l'autre. 
Cela impose quelques contraintes déjà connues telles que d'utiliser différentes valeurs de "server-id" et, comme précisé dans le précédent article, d'utiliser des valeurs correctes de "auto-increment-increment" et "auto-increment-offset" afin de limiter les risques de doublons. 
Il est par ailleurs déconseillé d'utiliser les deux machines en écriture simultanément car un risque de corruption existe. Il est par contre intéressant au niveau de la performance globale d'utiliser une machine en écriture et de diriger vers l'autre les applications en lecture seule, particulièrement des applications grosses consommatrices de lecture : états et statistiques diverses. Le retard entre les deux machines est en général à peu près négligeable. 
Ce type de redondance permet par contre un basculement extrêmement rapide en car d'incident sur le serveur "écriture". Les adresses IP virtuelles permettent de réaliser la basculement sans délai ni manipulation complexe et cela peut être automatisé à l'aide de divers outils.

Pour simplifier nous nommerons S1 et S2 les deux machines. Les deux opérations à réaliser sont extrêmement simples :

  • Rendre S2 "esclave" de S1, et lorsque cela est réalisé passer à la phase suivante
  • Rendre S1 "esclave" de S2.

1) Rendre S2 esclave de S1. 
Les commandes suivantes permettent : 
d'autoriser S2 à se connecter comme esclave :​

/* 
    Sur S1 
    ------------ 
    Autoriser accès slave 
*/ 
GRANT   REPLICATION SLAVE ON *.* TO 'slave_user'@'IP_de_S2'  
    IDENTIFIED BY 'password1234';

/* 
    Passer "SHOW MASTER STATUS" sur S1 pour obtenir les valeurs de LOG_FILE et LOG_POS 
*/

/*  
   Sur S2 
*/ 
CHANGE MASTER TO MASTER_HOST='IP_de_S1', 
    MASTER_USER='slave_user',  
    MASTER_PASSWORD='password1234',  
    MASTER_LOG_FILE='mysql-bin.xxxxxxxx',  
    MASTER_LOG_POS=  yyyyyy;

start SLAVE;


Lorsque cette opération est réalisée, on teste un peu en redémarrant chacun des deux serveurs et en vérifiant la synchro à l'aide des commandes : 
SHOW MASTER STATUS; 
et 
SHOW SLAVE STATUS\G 
(le \G permet un affichage plus agréable .... pour les quelques malheureux qui l'ignorent encore).

Il est alors possible d'effectuer les tests habituels de création de table et d'insertion de rangs en vérifiant bien que les séries des identifiants attribués diffèrent bien entre les deux machines. Cela marche aussi avec trois machines en utilisant les "bons" paramètres d'incrément pour créer les séries 1,4,7,10; 2,5,8,11 et 3.6.9.12 ... 

Le maître/maître ... mais c'est très simple !

A partir de là, tout ce qui se passe dans un des deux serveurs est reproduit sur l'autre. 

Si vous recopiez vos données par export (d'un autre système) + import dans le groupe maître/maître (vidé de préférence) cela fonctionne bien. Il faut, là encore, bien synchroniser vos deux serveurs avant le début de l'import des données. 
L'import dans l'un des serveurs sera reproduit fidèlement dans le second. 
A ce propos, si vous utilisez des "events" ils seront en état "ENABLED" dans le serveur dans lequel a été réalisé l'import et en l'état "SLAVE_DISABLED" dans l'autre. Ainsi pas de risque de déclenchement de deux traitements en parallèle. 
 

Maitre / esclave : petits inconvénients

Maitre / esclave : petits inconvénients jpp

Vous trouverez vite quelques inconvénients à cette réplication car certains ordres SQL ne fonctionnent pas (ou plus). 
Par exemple "create table as select * from ... where AA = '3' ", bien commode pourtant, vous jettera à la figure un message : 
ERROR 1786 (HY000): CREATE TABLE ... SELECT is forbidden when @@GLOBAL.ENFORCE_GTID_CONSISTENCY = 1. 
En effet cette construction est un concentré de deux ordres SQL : CREATE TABLE + INSERT et comme il s'agit d'une seule transaction elle n'aura qu'un "Transaction ID" et le slave recevra deux ordres différents le CREATE et l'INSERT avec un même TID, or un bon esclave n'exécute pas deux fois le même ID de transaction, il ferait le CREATE mais pas l'INSERT ! 
C'est un peu stupide, mais on arrive à s'y faire. 
D'autres inconvénients existent lors d'un mélange de tables MyISAM et InnoDB car InnoDB supporte les transactions mais pas MyISAM d'où d'autres messages parfois sybillins, mais je pense que vous n'avez plus de tables MyIsam ! C'est si facile de les convertir ... 
C'est peut-être un bon motif pour abandonner le MyISAM au profit de InnoDB pour vivre dans un monde transactionnel et maître/esclave sans erreurs superflues et sans "CREATE TABLE ... AS SELECT" (sniff). 
Ah, aussi, on ne peut pas créer de tables temporaires dans une transaction, il faut les créer en dehors de tout contexte transactionnel pour que cela se passe bien. 
Il semble d'ailleurs que le problème du "CREATE AS SELECT" soit résolu dans les dernières versions en choisissant bien le bon mode pour le binlog_mode.

Mysql-community-server personnalisé

Mysql-community-server personnalisé jpp

J'ai voulu installer la version 5.7 de Mysql depuis le repository de Mysql en chargeant depuis le site le paquet "Ubuntu / Debian (Architecture Independent), DEB" qui installe notament un fichier "mysql.list" dans /etc/apt/sources-list.d, ensuite on utilise les outils standard Debian : aptitude, apt-get ou synaptic. 
Remarque : j'ai inhibé le repository "mysql-tools-preview" 
Un "apt-get update" plus loin les choses sont disponibles et on peut installer "mysql-community-client" et "mysql-community-server" version 5.7.16 
J'ai voulu réaliser une installation non "standard" en positionnant les fonctionnalités principales de la base de données sur des systèmes de fichiers différents notamment pour la partie "log" et pour la partie "données"  afin d'optimiser le fonctionnement. 
La machine est munie de :

  • CPU    CoreI3 4330
  • RAM    16G
  • 3 disques SSD

Selon le schéma suivant : 
Un système de fichier racine 
Un système de fichier /DATA/DATA1 pour les données 
Un système de fichier /LOG/LOG1 pour les logs y compris les "logbin".

J'ai configuré le fichier /etc/mysql/mysql.conf.d/mysqld.cnf de la façon suivante :

[mysqld] 
pid-file               = /run/mysqld/mysqld.pid 
socket                 = /run/mysqld/mysqld.sock 
# By default we only accept connections from localhost 
bind-address           = 0.0.0.0 
# Disabling symbolic-links is recommended to prevent assorted security risks 
symbolic-links=0 
### Deplacement des répertoires 
datadir                = /DATA/DATA1/mysql 
tmpdir                 = /LOG/LOG1/mysql_tmp 
log_bin                = /LOG/LOG1/mysql/server-bin 
general_log_file       = /LOG/LOG1/mysql/general.log 
slow-query-log-file    = /LOG/LOG1/mysql/slow_query.log 
server_id              = 11 
log_error              = /LOG/LOG1/mysql/error.log 
secure-file-priv       = /DATA/DATA1/mysql-files 
slave_load_tmpdir      = /LOG/LOG1/mysql_tmp 
### configuration InnoDB

....

Nettoyez toute trace de l'installation imposée lors du démarrage (/var/lib/mysql, /var/log/mysql). 
Vérifiez bien que tous vos répertoires "mysql" ont bien le user et le groupe "mysql" :

cd /DATA//DATA1 
ls -al 
drwxr-xr-x 6 root  root   4096 nov.  18 20:00 . 
drwxr-xr-x 4 root  root   4096 nov.  14 23:27 .. 
drwx------ 2 root  root  16384 nov.  12 22:24 lost+found 
drwxr-xr-x 5 mysql mysql  4096 nov.  18 20:26 mysql 
drwx------ 2 mysql mysql  4096 nov.  18 20:02 mysql-files 
cd /LOG/LOG1 
drwxr-xr-x 6 root  root   4096 nov.  17 23:22 . 
drwxr-xr-x 4 root  root   4096 nov.  15 00:21 .. 
drwx------ 2 root  root  16384 nov.  15 00:21 lost+found 
drwxr-xr-x 2 mysql mysql  4096 nov.  18 20:04 mysql 
drwx------ 2 mysql mysql  4096 nov.  18 20:22 mysql-files 
drwxr-xr-x 2 mysql mysql  4096 nov.  18 20:25 mysql_tmp

Il faut maintenant attaquer la partie "systemd" et la corriger pour éluder un script (horrible) rempli de valeurs "en dur" qui viendrait perturber la belle ordonnance de nos paramètres. 
Ce script (/usr/share/mysql/mysql-systemd-start) est à ignorer purement et simplement et à remplacer par un petit script destiné uniquement à initialiser correctement le répertoire "mysqld" dans /run (et pas /var/run qui n'est qu'un lien vers /run) ce script hyper simple (je l'ai appelé "init-mysql") il est fourni à titre d'exemple :

#!/bin/bash 
MYSQLRUN=/run/mysqld 
if [ ! -d ${MYSQLRUN} ] 
   then 
    mkdir ${MYSQLRUN} 
    chown mysql:mysql ${MYSQLRUN} 
fi


Ce n'est vraiment pas grand chose ! 
Attention ce script est exécuté par le user "mysql", le mettre dans /usr/bin par exemple pour qu'il soit accessible sans difficultés.

Modifier le script "systemd" de lancement du service (/lib/systemd/system/mysql.service) pour en extirper la référence à l'horrible "mysql-systemd-start" : 
Partie du script "systemd" modifié :

[Service] 
User=mysql 
Group=mysql 
Type=forking 
PermissionsStartOnly=true 
PIDFile=/run/mysqld/mysqld.pid 
ExecStartPre=/usr/bin/init_mysql 
ExecStart=/usr/sbin/mysqld --daemonize --pid-file=/run/mysqld/mysqld.pid 
TimeoutSec=60 
LimitNOFILE = 5000 
Restart=on-abort 
RestartPreventExitStatus=1 
RuntimeDirectory=mysqld 
RuntimeDirectoryMode=755 
Ne pas oublier ensuite "systemctl daemon-reload" avant de relancer le service. 
Aller modifier le fichier /etc/mysql/conf.d/mysql.cnf (utilisé par le client mysql) pour y indiquer le nom du fichier "socket" : 
[mysql] 
socket = /run/mysqld/mysqld.sock

On doit encore réaliser un petit truc bizarre, créer un lien entre : 
/etc/mysql/mysql.conf.d/mysqld.cnf et /etc/my.cnf ??? 
On peut alors, enfin, lancer une installation "personnalisée" de l'ensemble en utilisant le paramètre "--initialize--insecure" de mysqld, adieu "mysql_install_db" on ne te regrettera pas trop. 
mysqld --initialize-insecure 
qui crée le user "root" sans mot de passe, il suffira d'en mettre un rapidement avec la commande "set password ..." pour rester correct, la commande se termine rapidement, SSD oblige. 
Cette commande a rempli les répertoires prévus /DATA/DATA1/mysql de ce qui se trouve dans /var/lib/mysql habituellement. 
Tout semble prêt on lance la commande fatidique : 
service mysql start 
qui rend la main rapidement (tout est sur des SSD), on vérifie un peu si tout va bien :

ps -ef | grep mysqld 
mysql  2733  1  2 22:52 ?  00:00:00 /usr/sbin/mysqld --daemonize --pid-file=/run/mysqld/mysqld.pid

puis :

ls -al /run/mysqld 
drwxr-xr-x  2 mysql mysql 100 nov.  18 22:52 . 
drwxr-xr-x 23 root  root  860 nov.  18 22:52 .. 
-rw-r-----  1 mysql mysql   5 nov.  18 22:52 mysqld.pid 
srwxrwxrwx  1 mysql mysql   0 nov.  18 22:52 mysqld.sock 
-rw-------  1 mysql mysql   5 nov.  18 22:52 mysqld.sock.lock

Tout à l'air parfait on lance le client mysql :

mysql 
Welcome to the MySQL monitor.  Commands end with ; or \g. 
Your MySQL connection id is 2 
Server version: 5.7.16-log MySQL Community Server (GPL)Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.Oracle is a registered trademark of Oracle Corporation and/or its 
affiliates. Other names may be trademarks of their respective 
owners.Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. 
mysql>

Ouaaaah tout baigne, vite mettons un mot de passe à ce vieux "root" :

use mysql; 
mysql> set password for 'root'@'localhost' = PASSWORD('Un_BeAu_#_PaSsWoRd_ToUt%Neuf?'); 
\q 
Et on teste notre mot de passe :

mysql --user=root -p 
Enter password:  
Welcome to the MySQL monitor.  Commands end with ; or \g. 
Your MySQL connection id is 5 
Server version: 5.7.16-log MySQL Community Server (GPL)Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.Oracle is a registered trademark of Oracle Corporation and/or its 
affiliates. Other names may be trademarks of their respective 
owners.Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>\q

C'est fini, on dispose d'une base toute neuve en version 5.7.16. Montée sur des systèmes de fichiers SSD cela devrait être assez rapide ! 
A bientôt pour quelques tests. 
 

Mysql : Maitre/esclave avec proxy

Mysql : Maitre/esclave avec proxy jpp

Mettre l'esclave au boulot ! Pour réaliser cet exploit il faut utiliser un "proxy intelligent" capable de distinguer les ordres en lecture seule (SELECT) et les autres (le SELECT ... FOR UPDATE) est un piège. 
Le proxy que j'ai choisi de tester s'appelle "ProxySql", nom très original s'il en est, mais il fonctionne fort bien et est assez simple à manipuler.

Le paquet est téléchargeable (V1.2.1 au moment de ces tests) à : 
https://github.com/sysown/proxysql/releases/download/v1.2.1/proxysql_1.2.1-debi… 
La liste complète des téléchargements est accessible à  l'URL: 
https://github.com/sysown/proxysql/releases/ 
L'installation se fait sans mystère par : 
dpkg -i proxysql_1.2.1-debian8_amd64.deb 
Un script d'init est installé ainsi qu'un fichier de configuration /etc/proxysql.cnf, un répertoire /var/lib/proxysql est créé, de la doc /usr/share/doc ... Tout a l'air présent ! 
Le service n'est pas démarré par défaut, après un "service proxysql initial" (pour la première fois) le service est lancé et écoute sur les ports 6032 et 6033 (paramètres par défaut). 
Ce paramètre "initial" permet d'initialiser la base de données interne de proxysql. 
Si vous n'avez pas personnalisé un minimum votre fichier "proxysql.cnf" vous pouvez le modifier et repasser un petit coup de "initial". Au passage renommer rapidement ce fichier sinon au prochain démarrage il sera réutilisé ! 
Proxysql utilise syslog et vous trouverez ses messages dans /var/log/daemon.log 
Par la suite on n'aura plus besoin de repasser le paramètre "initial", sauf pour réparer une "grosse bêtise". 
Testons maintenant la connexion à Proxysql (user/mot de passe fixés dans proxysql.cnf)  :

mysql -u admin -p  -h 127.0.0.1 -P 6032 
Enter password: admin 
Welcome to the MySQL monitor.  Commands end with ; or \g. 
Your MySQL connection id is 3 
Server version: 5.5.30 (ProxySQL Admin Module) 
Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved. 
Oracle is a registered trademark of Oracle Corporation and/or its 
affiliates. Other names may be trademarks of their respective 
owners. 
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. 
mysql>

Cela a un petit air connu ... 
Remarque : la connexion par défaut est sur la base "main" :

mysql> show databases; 
+-----+---------+-------------------------------+ 
| seq | name    | file                          | 
+-----+---------+-------------------------------+ 
| 0   | main    |                               | 
| 2   | disk    | /var/lib/proxysql/proxysql.db | 
| 3   | stats   |                               | 
| 4   | monitor |                               | 
+-----+---------+-------------------------------+ 
4 rows in set (0,00 sec) 
mysql> use main; 
show tables; 
+--------------------------------------+ 
| tables                               | 
+--------------------------------------+ 
| global_variables                     | 
| mysql_collations                     | 
| mysql_query_rules                    | 
| mysql_replication_hostgroups         | 
| mysql_servers                        | 
| mysql_users                          | 
| runtime_global_variables             | 
| runtime_mysql_query_rules            | 
| runtime_mysql_replication_hostgroups | 
| runtime_mysql_servers                | 
| runtime_scheduler                    | 
| scheduler                            | 
+--------------------------------------+ 
mysql> use disk; 
show tables; 
+--------------------------------------+ 
| tables                               | 
+--------------------------------------+ 
| global_variables                     | 
| mysql_collations                     | 
| mysql_query_rules                    | 
| mysql_replication_hostgroups         | 
| mysql_servers                        | 
| mysql_users                          | 
| runtime_global_variables             | 
| runtime_mysql_query_rules            | 
| runtime_mysql_replication_hostgroups | 
| runtime_mysql_servers                | 
| runtime_scheduler                    | 
| scheduler                            | 
+--------------------------------------+ 
\q

Tiens ? Les mêmes tables sont présentes dans les bases "disk" et "main".

On peut "regarder" le contenu des tables par des ordres "SELECT", ajouter des choses par "INSERT", modifier par "UPDATE", et faire des "DELETE". La plupart des commandes de Mysql fonctionnent (sauf "desc" ?). 
Pour faire des modifications "rollbackables" il faut les faire au sein d'une transaction explicite (begin transaction;), le rollback sera ainsi possible. 
Le proxy dispose de deux règles par défaut "Select for update" et "select" qui envoyent respectivement sur les "hostgroups" 0 et 1, traditionnellement le "0" est le maître (celui qui peut effectuer des modifications de la base) et le groupe 1 celui des esclaves en lecture seule. 
On se dépêche de changer le user "admin:admin" en autre chose de plus sécurisé : 
update global_variables set variable_value = 'new_admin:admin_new' where variable_name = 'admin-admin_credentials'; 
Attention aux '-' , ":" et aux '_' !!! 
Vous avez certainement remarqué les noms des tables doublés entre : 
global_variables 
runtime_global_variables 
..... 
Après une modif dans les tables "sans préfixe" il faut les recopier dans les tables "runtime" puis les sauvegarder sur disque pour rendre les modifications permanentes. 
On peut ainsi préparer une nouvelle config dans les tables "sans préfixe" qui ne sont pas actives puis la basculer sur les tables "runtime" ce qui les rend actives. 
Cela se fait à l'aide de commandes "LOAD" et "SAVE". 
Dans notre cas de modfification de l'accès administrateur il faut : 
Le reporter dans le "runtime" 
LOAD MYSQL VARIABLES TO RUNTIME; 
et les sauvegarder pour la postérité : 
SAVE MYSQL VARIABLES TO DISK;

Dès que vous avez changé le user/mot de passe administrateur vous pouvez (devez) :

  • Détruire (ou renommer) le fichier /etc/proxysql.cnf
  • Redémarrer le service proxysql

Votre nouveau user/mot de passe sera alors actif.

Pour les tests le hostgroup 0 est une VM Mysql "master", le hostgroup 1 est une VM Mysql "slave" de la première qui ne recevra que les ordres "SELECT" purs exempts de toute clause "FOR UPDATE". 
On revient un peu en arrière et on va voir la config postée initialement dans le fichier "proxysql.cnf" pour nos serveurs Serveur1 (master) et Serveur2 (slave) :

mysql_servers = 
( 
   { address="mysql1" , port=3306 , hostgroup=0 , max_connections=10 }, 
   { address="mysql2" , port=3306 , hostgroup=1 , max_connections=10 } 
) 
mysql_users: 
( 
  { username = "test" , password = "test", default_hostgroup = 0, active = 1 } 
)

Si on regarde (avec notre nouvel "admin") l'état de la table mysql_servers :

select * main.from mysql_servers; 
*************************** 1. row *************************** 
       hostgroup_id: 0 
           hostname: mysql1 
               port: 3306 
             status: ONLINE 
             weight: 1 
        compression: 0 
    max_connections: 10 
max_replication_lag: 0 
            use_ssl: 0 
     max_latency_ms: 0 
*************************** 2. row *************************** 
       hostgroup_id: 1 
           hostname: mysql2 
               port: 3306 
             status: ONLINE 
             weight: 1 
        compression: 0 
    max_connections: 10 
max_replication_lag: 0 
            use_ssl: 0 
     max_latency_ms: 0 
2 rows in set (0,00 sec)

C'est bien l'état indiqué dans le fichier original ,vérifions maintenant nos règles de répartition :

mysql> select * from mysql_query_rules\G 
*************************** 1. row *************************** 
              rule_id: 1 
               active: 1 
             username: NULL 
           schemaname: NULL 
               flagIN: 0 
          client_addr: NULL 
           proxy_addr: NULL 
           proxy_port: NULL 
               digest: NULL 
         match_digest: NULL 
        match_pattern: ^SELECT .* FOR UPDATE$ 
 negate_match_pattern: 0 
              flagOUT: NULL 
      replace_pattern: NULL 
destination_hostgroup: 0 
            cache_ttl: NULL 
            reconnect: NULL 
              timeout: NULL 
              retries: NULL 
                delay: NULL 
       mirror_flagOUT: NULL 
     mirror_hostgroup: NULL 
            error_msg: NULL 
                  log: NULL 
                apply: 1 
*************************** 2. row *************************** 
              rule_id: 2 
               active: 1 
             username: NULL 
           schemaname: NULL 
               flagIN: 0 
          client_addr: NULL 
           proxy_addr: NULL 
           proxy_port: NULL 
               digest: NULL 
         match_digest: NULL 
        match_pattern: ^SELECT 
 negate_match_pattern: 0 
              flagOUT: NULL 
      replace_pattern: NULL 
destination_hostgroup: 1 
            cache_ttl: NULL 
            reconnect: NULL 
              timeout: NULL 
              retries: NULL 
                delay: NULL 
       mirror_flagOUT: NULL 
     mirror_hostgroup: NULL 
            error_msg: NULL 
                  log: NULL 
                apply: 1 
2 rows in set (0,00 sec)

C'est bien ce que nous voulions installer. 
Quand à notre user il semble lui aussi OK :

select * from MYSQL_USERS\G 
*************************** 1. row *************************** 
              username: test 
              password: test 
                active: 1 
               use_ssl: 0 
     default_hostgroup: 0 
        default_schema: 
         schema_locked: 0 
transaction_persistent: 0 
          fast_forward: 0 
               backend: 1 
              frontend: 1 
       max_connections: 10000


Tout cela a l'air bel et bon.


Site à voir : 
http://severalnines.com/blog/how-proxysql-adds-failover-and-query-control-your-mysql-replication-setup

Mysql huges pages or not

Mysql huges pages or not jpp

Afin de mesurer l'influence des "huge pages" j'ai réalisé deux séries de tests : 
- en utilisant les "huge pages", 8 Go alloués aux huge pages + paramètre "large-pages" dans la config Mysql. 
- sans les utiliser, vm.nr_hugepages = 0 dans sysctl.conf, pas de paramètre "large-pages" pour Mysql.

Remarques liées à la version 5.7.16 : 
Lors du chargement des données, à partir d'un dump depuis une machine 5.6.30, j'ai remarqué quelques différences de comportement entre les versions 5.7.16 et 5.6.30 : 
- certaines valeurs de "datetime" donnent des erreurs à l'importation des données, par exemple "000-00-00 00:00:00", valeur utilisée par défaut en 5.6 sont refusées à l'import en 5.7.  Ceci m'a obligé à "corriger" les valeurs dans les tables originales pour que les tables soient importées. 
- la présence en double d'une colonne dans un ordre "select" est indiquée comme erreur : "ERROR 1060 (42S21): Duplicate column name 'CODPAYS'"

Conditions du test : 
Afin de ne mesurer que l'influence de la mémoire la taille du "innodb_buffer_pool_size" a été portée à 6Go afin que l'intégralité des données se trouve en cache (total données #4Go). Le "dashboard" de mysql-workbench donne le cache rempli à 70%. 
Les valeurs de "query_cache" sont à zéro pour inhiber les fonctions de cache de requêtes. 
Les valeurs allouées à "max_heap_table_size" et "tmp_table_size" ont été fixées à 256M pour réaliser toutes les opérations en mémoire. 
Chaque test a été précédé d'un redémarrage complet de la machine pour bien "enregistrer" la configuration des "huge pages" sans désorganiser la mémoire afin de bénéficier de performances non biaisées. 
Les tests ont chaque fois été précédés de deux tours de chauffe afin de charger les buffers et effectivement après le premier tour de chauffe on ne constate plus d'IO sur le disque "DATA" et quelques IO sur le disque où sont placées les tables temporaires. 
Pour simplifier les tests, les ordres qui affichent des lignes ont été "entouré de "select from ( ...... ) xx;" afin de limiter l'affichage au nombre de lignes du résultat et au temps d'exécution. 
Chaque test a été lancé trois fois en respectant l'ordre 1 .. 7, 1 .. 7, 1.. 7, les chiffres du tableau (en secondes) sont le total des trois passages.

  Secondes  
N0 test Huge inhibé Huge activé Delta %
TEST 1 28,09 27,91 -0,64%
TEST 2 5,81 5,75 -1,03%
TEST 3 0,49 0,47 -4,08%
TEST 4 3,10 3,03 -2,26%
TEST 5 2,87 2,88 0,35%
TEST 6 (join + OR) 49,37 49,06 -0,63%
TEST 6 (union) 46,49 46,25 -0,52%
TEST 7 17,70 17,52 -1,02%
TOTAL 153,92 152,87 -0,68%

Commentaires : 
L'amélioration, bien que visible et a peu près constante sur les tests, reste faible : #0,7% sur le total des tests, mais elle devrait être un peu plus nette sur des tailles de buffers élevées, des "buffer_pool" de plus de 100 Go sont courants sur de "vraies" bases que dépassent couramment les 400 Go. 
Note 2021 : des tests réalisés avec des versions récentes de MariaDB donnent le même genre de résultats.

MySQL : utilisation tables Federated(x)

MySQL : utilisation tables Federated(x) jpp

Les tables "FEDERATED"  sont des tables "distantes" sur un système différent. 
Les tables de type "FEDERATED" permettent la communication (facile) entre deux bases MySQL (et même non-MySQL, par exemple MariaDB). 
Ce plugin est relativement insensible aux versions des bases utilisées. 
Remarque : pour MariaDB l'utilisation du plugin "ha_federated.so" est à déconseiller car ce plugin n'est plus maintenu ... mais remplacé par "ha_federatedx.so" qui, lui, est toujours maintenu et a un fonctionnement compatible. Même avec le plugin "federatedx" il faut ajouter "federated" dans votre fichier de configuration. 
Pour MariaDB le "nec plus ultra" est d'utiliser un nouveau plugin "connect" dont je vous proposerais un test prochainement.

Un petit exemple d'utilisation de "federated" :

  • Je dispose d'une machine "A" qui gère les tables utilisées par NtopNG, je ne veux pas laisser dans cette machine un trop gros volume de donnnées car NtopNG est assez consommateur d'espace et j'ai limité a 30 jours la rétention des données.
  • Je voudrais disposer d'un historique plus "long" sur une machine "B" pour pouvoir m'amuser à faire des analyses à plus long terme.

La gestion du transfert des données de A vers B m'a posé quelques problèmes, car les solutions "classiques" ne sont pas faciles à gérer :

  1. Extraction dans une table "tampon" des données de la dernière semaine,
  2. Export de la table tampon par mysqldump,
  3. Transfert du ficher export
  4. Import dans une table tampon
  5. Intégration dans la table définitive.

Les rangs de la table principale sont identifiés par un ID en auto-incrément et il est donc aisé de repérer ceux qui manquent dans la machine "B" et c'est là que se profile l'utilisation d'une table "FEDERATED" Mysql. 
En effet il suffit de déclarer dans la machine "B" une table identique à la table principale de "A" avec "engine=FEDERATED" pour accéder de "B" vers "A". La création est simple et toutes les opérations se font sur la machine "B". Il faut quand même vérifier que "monuser" a bien accès à la base "A" depuis l'adresse de "B" :

  • Créer un "serveur" :

    create server xxxxxxx 
        foreign data wrapper mysql 
        options (USER 'monuser', PASSWORD 'monpassword', HOST '192.168.x.y', DATABASE 'mabaseA');
  • Créer une "table" FEDERATED (IDX = PRIMARY KEY) :

    create table table_distante 
    (    ...... 
    KEY    (IDX), 
    .... 
    ) engine=FEDERATED connection='xxxxxxx/table_sur_serveur_A';

Ensuite récupérer les données sur B est hyper simple :

select max(idx) into @IDMAX from table_sur_B;

insert into table_sur_B select * from table_distante 
where idx > @IDMAX;

commit;

Et en plus c'est assez rapide comme le montre la trace suivante :

-------------- 
select max(idx) into @IDMAX from table_sur_B, 
--------------

Query OK, 1 row affected (0,03 sec)

-------------- 
select @IDMAX 
--------------

+----------+ 
| @IDMAX   | 
+----------+ 
| 12775832 | 
+----------+ 
1 row in set (0,00 sec)

-------------- 
insert into table_sur_B select * from table_distante 
where idx > @IDMAX 
--------------

Query OK, 532917 rows affected (7,30 sec) 
Records: 532917  Duplicates: 0  Warnings: 0

Plus de 530 000 rangs en 7,3 secondes ... c'est bien plus rapide que la manip "old style" et surtout bien plus simple ! 
La base distante est ici une base Mysql mais cela marche aussi avec d'autres bases ... Oracle, Postgres ... selon les modules disponibles.

Attention toutefois, un bête "select" ne passant pas par un index entraînera le transport de toute la table en local (temporaire) et peut durer assez longtemps et être très consommateur de ressources (disque et mémoire), donc éviter comme la peste les ordres n'utilisant pas correctement un index, mon exemple utilise la Primary Key et est donc très rapide.

Mariadb : Connect Engine

Mariadb : Connect Engine jpp

Le "vieux" plugin "federated" n'est plus maintenu, il existe une autre version "federatedx" qui fonctionne correctement sur Mysql (détails ici), je ne l'ai pas testée récemment sur MariaDB.  
La méthode "IN" préconisée aujourd'hui pour MariaDB est d'utiliser le plugin "CONNECT". Celui-ci  met à votre disposition un nouvel "ENGINE" qui permet la connexion à des bases distantes (de diverses saveurs), à des fichiers "plats", du Xml, du Json. 
Le "CONNECT ENGINE" fait parfois partie des distributions stantard sur Linux  : 
- Debian/Ubuntu    : apt install mariadb-plugin-connect 
- RedHat/Centos/Fedora : yum install MariaDB-connect-engine 
Ensuite il suffit de l'activer (avec un utilisateur qui a des droits suffisants !) :

mysql --user=... -p 
prompt> INSTALL SONAME 'ha_connect'; 
prompt> \q

Ensuite on pourra passer aux choses sérieuses ... 
La création des tables reste classique, quelques exceptions notables : 

  1. Pas de champ en AUTO_INCREMENT --> utiliser des séquences gérées "à la main" (MariaDB >= 10.3).
  2. Un champ présent dans un index doit absolument être spécifié "NOT NULL".
  3. Pas de champs TEXT/BLOB, ça c'est un peu dommage !
  4. Les clefs d'index ne doivent pas dépasser 255 caractères.

En ce qui concerne l'accès à une base Mysql/MariaDB le protocole étant standardisé il suffit d'indiquer après les définitions d'index :

... définition des données et index ... 
) Engine=CONNECT DEFAULT CHARSET xxxxxx 
table_type=mysql 
connection='mysql://USER/PASSWORD@HOST/DATABASE/TABLE';

La table est crée et permet un accès direct aux données. On peut, bien sûr, utiliser un mélange de tables locales et distantes dans le même ordre SQL.  
Je m'en suis notamment servi pour repérer des différences au sein d'un cluster maître/maître qui avait subi quelques manipulations hasardeuses au niveau de la définition des GTID des slaves (mélange entre gtid_slave_pos et gtid_current_pos). 
Quelques tests rapides. 
Les tests ont été réalisés sur deux VM identiques Debian 10 sur KVM : 
2 vcpu, 2Go de RAM, une seule partition disque de 127Go sur une carte M2, MariaDB 10.3.23 paramétré avec un cache InnoDB de 1Go et 4 pool instances.  
Chaque machine dispose de deux tables locales quasiment identiques, les tables notées "Z_..." sont des tables distantes, celles notées "D_..." sont des tables locales. L'espace disque est d'environ 1,7Go par table.

Mysql redémarré avant le premier test (caches vidés) : 
Un test rapide de comptage sur table distante.

select count(*) from Z2_TABLE_DISTANTE 
+----------+ 
| count(*) | 
+----------+ 
|  1454065 | 
+----------+ 
1 row in set (0.984 sec)

select count(*) from Z2_TABLE_DISTANTE; 
+----------+ 
| count(*) | 
+----------+ 
|  1454065 | 
+----------+ 
1 row in set (0.910 sec)

 L'effet du cache n'est ici pas énorme, même pas 10% de gain. 
Test sur la même table en local :

select count(*) from D2_TABLE_LOCALE   
+----------+ 
| count(*) | 
+----------+ 
|  1454065 | 
+----------+ 
1 row in set (0.379 sec)

select count(*) from D2_TABLE_LOCALE; 
+----------+ 
| count(*) | 
+----------+ 
|  1454065 | 
+----------+ 
1 row in set (0.229 sec)

L'effet du cache est ici beaucoup plus notable ...

Autre test : jointures entre table distante et table locale :

select count(*)  
from Z1_TABLE_DISTANTE Z1 
join D2_TABLE_LOCALE   D2 on D2.ID = Z1.ID; 
+----------+ 
| count(*) | 
+----------+ 
|  1454065 | 
+----------+ 
1 row in set (6.093 sec) 

select count(*) 
from D1_TABLE_LOCALE Z1 
join D2_TABLE_LOCALE D2 on D2.ID = Z1.ID 
+----------+ 
| count(*) | 
+----------+ 
|  1454065 | 
+----------+ 
1 row in set (4.349 sec)

Les performances sont ici très correctes la différence de temps d'exécution est relativement faible ce moteur est très facile à utiliser et présente des performances intéressantes.

Autre test : 
Voir la différence entre le traitement d'une table "locale" et d'une table "pseudo distante" (déclarée comme une table distante mais utilisant l'adresse de la machine locale) :

select count(*) from Z2_TABLE_PSEUDO_DISTANTE; 
+----------+ 
| count(*) | 
+----------+ 
| 23919958 | 
+----------+ 
1 row in set (11.853 sec)

select count(*) from TABLE_LOCALE; 
+----------+

| count(*) | 
+----------+ 
| 23919958 | 
+----------+ 
1 row in set (3.613 sec)


 La différence est assez significative, j'ai refait plusieurs essais pour éliminer l'influence du cache mais la table utilise environ 13 Go d'espace disque, le cache de 1Go ne joue que peu.

MariaDB et Galera

MariaDB et Galera jpp

J'ai décidé de faire un test de la dernière version de MariaDB associée à Galera, non ce n'est pas la galère, puis y associer Maxscale.  
Ceci nécessite une certaine infrastucture (ici réalisée avec des machines virtuelles KVM) : 

  • 1 machine pour Maxscale  
    3 machines pour MariaDB/Galera 

Pourquoi trois machines ? 
Parce que avec seulement deux machines un système plus simple est possible et qu'un système comme Galera se comporte mieux avec 3 machines car cela permet d'éviter des duels fratricides en cas d'incident sur une des machines ce que les anglicistes nomment "split brain" et je n'ai pas de traduction sensée à fournir !  

Le test de Maxscale est repoussé car bien que IPV6 soit inhibé ici, le module de traitement "lecture seule" cherche un utilisateur avec une adresse "bizarre" de style "IPV6" et refuse donc la connexion. 
L'accès lecture/écriture, lui, fonctionne fort bien mais je voudrais trouver l'explication de ces adresses "fantaisie" pour le module lecture-seule avant de passer à des tests complets. 
Je n'ai pas réglé ce problème pseudo "IPv6", mais après une mise à jour cela ne semble plus se produire ... 
 

Installation machines BDD

Installation machines BDD jpp

Préambule.

Les configurations suivantes sont adoptées, elles sont "légères" mais largement suffisantes pour nos tests :     
  - GALE_1 : future machine "Maxscale"     
    Disque 40GB     
    Mémoire 2048M     
    2 VCPU     
 - GALE-2, GALE-3 et GALE-4 nos trois machines bases de données :     
    Disque 40GB     
    Mémoire 4096M     
    2 VCPU     
Le partitionnement adopté, volontairement simple, est suffisant pour des tests :     
   /boot 656Mo     
   / 36Go     
   Swap 3,5Go

Les deux premières machines ont été installées "normalement" à partir d'une image ISO, les machines 3 et 4 ont été "clonées" depuis la machine 2 après l'installation de la base de données et les premiers tests de validation. "dd" est un bon ami.

Installation.     
Il faut commencer par installer la définition du repository spécifique à MariaDB qui installe tout ce qu'il faut pour MariaDB, Maxscale et quelques outils complémentaires.     
- Commencer par installer les outils curl, net-tools, vim ou gvim-gtk3 ...     
Pour pouvoir ensuite installer, valider, suivre et mettre à jour confortablement tout ce qu'il faut pour un système "normal".     
- Installer le repository MariaDB :     
curl -LsS https://r.mariadb.com/downloads/mariadb_repo_setup | sudo bash     
Ce script gère tout, y compris un "apt update" pour mettre à jour les listes de paquets.      
Comme les machines sont installées en Debian Bullseye on est limité aux versions 10.5 de MariaDB, 22.08 de Maxscale et Galera 3 ou 4 (on utilisera bien sûr la version 4).     
- On installe ici (GALE-2) MariaDB et Galera :     
apt install mariadb-client-10.5 mariadb-server-10.5 galera-4     
Cela récupère un paquet de dépendances : #28Mo d'archives et promet d'occuper 213MO sur disque.     
Après le fatidique "sysctl start mariadb" tout se passe bien !    
Lancée sous "root" la commande "mysql" nous lance gentiment le client connecté à notre nouvelle base.      
On peut alors mettre un mot de passe à l'utilisateur "root", limité à localhost et créer un compte spécifique d'administration avec une visibilité plus importante (/24 par exemple). Bien le faire avant le clonage de GALE-2 sur les deux machines suivantes.

Le système "modèle" est prêt et on peut le cloner dans les deux autres machines, il suffira de changer :     
- hostname      /etc/hostname     
- IP                  /etc/network.interfaces IP statiques    
et pour la base MariaDB.     
- server_id      /etc/mysql/mariadb.conf.d/50-server.cnf      
y mettre 2,3 et 4, les numéros de nostrois  braves machines.     
Maintenant que nos trois machines bases de données sont prêtes on va pouvoir passer à la mise en service de Galera ce qui promet d'être plus amusant.

Avant de se lancer dans Galera proprement dit il vaut mieux "arranger un peu" la configuration de MariaDB. 
Par exemple mettre les fichiers "binlog" ailleurs que dans /var/log/mariadb ! 
J'ai donc ici créé un répertoire /var/lib/binlogs : 
mkdir /var/lib/binlogs 
chown mysql: /var/lib/binlogs 
et modifié en conséquence le fichier /etc/mysql/mariadb.conf.d/50-server.cnf. 
 

Galera en route

Galera en route jpp

Pour Galera le fichier de paramétrage est /etc/mysql/mariadb.conf.d/60-galera.cnf, voir ci-dessous mon paramétrage :

#
# * Galera-related settings
#
[galera]
# Mandatory settings
wsrep_on                 = ON
wsrep_provider           = "/usr/lib/libgalera_smm.so"
wsrep_cluster_name       = "clustertest"
#
wsrep_node_name          = gale-2
wsrep-node-address	 = 192.168.2.153
#
wsrep_cluster_address    = "gcomm://192.168.2.153,192.168.2.154,192.168.2.155"
binlog_format            = row
default_storage_engine   = InnoDB
innodb_autoinc_lock_mode = 2
# Allow server to accept connections on all interfaces.
bind-address = 0.0.0.0
# Optional settings
wsrep_slave_threads = 1
innodb_flush_log_at_trx_commit = 0
# pour restart si cluster planté
wsrep_provider_options="gcache.recover=yes"

Pour le premier lancement il faut impérativement utiliser la commande suivante : 
mysqld --wsrep-new-cluster 
sur chacun des trois membres du cluster.  
Malheureusement cette commande ne "rends pas la main" j'ai donc du passer par la suite de commandes suivantes : 
mysqladmin shutdown 
systemctl start mariadb 
pour "rendre la main" et démarrer normalement, j'ai fait cela sur chacun des nœuds en laissant un petit temps intermédiaire pour laisser les machines se re-synchroniser.

Pour contrôle je crée une base : 
CREATE DATABASE montest; 
Et je vérifie qu'elle est bien créée sur les trois nœuds, youpi cette base existe bien partout ... c'est le pied. 
De même la création d'une table de test est parfaitement répliquée sur les trois nœuds : 
Create Table: CREATE TABLE `TT` ( 
 `ID` bigint(20) NOT NULL AUTO_INCREMENT, 
 `ZONE` varchar(32) DEFAULT NULL, 
 PRIMARY KEY (`ID`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

Vérifions maintenant l'insertion : 
Sur le nœud gale-2 (nœud 1) : 
insert into TT (ZONE) values ('UN'); 
Sur les deux autres nœuds on voit :

select * from TT;
+----+------+
| ID | ZONE |
+----+------+
|  3 | UN   |
+----+------+

Tiens, l'autoincrement débute à 3 ? 
Voyons voir un insert sur le nœud gale-3 (nœud 2) : 
insert into TT (ZONE) values ('DEUX'); 
L'ID généré est égal à 5. 
Un insert sur le nœud gale-4 (nœud 3) : 
insert into TT (ZONE) values ('TROIS'); 
L'ID généré est égal à 7; 
Retour sur le nœud 1 qui génère un ID à 9; 
En bref cela à l'air de bien fonctionner ... 
On essaye un arrêt de toutes les machines machines, puis on les redémarre, et ... c'est la catastrophe, rien ne redémarre. 
Il faut chercher quel était le nœud "maître" en examinant les fichiers "grastate.dat" (dans /var/lib/mysql) et chercher celui qui possède la ligne "safe_to_bootstrap: 1". 
Ici c'est le nœud "gale-4" qui a été stoppé en dernier ... donc il doit pouvoir repartir car il est réputé le plus à jour. Si aucun fichier ne comporte cette mention (cas de crash simultané) il faudra en éditer un "à la main" et mettre la valeur à 0. Dans le cas précédent le noeud "gale-4" démarre normalement, les autres peuvent alors démarrer et se connecter au cluster.

Si la situation est réellement "sérieuse" (refus complet de démarrage) mettre à jour le fichier "/var/lib/mysql/grastate.dat" de la machine stoppée en dernier et y forcer la valeur 0 dans la ligne "safe_to_bootstrap:", les autres membres du cluster pourront alors être démarrés avec un simple "systemctl start mariadb".

Utilisation du paramètre "wsrep_provider_options="gcache.recover=yes" ne change rien ... par contre en utilisant la commande "galera_new_cluster" sur le nœud 4 celui-ci démarre normalement et le démarrage des deux autres est alors possible ... Ouf! Le cluster se comporte ensuite normalement et il est possible de démarrer les autres systèmes avec "systemctl start mariadb".  
La commande "galera_new_cluster" ne fonctionne directement que sur la dernière machine stoppée du cluster afin d'être sûr qu'aucune donnée ne sera perdue. 
Souvenez-vous de cette commande ... qui peut servir en cas d'incident grave ou pour des machines de test que l'on stoppe souvent. 
Sinon il faut aller s'intéresser au fichier "/var/lib/mysql/grastate.dat" et, éventuellement (si la dernière machine stoppée est hors-service) et y passer la valeur de "safe_to_bootstrap" de 0 à 1. Mais le risque sera de perdre les dernières données traitées avant l'arrêt de la dernière machine du cluster.

Le redémarrage d'un seul des membres de notre cluster se passe fort bien et chacun des autres nœuds note que ce membre a disparu : 
2022-11-01 18:01:03 0 [Note] WSREP:  cleaning up e713c304-9429 (tcp://192.168.2.155:4567) 
192.168.2.155 est l'adresse du membre qui s'est absenté, lorsqu'il redémarre il se signale et les deux autres membres constatent ce fait avec un message : 
2022-11-01 18:03:24 0 [Note] WSREP: Member 0.0 (gale-4) synced with group. 
Une fonction qui peut être utile pour signaler ce type d'événement est disponible dans le paramétrage de Galera :

wsrep_notify_cmd = 'une commande shell' 
Cette commande permet de signaler les événements importants. 
J'en ai installé une très simple sur le nœud GALE_2 : 
#!/bin/bash 
FLOG=/var/tmp/MSG.LOG 
DTT=$(date +%Y/%m/%d_%H:%M:%S) 
echo ${DTT}' | '$* >>${FLOG} 
Lors du démarrage des machines (dans l'ordre inverse de celui dans lequel elles ont été stoppées, (le dernier membre actif doit redémarrer le premier) on obtient l'affichage suivant :

23:52:04 | --status connected
23:52:04 | --status joiner
23:52:04 | --status initializing
23:52:05 | --status initialized
23:52:05 | --status joined
23:52:05 | --status joined --uuid 28736ddb-59f8-11ed-abde-fe2234c75fO
23:52:05 | --status synced
23:53:15 | --status synced --uuid 28736ddb-59f8-11ed-abde-fe2234c75fO
23:53:15 | --status donor
23:53:15 | --status joined
23:53:15 | --status synced
23:54:42 | --status synced --uuid 28736ddb-59f8-11ed-abde-fe2234c75fO
23:54:42 | --status donor
23:54:42 | --status joined
23:54:42 | --status synced

C'est très anormal que toutes les machines soient stoppées, mais cela peut arriver, tant qu'il reste une machine en fonction les autres peuvent venir s'abonner au cluster et tout se passe bien.

Le seul problème est que lors de la "panne" d'une machine son adresse IP ne réponds plus et les clients n'ont plus accès aux données et établir un système de haute disponibilité entre trois machines n'est pas une sinécure. 
C'est là l'avantage d'utiliser Maxscale qui va, entre autres choses gérer l'accès à notre cluster. Maxscale permet de gérer des clusters disposant de plus de 3 nœuds, nous verrons cela dans un prochain chapitre ... à suivre 
 

Maxscale : paramétrage

Maxscale : paramétrage jpp

Cet article détaille, un peu, la mise en place de Maxscale.

Le paramétrage est relativement simple mais les dernières présentent un petit "piège" car l'adresse d'écoute par défaut est une adresse IPV6 et cela pourrait vous surprendre. Il est donc important de préciser "address=0.0.0.0" (ou une autre adresse IPV4) dans le paramétrage de Maxscale. 
D'abord la définition des serveurs du cluster Galera :

# Server definitions
#
[gale-2]
type=server
address=192.168.2.153
port=3306
protocol=MariaDBBackend
#
[gale-3]
type=server
address=192.168.2.154
port=3306
protocol=MariaDBBackend
#
[gale-4]
type=server
address=192.168.2.155
port=3306
protocol=MariaDBBackend

J'ai ensuite décidé de créer deux "voies d'accès" :

  1. Une en lecture seule
  2. Une en lecture/écriture
[lect-seule]
type=service
router=readconnroute
servers=gale-4
user=util_services
password=xxxxxxxx
router_options=running
# ReadWriteSplit documentation:
# https://mariadb.com/kb/en/mariadb-maxscale-6-readwritesplit/
[ecri-lect]
type=service
router=readwritesplit
servers=gale-2,gale-3
user=util_services
password=xxxxxxxx

Une fois ces deux services créés il faut leur affecter un "listener" qui permettra l'accès à nos services, nous aurons donc deux "listeners".

[Read-Only-Listener]
type=listener
service=lect-seule
protocol=MariaDBClient
port=3007
address=0.0.0.0
[Read-Write-Listener]
type=listener
service=ecri-lect
protocol=MariaDBClient
port=3006
address=0.0.0.0

C'est ici qu'il faut préciser l'adresse au format IPV4 pour éviter le piège de l'IPV6 par défaut.

Enfin le dernier paragraphe pour fixer les paramètres du monitoring de nos 3 serveurs :

# This will keep MaxScale aware of the state of the servers.
# MariaDB Monitor documentation:
# https://mariadb.com/kb/en/maxscale-6-monitors/
#
[MariaDB-Monitor]
type=monitor
module=mariadbmon
servers=gale-2,gale-3,gale-4
user=xxxxxxx
password=yyyyyyyy
monitor_interval=2s

Ne pas oublier de "protéger" ce fichier par :

chown maxscale: /etc/maxscale.cnf 
chmod 600  /etc/maxscale.cnf 

 

Maxscale GUI

Maxscale GUI jpp
Le GUI de gestion de Maxscale.

Il est accessible sur le port 8989 de la machine selon les paramètres du fichier de configuration :

# MAXGUI 
admin_host=192.168.2.152
# GUI without SSL
admin_secure_gui=false

Ici j'ai inhibé le SSL ce qu'il ne faudrait pas faire en production ... 
Le premier écran qui s'affiche est l'écran de surveillance (Dashboard) de notre cluster Galera première option a gauche dans le menu horizontal.

Ecran démarrage

Cet écran présente l'état de notre cluster, on y voit l'état de notre cluster "Running" en vert dans la colonne "STATE" et la liste de tous les membres du cluster avec le nombre de connexions, leur état (autre colonne "STATE"). 
Le master a droit à la couleur verte et au statut "Master, Running", les deux autres à une mension rouge et Running, donc tout va bien. La dernière colonne indique à quel "service" chaque machine est attachée. 
Un graphique, en haut de l'écran, montre la charge de chaque machine (nombre de sessions, connctions et le load). 
Un menu horizontal permet d'afficher : 
Les sessions courantes. Il est possible ici de "tuer" une session gênante. 
Les services avec les machines concernées et les les sessions en cours. 
Les listeners le nom du listener avec le port qu'ils utilisent, leur état (Running normalement !) et le nom du service. 
Les filtres actifs. Les filtres permettent de réaliser des actions en fonction de l'utilisateur, par exemple de "cacher" certaines données confidentielles .... 
En haut à droite de l'écran le bouton "Create New" permet de créer de nouveaux services, de nouveaux serveurs et des filtres. 
Pour les filtres voir la documentation de Maxscale qui présente toutes les possibilités.  
Depuis le menu principal les options suivantes :

  • Visualisation : permet d'afficher notre cluster en mode graphique.

 
 

  • Query-editor : permet d'exécuter des ordres SQL après s'être identifié auprès d'un service.
  • Users : permet de gérer les utilisateurs de Maxscale (pas ceux du cluster).
  • Logs archives : permet d'afficher le log de Maxscale.
  • Settings : permet de modifier les paramètres de Maxgui, et il y en a un bon paquet ! N'y toucher que si l'on sait ce que l'on fait. 
     

That's all folks.

Mysql/MariaDB : divers

Mysql/MariaDB : divers jpp

J'ai regroupé ici plusieurs notes ou articles parfois très anciens, le premier date de 2009.

Mysql : optimisation variable "key_buffer"

Mysql : optimisation variable "key_buffer" jpp

Cette page de 2009 reste d'actualité (au moins pour les tables MyISAM) et les remarques et conseils peuvent s'appliquer à la variable "innodb_buffer_size" qui remplit à peu près la même fonction pour InnoDB.

Sur quelle information optimiser la valeur de ce paramètre ? 
Ce paramètre est réservé aux tables "MyIsam", de plus en plus rares au profit des tables InnoDB.

KEY_BUFFER = taille réservée au buffer des index. Sa taille doit être assez importante, l'idéal est que tous les index résident en mémoire limitant ainsi les IO. 
Vous pouvez calculer cette taille en évaluant la taille des index de votre base de données. L'utilisation de MySQL-Admin vous permet de suivre "en direct" l'évolution de l'utilisation de ce cache. Voir dans la section "Health", onglet "Memory Health" de MySQL-Admin. La partie "Key efficiency" vous permet de voir la partie du buffer effectivement utilisée ainsi que le "hitrate". 
Le "hitrate" est le pourcentage de clés effectivement lues depuis le buffer. 
Attention : Ne pas tenir compte des valeurs présentes peu après un redémarrage, il faut attendre plusieurs heures que le fonctionnement de la base se stabilise et que l'utilisation corresponde à l'utilisation moyenne normale des applications liées à cette base.

INNODB_BUFFER_POOL_SIZE = taille réservée au buffer Innodb (data + index) 
Plus la valeur est élevée, meilleur est le fonctionnement de la base ... La mémoire étant bien moins rare qu'il y a quelques années on peut mettre ce qu'il faut. 
Si on a assez de mémoire pour contenir la taille de la base ne pas hésiter, mais calculer au moins le volume des tables les plus accédées plus 20 ou 30%.

INNODB_BUFFER_POOL_INSTANCES = Nombre de "sous pools" 
Une valeur de 1 par Gigaoctet de POOL_SIZE est à considérer comme correcte, mais mettre une valeur proche de celle du nombre de coeurs de processeur est une option à ne pas négliger.

  • Des buffers trop petits --> hitrate bas et :
    •     temps de réponse allongé
    •     IO plus élevées
  • Des buffers trop grands --> mémoire gachée.

Il n'est pas la peine de mobiliser 8 Gigaoctets de mémoire si seulement 50% sont utilisés, la mémoire économisée ici peut avec profit être utilisée ailleurs, c'est le cas de la plupart des paramètres de mémoire, trop petits cela ralentit, trop grands cela utilise inutilement de la mémoire. 
Si votre base est "petite" par rapport à la mémoire mettez ce qu'il faut, sur une machine spécialisée "base de données" 75/80% de la mémoire est parfait pour InnoDB. 
Toutefois si l'ensemble de vos tables ne "pèse" que 30 Go il est inutile de réserver 64 Go de mémoire !

TABLE_OPEN_CACHE = nombre de tables en cache. 
Une cette valeur égale au nombre de tables si cela est possible. Note : on trouve un tas de choses à ce sujet ... 
Une taille peu différente du nombre de tables de la base semble en général favorable.

Un autre point à surveiller est la mémoire affectée aux tris et autres zones temporaires (TMP_TABLE_SIZE et MAX_HEAP_TABLE_SIZE). Si certaines requêtes ramènent beaucoup de lignes à trier cela nécessite de la place et, là aussi, affecter un peu de mémoire n'est pas un luxe. En général 64 à 320Mo sont suffisants, surveiller le pourcentage de tables temporaires sur disque. La valeur de READ_IO_THREADS et WRITE_IO_THREADS peut être au minimum de 2, mais sur une machine multiprocesseurs aller presque jusqu'au nombre de coeurs est une bonne pratique ... 
La taille des READ, SORT et JOIN BUFFER_SIZE est à suivre et des valeurs de 2 à 16M sont en général adaptées.

Mais il faut aussi penser à l'activité de création/modification de la base et affecter un minimum de mémoire au INNO_DB_LOG_BUFFER_SIZE afin de fluidifier les écritures sur le log. 
A propos de accès disques, plus ils sont rapides, mieux c'est. L'utilisation de SSD permet d'atteindre des vitesses impressionnantes et la mise en RAID 1 permet d'augmenter encore ce type de performance avec toutefois un certain risque, le RAID 10 est parfait. Voir ici l'influence de la variable INNODB_IO_CAPACITY qui permet à la base d'optimiser son fonctionnement en fonction de la vitesse des I/O, en général de 100 à 1000 pour des disques standard et > 10000 pour des SSD. 
Sur des systèmes largement multi-processeurs on peut aussi jouer sur les paramètres INNODB_READ_IO_THREADS et INNODB_WRITE_IO_THREADS et les augmenter à 2,3 ... afin d'accélérer au maximum les IO, base en lecture --> forcer le nombre de READ_IO_THREADS, si beaucoup d'écritures forcer le nombre de WRITE_IO_THREADS. Toutefois si vous n'avez pas de SSD ou de multiples disque de données cela peut être très peu efficace. 
Pour l'ajustement de ces valeurs suivre l'évolution des compteurs et ratios fournis par un système de suivi de performance tel que Shinken + Graphite/InfluxDB ou d'autres produits non libres, Mysql Workbench fournit aussi l'accès à beaucoup d'informations utiles.

Erreur mise à jour MariaDB

Erreur mise à jour MariaDB jpp

Erreur mise à jour MariaDB. 
Mise à jour de  10.1.23-9+deb9u1 vers 10.1.26-0+deb9u1 
Lors de cette mise à jour normale par apt-get upgrade l'installation s'est "plantée" avec la trace suivante : 
regarding mariadb-server-10.1_10.1.26-0+deb9u1_amd64.deb containing mariadb-server-10.1, pre-dependency problem: 
mariadb-server-10.1 pre-depends on mariadb-common (>= 10.1.26-0+deb9u1) 
mariadb-common latest configured version is 10.1.23-9+deb9u1. 
...... 
update-alternatives: error: alternative path /etc/mysql/mariadb.cnf doesn't exist 

Une solution : 
cd /etc/mysql 
ln -s ./mariadb.conf.d/50-server.cnf ./mariadb.cnf 

Quand on relance l'installation du paquet "fautif" on obtient : 
dpkg -i mariadb-common_10.1.26-0+deb9u1_all.deb 
(Reading database ... 192496 files and directories currently installed.) 
Preparing to unpack mariadb-common_10.1.26-0+deb9u1_all.deb ... 
Unpacking mariadb-common (10.1.26-0+deb9u1) over (10.1.26-0+deb9u1) ... 
Setting up mariadb-common (10.1.26-0+deb9u1) ... 
update-alternatives: warning: forcing reinstallation of alternative /etc/mysql/mariadb.cnf because link group my.cnf is broken

Le paquet s'est bien installé, voyons voir l'installation des paquets "client" : 
dpkg -i mariadb-client*deb 
(Reading database ... 192496 files and directories currently installed.) 
Preparing to unpack mariadb-client-10.1_10.1.26-0+deb9u1_amd64.deb ... 
Unpacking mariadb-client-10.1 (10.1.26-0+deb9u1) over (10.1.26-0+deb9u1) ... 
Preparing to unpack mariadb-client-core-10.1_10.1.26-0+deb9u1_amd64.deb ... 
Unpacking mariadb-client-core-10.1 (10.1.26-0+deb9u1) over (10.1.26-0+deb9u1) ... 
Setting up mariadb-client-core-10.1 (10.1.26-0+deb9u1) ... 
Setting up mariadb-client-10.1 (10.1.26-0+deb9u1) ... 
Processing triggers for man-db (2.7.6.1-2) ... 
Processing triggers for menu (2.1.47+b1) ... 

C'est OK et les paquets "serveur" de MariaDB s'installent eux aussi correctement et une connexion à la base s'effectue normalement.

MariaDB : tables federated

MariaDB : tables federated jpp

Le plugin "Federatedx" responsable de la gestion des tables "federated" est livré en standard avec MariaDB mais n'est pas activé par défaut, le paramètre "federated", si présent dans les fichiers de configuration, provoque une erreur.

Pour quelques détails sur les tables "federated" voir ici. 

L'import sans précaution d'une base comprenant des tables "federated" ne donne pas les résultats escomptés, les tables sont bien créées, mais avec le type "InnoDB" ce qui n'est pas le but recherché. 
En effet l'objet "serveur" auquel sont rattachées les tables "federated" ne fait pas partie de l'export.

L'activation est très simple : 
Dans une console "mysql" administrateur : 
INSTALL SONAME 'ha_federatedx'; 
Ensuite jouter dans le fichier de config : 
federated 
On redémarre et c'est OK. 
Remarque ; 
Même en cas d'export/import la création du "serveur" doit être exécutée sur la nouvelle machine avant toute tentative de création d'une table "federated" sinon celle-ci sera créée en ="InnoDB".

Script mysql_slow_log_parser.pl

Script mysql_slow_log_parser.pl jpp

Ce script de :  Nathaniel Hendler   permet d'analyser finement le fameux "mysql-slow.log". 
Rappel : 
L'enregistrement des requêtes lentes ou des requêtes sans index se paramètre dans le fichier "my.cnf" : 

# Here you can see queries with especially long duration 
log_slow_queries    = /var/log/mysql/mysql-slow.log 
long_query_time = 2 
log-queries-not-using-indexes 

Ici les lignes sont décommentées afin d'activer l'enregistrement. 
Attention, assez consommateur de ressources,peu conseillé sur des machines de production qui ont déjà des problèmes de temps de réponse ..... 

Una analyse visuelle de ce fichier peut être intéressante mais devient rapidement rébarbative, heureusement "mysql_slow_log_parser.pl" est arrivé. 

Ce petit script Perl effectue un regroupement des "queries" coûteux en fournissant pour chaque query identifié :

  • le nombre d'occurences
  • le temps total utilisé et le temps moyen
  • le nombre de rangs analysés
  • un "modèle" du query, dépourvu des variables
  • un exemple de query avec les valeurs des variables

Ces résultats sont une mine d'or pour les développeurs car tous les ordres SQL critiques (criticables) apparaissent en clair avec même un exemple à soumettre à "EXPLAIN". 
L'utilisation de ce module aide à repérer :

  • les index manquants ou non judicieux
  • les requêtes mal faites ...

Le comptage de chaque type de requête permet de se pencher d'abord sur les plus critiques (les plus nombreuses/coûteuses). 

En bref un très bon outil que l'on peut trouver facilement sur Internet.

Mariadb 10.1.23 vers 10.1.26

Mariadb 10.1.23 vers 10.1.26 jpp

J'ai rencontré un problème bizarre lors d'une mise à jour de MariaDB.

La mise à jour se passe mal et l'installation du paquet "mariadb-server-10.1_10.1.26-0+deb9u1_amd64.deb" échoue avec le message : 
E: mariadb-server-10.1: subprocess installed post-installation script returned error exit status 2

En examinant les logs on trouve un message : 
[ERROR] /usr/sbin/mysqld: unknown option '--federated' 
[ERROR] Aborting 

Si on commente dans le fichier "50-server.cnf" la ligne "federated" le démarrage se passe bien. 
Il faut alors terminer la mise à jour "ratée" ... 
On peut alors dé-commenter la ligne "federated" et relancer MariaDB.

​Et cela fonctionne.

En effet le plugin "federated" a été déclaré obsolete et doit être remplacé par "ha_federatedx" ou il faut utiliser mariadb-connect ou FederatedX.

MariaDB : Renommer une partition

MariaDB : Renommer une partition jpp

Le partitionnement c'est parfois très commode et cela permet de débarrasser une base opérationnelle de données "anciennes" qui sont mieux placées dans des archives. 
C'est ce qui a été fait chez un client où les données d'opération sont reportées dans des tables d'archives dès qu'elles sont jugées inutiles aux opérations courantes, elles restent ainsi disponibles pour des statistiques, des études commerciales et ... 
Toutefois au bout de quelques mois elles sont utilisées de plus en plus rarement et leur présence n'est plus justifiée dans la base opérationnelle.  
Le partitionnement par semaine/mois permet de se débarrasser facilement et rapidement de ces données par un simple "ALTER TABLE TRUNCATE PARTITION XXXXX" qui supprime l'intégralité du contenu en quelques instants au lieu d'heures à défiler des ordres "DELETE. 
Or lors de la création d'une de ces tables d'archives une petite erreur de frappe s'est glissée, le nom des partitions comporte une référence directe au numéro de mois. Les partitions sont donc nommées de P01 à P12, un processus automatique supprime les partitions du mois N avec un ordre de troncature de la partition "PN", la faute de frappe concernait la partition "P11" malheureusement orthographiée "P21" ce qui aurait provoqué le plantage du processus d'archivage/purge lorsqu'il aurait voulu traiter la partitions P11 associée au mois 11. 
Mais quel ordre SQL permet de faire cela ? Eh bien non il n'y a pas d'ordre dédié au renommage de partitions ... il faut utiliser la réorganisation de partitions avec l'ordre suivant :

ALTER TABLE TABLE_IDIOTE_ARCHIVES

REORGANIZE PARTITION P21 INTO ( PARTITION P11 VALUES in (11) );

Et le tour est joué en ... quelques temps, car ici la partition contient plus de 50 millions de rangs et occupe environ 8Go, mais cela s'est bien exécuté en un temps raisonnable :

Query OK, 52893529 rows affected (9 min 27.36 sec)

Records: 52893529  Duplicates: 0  Warnings: 0

Oui, c'est une belle base avec une activité certaine dont voici quelques chiffres tirés de la fin d'un "SHOW ENGINE INNODB STATUS" :

Number of rows inserted 7201350564, updated 16196473370, deleted 3242922826, read 14571828912181

528.20 inserts/s, 2777.79 updates/s, 205.45 deletes/s, 2109444.41 reads/s

Mot de passe root perdu

Mot de passe root perdu jpp

Vous avez perdu le mot de passe "root" de votre belle installation de MariaDB !

Note 2021 : Cela ne fonctionne plus avec les versions récentes de MariaDB car la table mysql.user n'est plus qu'une vue vers une structure plus complexe, il faut obligatoirement passer par un "init_file" pour re-créer un mot de passe "root". Ce fichier "init_file" peut être exécuté au démarrage de MariaDB, avant le démarrage complet, et permet d'exécuter des commandes en passant outre à la vérification des droits. A ne pas laisser entre toutes les mains ... et à bien supprimer du fichier de paramétrage après usage !

Pas de panique la procédure suivante vous sortira de l'embarras (à condition de stopper votre base quelques instants ce qui peut ne pas être évident pour une base de production ... mais pour celles la vous avez soigneusement sauvegardé les mots de passe dans un gestionnaire spécialisé).

  • Stopper MariaDB
  • En root lancer lé commande suivante :

mysqld_safe --skip-grant-tables --skip-networking & 
Ici aucun accès ne sera possible aux commandes "classiques" "SET PASSWORD..." ou "ALTER USER ..." il faut mettre à jour directement la table "mysql.user" :

mysql -u root 
UPDATE mysql.user SET authentication_string = PASSWORD('nouveau_mot_de_passe')  
where user = 'root' 
and host = 'localhost'; 
commit; 
\q

Il suffira alors de stopper (gentiment) notre "mysqld_safe" avec la commande : 
mysqladmin -u root shutdown

Puis de relancer MariaDB avec systemctl et se retrouver dans un état normal.

Ceci fonctionne parfaitement avec un MariaDB 10.3