MySQL InnoDB Cluster avec Group Replication

Nous allons nous intéresser à la solution HA native de MySQL : MySQL InnoDB Cluster avec le plugin Group Replication disponible depuis la version 5.7.

Attention à ne pas confondre cette technologie avec MySQL NDB Cluster, qui repose quant à lui sur un moteur de stockage NDB.

MySQL HA : les différents mode de réplication

  • Asynchrone => MySQL Replication
    • Il s’agit de la réplication par défaut de MySQL. De manière parallèle, MySQL acquitte les transactions côté application et réplique les transactions vers le(s) slave(s).
    • Les performances sont au rendez-vous, mais au détriment du risque de perte de données en cas de défaillance de la base Master.
  • Semi-Synchrone => MySQL Replication avec plugin semisynchronous
    • A partir de MySQL 5.5+ et amélioré en MySQL 5.7.
    • La Master attends que les transactions soient reçues par la Slave et de manière parallèle acquitte les transactions coté client et applique les changements sur la Slave.
    • Performance intermédiaires, mais sans perte de données
  • Virtuellement-Synchrone => MySQL InnoDB Cluster avec MySQL Group Replication
    • Les transactions sont appliquées de manière parallèle sur tous les nœuds du cluster.
    • Mode Multi-Master disponible.
    • Performances intermédiaires, idéal pour les petites transactions. Pas de perte de données.
  • Synchrone => MySQL NDB Cluster
    • Le cluster attend que tous les nœuds du cluster aient appliqué la transaction avant d’acquitter la transaction côté applicatif.
    • Latences plus élevées, idéal pour les petites transactions. Pas de perte de données.

Sur quoi repose MySQL InnoDB Cluster ?

MySQL InnoDB cluster =

  • MySQL Group Replication
  • + MySQL Shell
  • + MySQL Router

MySQL Group Replication est un plugin MySQL développé par Oracle et disponible depuis la version 5.7.17.

MySQL Shell est une interface en ligne de commande (CLI), qui permet d’administrer le cluster au travers de l’AdminAPI.

MySQL Router permet la redirection automatique des connexions clientes vers les nœuds du cluster.

Group Réplication : le cœur de MySQL InnoDB Cluster

Le plugin Group Replication developpé par Oracle est disponible sur toutes les plateformes supportées par MySQL (Linux, Windows, Solaris, OSX, Freebsd). Comme il est nativement intégré dans le serveur MySQL et s’intègre aux composants de la base (InnoDB, Réplication, GTID, Performance Schema, …), il n’y a aucun besoin de télécharger de plugin supplémentaire.

Le plugin permet l’écriture simultanée sur l’ensemble du cluster (cluster nodes) de manière simultanée en préservant la consistance des données.

Chacun des nœuds représente une instance de la même base de données.

Un minimum de 3 membres est nécessaire, dû à la nécessité du cluster d’obtenir un quorum pour éviter les situations de split-brain. Le système est donc tolérant aux pannes et permet de gérer les crash et les pannes avec une reconnexion automatique des nœuds au cluster.

Les failovers et les transitions de rôles sont automatiques en cas de défaillance, il n’y a pas besoin d’actions manuelles ni de scripting complexe.

Group Replication permet également de créer des topologies permettant les mises à jour sur tous les nœuds (topologies Multi-Master).

La mise en place de la Haute-Disponibilité est donc grandement facilitée sur MySQL.

Cas d’usages

Scaling-out

Une utilisation typique de Group Replication est le scaling-out des lectures. Si une application requiert un nombre plus important de lectures au cours de la montée en charge, il est possible d’ajouter très simplement de nouvelles Slaves en les intégrant au cluster.

Tolérance aux pannes / Haute disponibilité

Que l’on ait déployé une topologie Single-Primary ou Multi-Primary, la haute disponibilité est gérée de manière quasi transparente pour les applications. On peut donc de manière élastique gérer de manière dynamique le nombre de serveurs nécessaires en fonction du workload.

Gestion des transactions

  • Execution Locale : Group Replication commence à répliquer une transaction lors de la phase de COMMIT, juste avant d’écrire dans les binary logs
  • Envoi vers tous les membres du cluster : Les transactions sont alors broadcastées sur le réseau via le protocole de communication Paxos
  • Certification indépendante de chaque transaction par les nœuds : Chaque membre du cluster reçoit les transactions dans l’ordre et exécute les algorithmes de certification afin de déterminer si les transactions peuvent être appliquée de manière sure.
  • Application Asynchrone : sur chacun des nœuds distants, les transactions certifiées sont écrites dans les relay-logs et appliquées de manière asynchrone. Sur le nœud local, la transaction peut alors être commitée dès lors qu’elle a reçu l’acquittement de la majorité des nœuds du cluster.

 

Résolution des conflits

Lors d’une transaction, un lock local (InnoDB) a lieu. Le moteur de réplication assume de manière optimiste (Optimistic Locking) qu’il n’y aura pas de conflit sur les autres nœuds (aucune communication n’a lieu vers les autres serveurs à ce stade).

La résolution de conflit a lieu par la suite lors de la phase de certification au moment du commit : Le premier à réaliser le commit l’emporte.

L’inconvénient de l’Optimistic Locking est qu’il est plus susceptible de se produire de conflits lors des transactions longues ou volumineuses.

Les pré-requis de Group Replication

  • Pas de support des tables sans PK,  toutes les tables à répliquer doivent posséder une clé primaire
  • Les données doivent être stockées dans le moteur InnoDB
  • IPv4 uniquement : la communication utilisée entre les serveurs du cluster se fait pour le moment uniquement via iPv4
  • Group Réplication est conçu pour être déployé sur un cluster dont les nœuds sont proches géographiquement. La latence et la bande passante sont des critères décisif pour garantir de bonnes performances.
  • Activation des log-bin nécessaire et Binlog_format = ROW
  • Activation des GTID (Global Transaction Identifiers)
  • Les metadonnées de réplication doivent être stockées dans les tables system :
    • –master-info-repository=TABLE
    • –relay-log-info-repository=TABLE
  • Writeset extraction activé : –transaction-write-set-extraction=XXHASH64
  • log-slave-updates activé

MySQL Router

MySQL Router est un middleware qui fournit un service de routage transparent entre les applicatifs et les serveurs MySQL backend.

L’interfaçage (bootstrap) avec les métadonnées du cluster InnoDB est automatique, il n’y pas de configuration à effectuer. Le routage est alors  automatique vers les serveurs MySQL appropriés en fonction des rôles de ces derniers (Primary / standby read-Only).

MySQL Router propose deux endpoints sur deux ports différents : un pour les accès Read/Write, un pour les accès Read-Only. C’est donc à l’application de choisir vers quel endpoint s’adresser, il n’y a pas de fonctionnalités de type Read/write splitting.

Toutefois, Il est possible d’utiliser le cluster InnoDB avec des Routeurs/proxy tiers (ProxySQL supporte Group Replication), afin de bénéficier de telles fonctionnalités avancées.

Typiquement, on installera le MySQL Router sur chaque serveur d’application ou sur chacun des serveurs de bases de données.

MySQL Shell

MySQL Shell est l’utilitaire en ligne de commande utilisé pour administrer le cluster. Il permet de masquer la complexité de configuration, de provisionning et d’Orchestration du cluster au travers de l’AdminAPI :

  • create clusters
  • get cluster info
  • start/stop MySQL Instances
  • Validate MySQL Instances…

Il permet le scripting et est multi-langage : Javascript, Python et SQL. Il peut être utilisé en mode batch ou interactif.

Supervision et Monitoring

MySQL Enterprise Monitor 4.0 supporte de manière native les clusters Group Replication :

  • Monitoring et alerting
  • Représentation graphique des topologies des clusters
  • Métriques détaillées et graphiques associés
  • Suggestion de best-practices

Les vues du schema performance_schema permettent également d’interroger le statut de réplication du cluster.

Exemple de déploiement d’un Cluster InnoDB

Afin de pouvoir déployer un cluster, il faut au minimum 3 serveurs. Toutefois, MySQL permet de réaliser une installation de type Sandbox afin de se familiariser avec la technologie, plusieurs membres du même cluster sont alors installés sur le même host. Nous ne traiterons pas ici le cas d’installation de type Sandbox.

Creation du user d’administration

Nous commençons tout d’abord par créer un user d’administration du cluster :

mysql >CREATE USER innodb_user@'%' IDENTIFIED BY 'mon_password'  ;
mysql >GRANT ALL PRIVILEGES ON mysql_innodb_cluster_metadata.* TO innodb_user@'%' WITH GRANT OPTION;
mysql >GRANT RELOAD, SHUTDOWN, PROCESS, FILE, SUPER, REPLICATION SLAVE, REPLICATION CLIENT,  CREATE USER ON *.* TO innodb_user@'%' WITH GRANT OPTION;
mysql>GRANT SELECT ON *.* TO innodb_user@'%' WITH GRANT OPTION;
mysql>GRANT DELETE, INSERT, UPDATE ON *.* TO innodb_user@'%' WITH GRANT OPTION;
mysql>flush privileges;

Ajout des pré-requis

Afin de permettre à la GROUP REPLICATION de fonctionner, les paramètres suivants sont obligatoires et doivent être ajoutés au fichier my.cnf se situant dans le répertoire d’installation de mysql/

Ajouter ces paramètres à la fin de la section mysqld :

binlog_checksum=NONE
enforce_gtid_consistency=ON
gtid_mode=ON
log_bin=1
log_slave_updates=ON
master_info_repository=TABLE
relay_log_info_repository=TABLE
server_id=1
transaction_write_set_extraction=XXHASH64

Chaque serveur MySQL doit avoir un server_id différent.

Vérification des pré-requis

$>mysqlsh

MySQL  JS > dba.checkInstanceConfiguration('innodb_user@server1:3306')

Please provide the password for 'innodb_user@server1:3306': ********

Validating local MySQL instance listening at port 3306 for use in an InnoDB cluster...

This instance reports its own address as server1

Clients and other cluster members will communicate with it through this address by default. If this is not correct, the report_host MySQL system variable should be changed.

Checking whether existing tables comply with Group Replication requirements...

No incompatible tables detected

Checking instance configuration...

Note: verifyMyCnf option was not given so only dynamic configuration will be verified.

Instance configuration is compatible with InnoDB cluster

The instance 'server1' is valid for InnoDB cluster usage.
{
"status": "ok"
}

Installation du plugin group_replication

mysql> INSTALL PLUGIN group_replication SONAME 'group_replication.so';

Query OK, 0 rows affected (0.02 sec)

A partir de ce stade, la base de données Master faisant office de référence, est prête à être dupliquée vers les autres membres.

Copie Physique de la base master

Nous réalisons à cette étape une copie de la base de données modèle afin de la dupliquer sur les autres serveurs.

Cette copie peut se faire au choix en utilisant différents systèmes de sauvegarde/restore (mysqldump, mysql backup) ou bien par copie physique des fichiers (arrêt de la base nécessaire).

Avant de démarrer chaque instance de nouveau nœuds, il est nécessaire d’effectuer les taches suivantes.

Générer un nouveau server-uuid et modifier le server-id

Après avoir importé une copie du nœud master, il est impératif de générer un nouveau server-uuid, sur chaque nouveau nœud :

Il suffit de supprimer le fichier auto.cnf. Un nouveau server-uuid sera automatiquement généré au prochain démarrage.

Comme mentionné précédemment, il est nécessaire de modifier le server-id dans le my.cnf afin que celui-ci soit unique.

L’instance peut alors être démarrée.

Configuration de chaque instance pour MySQL Group Replication

$>mysqlsh

MySQL  JS > dba.configureLocalInstance('innodb_user@localhost:3306')

Please provide the password for 'innodb_user@localhost:3306': ********

Configuring local MySQL instance listening at port 3306 for use in an InnoDB cluster...

This instance reports its own address as server1

The instance 'localhost:3306' is valid for InnoDB cluster usage.

 

Création du cluster InnoDB

La creation du cluster s’effectue depuis le nœud primary (ici nœud ayant pour server-id = 1 sur server1).

Nous créons ici un cluster en mode « single-primary » et l’appellons « prodCluster ».

mysqlsh --uri innodb_user@server1:3306

MySQL  server1 JS > var cluster = dba.createCluster('prodCluster')

A new InnoDB cluster will be created on instance 'innodb_user@server1:3306'.

Validating instance at server1:3306...

This instance reports its own address as server1

Instance configuration is suitable.

Creating InnoDB cluster 'prodCluster' on 'innodb_user@server1:3306'...

WARNING: On instance 'server1:3306' membership change cannot be persisted since MySQL version 5.7.17 does not support the SET PERSIST command (MySQL version >= 8.0.5 required). Please use the <Dba>.configureLocalInstance command locally to persist the changes.

Adding Seed Instance...

Cluster successfully created. Use Cluster.addInstance() to add MySQL instances.

At least 3 instances are needed for the cluster to be able to withstand up to one server failure.


Le cluster est initialisé, nous pouvons ajouter à présent chacun des nœuds du cluster. Pour le 2ème nœud :

MySQL  server1JS > cluster.addInstance('innodb_user@server2:3306');

A new instance will be added to the InnoDB cluster. Depending on the amount of

data on the cluster this might take from a few seconds to several hours.

Please provide the password for 'innodb_user@server2:3306': ********

Adding instance to the cluster ...

Validating instance at server2:3306...

This instance reports its own address as cdc-s80D5wb

Instance configuration is suitable.

WARNING: On instance 'server2:3306' membership change cannot be persisted since MySQL version 5.7.17 does not support the SET PERSIST command (MySQL version >= 8.0.5 required). Please use the <Dba>.configureLocalInstance command locally to persist the changes.

WARNING: On instance 'server1:3306' membership change cannot be persisted since MySQL version 5.7.17 does not support the SET PERSIST command (MySQL version >= 8.0.5 required). Please use the <Dba>.configureLocalInstance command locally to persist the changes.

The instance 'innodb_user@server2:3306' was successfully added to the cluster.

Procéder de même pour le 3ème serveur.

Les warnings remontés par l’outil mysql shell nous indiquent qu’il faut à présent faire « persister » dans le fichier my.ini les changements de configuration dû à la configuration du cluster. Nous sommes ici sur du MySQL 5.7 et la commande SET PERSIST n’est disponible qu’à partir de la version 8.

Chaque ajout de nouveau nœud nécessitant de faire persister les modifications de configuration du cluster, nous réalisons sur chacun des nœud l’application des modification une fois tous les nœuds ajoutés au cluster.

Enregistrement de la configuration du cluster sur chaque nœud

Comme mentionné précédemment, nous faisons persister la configuration cluster grâce à mysql shell, au travers de la commande « dba.configureLocalInstance ».

Ainsi, localement sur chacun des nœuds, nous lançons la commande :

$>mysqlsh

MySQL  JS > \connect innodb_user@localhost:3306

Creating a session to 'innodb_user@localhost:3306'

Enter password: ********

Fetching schema names for autocompletion... Press ^C to stop.

Your MySQL connection id is 90

Server version: 5.7.17-log MySQL Community Server (GPL)

No default schema selected; type \use <schema> to set one.

MySQL  localhost:3306  JS > dba.configureLocalInstance('innodb_user@localhost:3306')

Please provide the password for 'innodb_user@localhost:3306': ********

The instance 'localhost:3306' belongs to an InnoDB cluster.

Detecting the configuration file...

Default file not found at the standard locations.

Please specify the path to the MySQL configuration file: /etc/my.cnf

Persisting the cluster settings...

The instance 'localhost:3306' was configured for use in an InnoDB cluster.

The instance cluster settings were successfully persisted.

 

Confirmation du fonctionnement du cluster

Afin de s’assurer que le cluster est bien fonctionnel, nous interrogeons ce dernier pour connaitre son statut ainsi que sa topologie, depuis n’importe quel nœud nous utilisons mysql shell :

mysqlsh
MySQL  JS > connect innodb_user@localhost:3306
MySQL  localhost:3306  JS > var cluster = dba.getCluster()
 MySQL  localhost:3306  JS > cluster.status()
{
    "clusterName": "prodCluster",
    "defaultReplicaSet": {
        "name": "default",
        "primary": "server1:3306",
        "ssl": "DISABLED",
        "status": "OK",
        "statusText": "Cluster is ONLINE and can tolerate up to ONE failure.",
        "topology": {
            "server1:3306": {
                "address": "server1:3306",
                "mode": "R/W",
                "readReplicas": {},
                "role": "HA",
                "status": "ONLINE"
            },
            "server2:3306": {
                "address": "server2:3306",
                "mode": "R/O",
                "readReplicas": {},
                "role": "HA",
                "status": "ONLINE"
            },
            "server3:3306": {
                "address": "server3:3306",
                "mode": "R/O",
                "readReplicas": {},
                "role": "HA",
                "status": "ONLINE"
            }
        }
    },
    "groupInformationSourceMember": "mysql://innodb_user@localhost:3306"
}

Nous voyons ici que le nœud server1 est master (R/W) et les deux autres nœuds sont slave (R/O).

Le cluster est fonctionnel.

Déploiement de MySQL Router

Nous déployons ici MySQL Router sur les serveurs d’application de la manière suivante :

$>mysqlrouter --bootstrap innodb_user@server1:3306
Please enter MySQL password for innodb_user:
Bootstrapping system MySQL Router instance...

MySQL Router  has now been configured for the InnoDB cluster 'prodCluster'.

The following connection information can be used to connect to the cluster.

Classic MySQL protocol connections to cluster 'prodCluster':
- Read/Write Connections: localhost:6446
- Read/Only Connections: localhost:6447