12. Réplication multi-nœud (édition Cluster)
12.1 Présentation#
L'édition Cluster de MIRAJ ajoute la réplication multi-nœud au moteur : un serveur primaire reçoit les écritures, et un ou plusieurs serveurs secondaires reçoivent en continu le journal du primaire, le rejouent et restent accessibles en lecture par les applications clientes.
Principes :
- Un seul primaire à la fois. Il exécute toutes les instructions qui écrivent (DML, DDL, comptes) et journalise chacune d'elles.
- Des secondaires en lecture seule, qui rejouent le journal du primaire dans le même ordre et aux mêmes numéros de séquence (LSN). Un client peut s'y connecter comme à n'importe quel serveur MIRAJ, sur le port habituel des clients (7007 par défaut), et y exécuter des lectures.
- Réplication asynchrone par défaut, semi-synchrone au choix : par défaut le primaire n'attend pas les secondaires pour rendre la main au client qui écrit (voir les limites, §12.7) ; une session, ou tout le serveur, peut demander qu'une écriture ne soit confirmée qu'une fois reçue (ou appliquée) par un ou plusieurs secondaires (§12.8).
- Promotion manuelle et contrôlée, sans élection automatique ni consensus : c'est l'administrateur qui désigne le primaire, au démarrage du cluster et lors d'une bascule ; le nœud promu vérifie d'abord auprès de ses pairs que la bascule est sûre et récupère ce que le secondaire le plus avancé a reçu de plus que lui (§12.4.1).
Ce que cela apporte :
- Haute disponibilité en lecture : si le primaire devient indisponible, les secondaires continuent de répondre aux lectures, et l'un d'eux peut être promu primaire pour reprendre les écritures.
- Répartition de charge en lecture : des rapports, exports ou tableaux de bord peuvent interroger un ou plusieurs secondaires sans peser sur le serveur qui traite les écritures.
Ce que l'édition Cluster n'apporte pas en l'état : il n'y a ni répartition des données entre plusieurs nœuds (chaque nœud porte une copie complète de chaque base), ni bascule automatique du primaire, ni consensus entre nœuds. Ces sujets appartiennent à une trajectoire ultérieure du produit et ne sont pas couverts par ce chapitre (voir le chapitre sur les limites connues pour ce qui est aujourd'hui hors de portée).
12.2 Configuration#
12.2.1 Le fichier cluster.toml#
Un nœud du cluster est configuré par un fichier cluster.toml, lu par un analyseur maison d'un sous-ensemble de TOML (tables [node] et [cluster], clés nues, chaînes entre guillemets, entiers, booléens, tableaux de chaînes sur une seule ligne, commentaires #). Par défaut, MIRAJ cherche cluster.toml à côté de l'exécutable miraj-server ; l'option --cluster-config <fichier> permet d'en indiquer un autre.
Sans fichier cluster.toml, un serveur MIRAJ se comporte exactement comme en dehors du cluster : aucun port entre nœuds n'est ouvert, rien n'est journalisé pour la réplication, le comportement est strictement celui de l'édition sans réplication.
Exemple commenté :
[node]
id = "n1" # identifiant stable du nœud (lettres, chiffres, _ et $, 64 caractères au plus)
listen = "0.0.0.0:7107" # port d'écoute entre nœuds : distinct du port client (7007 par défaut)
advertise = "10.0.0.1:7107" # adresse annoncée aux autres nœuds (par défaut : listen)
[cluster]
name = "gestium-prod" # nom du cluster : un nœud d'un autre cluster est refusé à la connexion
seeds = ["10.0.0.1:7107", "10.0.0.2:7107"] # adresses des pairs (la propre adresse annoncée du nœud est ignorée dans cette liste)
tls_cert = "node.pem" # certificat de ce nœud, chemins relatifs à cluster.toml
tls_key = "node-key.pem" # clé privée de ce nœud
tls_ca = "cluster-ca.pem" # autorité qui a signé tous les certificats des nœuds du cluster
# Facultatifs (valeurs par défaut indiquées)
ack = "written" # "written" (le secondaire a écrit le lot dans son journal) ou "durable"
# (le secondaire l'a en plus vidé sur disque) avant d'accuser réception
journal_retention_mb = 1024 # au-delà, un secondaire resté trop longtemps injoignable est réamorcé
# par copie complète des bases plutôt que rattrapé par le journal
max_promotion_lag_mb = 0 # 0 : illimité ; sinon, promotion refusée (sauf 'force_primary') si le
# candidat, après rattrapage, reste à plus de N Mio de la dernière
# position connue du primaire
promotion_catchup_mb = 64 # journal retenu par chaque secondaire pour servir le rattrapage d'un
# pair promu (0 : aucune retenue)
sync_commit = "off" # réplication semi-synchrone (§12.8) : "off", "received", "applied" ou
# "majority", valeur initiale de @@GLOBAL.cluster_sync_commit
sync_replicas = 1 # accusés de secondaires requis par écriture
sync_timeout_ms = 10000 # attente maximale d'une écriture, en millisecondes
sync_timeout_action = "fallback" # au délai : "fallback", "error" ou "wait"
mode = "manual" # "manual" (primaire désigné par l'administrateur) ou "raft" (élu, §12.9)
election_timeout_ms = 3000 # mode raft : délai sans nouvelle du leader avant candidature (≥ 500)
test_hooks = false # crochets de test (isolement d'un nœud) : jamais en productionTable [node] :
| Clé | Obligatoire | Description |
|---|---|---|
id | oui | Identifiant stable du nœud, un identifiant valide MIRAJ (64 caractères au plus). Sert de base à @@server_id et apparaît dans information_schema.MIRAJ_NODES. |
listen | oui | Adresse hôte:port d'écoute pour les connexions des autres nœuds. Doit être un port différent du port client du serveur. |
advertise | non (par défaut : listen) | Adresse hôte:port que ce nœud annonce aux autres. Doit être une adresse effectivement joignable par les pairs : 0.0.0.0 est refusé au démarrage. Le certificat TLS de ce nœud doit porter cette adresse (nom DNS ou IP) dans son SAN. |
Table [cluster] :
| Clé | Obligatoire | Description |
|---|---|---|
name | oui | Nom du cluster. Un nœud qui annonce un autre nom est refusé à la poignée de main. |
seeds | non (par défaut : vide) | Liste des adresses hôte:port des pairs à contacter. L'adresse annoncée de ce nœud lui-même, si elle y figure, est ignorée. |
tls_cert | oui | Certificat de ce nœud (chemin relatif au dossier de cluster.toml). |
tls_key | oui | Clé privée correspondante. |
tls_ca | oui | Certificat de l'autorité qui a signé les certificats de tous les nœuds du cluster. |
ack | non (par défaut : written) | written ou durable : ce qu'un secondaire doit avoir fait avant d'accuser réception d'un lot du journal. |
journal_retention_mb | non (par défaut : 1024) | Au-delà de cette taille de journal retenue pour un secondaire absent, celui-ci est réamorcé par copie complète des bases à sa reconnexion plutôt que rattrapé. |
max_promotion_lag_mb | non (par défaut : 0, illimité) | Retard maximal (Mio, toutes bases cumulées) qu'un secondaire peut garder, après rattrapage, envers la dernière position du journal que le primaire lui a annoncée, pour être promu par 'primary'. Au-delà, la promotion est refusée (erreur 9003) ; 'force_primary' passe outre. |
promotion_catchup_mb | non (par défaut : 64) | Journal que chaque secondaire garde, par base, au-delà de ce qu'il a appliqué, pour servir le rattrapage d'un pair promu (§12.4.1). 0 : aucune retenue ; un pair plus avancé ne peut alors généralement plus servir ce qui manque au candidat, et la promotion contrôlée est refusée (voir §12.4.4). |
sync_commit | non (par défaut : off, majority en mode raft) | Valeur initiale de @@GLOBAL.cluster_sync_commit (§12.8) : off, received, applied ou majority. |
sync_replicas | non (par défaut : 1) | Valeur initiale de @@GLOBAL.cluster_sync_replicas : nombre de secondaires qui doivent accuser réception d'une écriture (entier ≥ 1). |
sync_timeout_ms | non (par défaut : 10000) | Valeur initiale de @@GLOBAL.cluster_sync_timeout : attente maximale des accusés, en millisecondes (≥ 1). |
sync_timeout_action | non (par défaut : fallback, error en mode raft) | Valeur initiale de @@GLOBAL.cluster_sync_timeout_action : fallback, error ou wait (§12.8.3). |
mode | non (par défaut : manual) | manual : le primaire est désigné par l'administrateur (§12.3, §12.4). raft : il est élu par une majorité des membres (§12.9) ; les membres votants sont les seeds, qui doivent alors contenir l'adresse annoncée de ce nœud (erreur au démarrage sinon), deux au moins (deux membres : accepté avec un avertissement, aucune panne tolérée). |
election_timeout_ms | non (par défaut : 3000) | Mode raft : délai sans nouvelle du leader au-delà duquel un nœud se présente (tiré au sort entre une et deux fois cette valeur), aussi bail du leader. Entre 500 et 600 000. |
test_hooks | non (par défaut : false) | Crochets réservés aux tests automatisés (isolement d'un nœud par un fichier témoin miraj/cluster-isolate). Ne jamais activer en production. |
Au premier démarrage d'un nœud (aucun état de cluster enregistré), tous les nœuds démarrent secondaires : un cluster ne démarre jamais avec deux primaires par défaut. C'est à l'administrateur de désigner le primaire une fois (§12.3).
12.2.2 Certificats TLS mutuels#
Les nœuds se parlent uniquement en TLS mutuel : chaque nœud présente aux autres un certificat signé par l'autorité du cluster (tls_ca), et vérifie de même le certificat de chaque pair auquel il se connecte ou qui se connecte à lui. Le certificat de chaque nœud doit porter son adresse advertise (nom DNS ou adresse IP) dans son SAN (subjectAltName), et un usage étendu couvrant à la fois serverAuth et clientAuth (un nœud est à la fois serveur et client TLS vis-à-vis de ses pairs).
Outil miraj-cluster-pki. MIRAJ fournit un petit outil en ligne de commande, indépendant du serveur, qui crée l'autorité du cluster et signe les certificats des nœuds (clés ECDSA P-256, sans openssl) :
# Une seule fois, sur le poste d'administration
miraj-cluster-pki init-ca --name gestium-prod --out pki
# Pour chaque nœud, avec son adresse annoncée (`advertise`) en --san (répétable : IP ou nom DNS)
miraj-cluster-pki issue-node --ca pki --id n1 --san 10.0.0.1 --san n1.exemple.local --out pki/n1
miraj-cluster-pki issue-node --ca pki --id n2 --san 10.0.0.2 --out pki/n2
miraj-cluster-pki issue-node --ca pki --id n3 --san 10.0.0.3 --out pki/n3init-ca produit cluster-ca.pem et cluster-ca-key.pem (validité de 10 ans par défaut, --days pour changer) ; issue-node produit node.pem, node-key.pem et une copie de cluster-ca.pem (validité de 825 jours par défaut), prêts à être copiés dans le dossier de cluster.toml du nœud. Gardez cluster-ca-key.pem hors des nœuds : elle signe les certificats, et seul cluster-ca.pem est distribué. Un fichier existant n'est jamais écrasé sans --force.
Vous pouvez aussi utiliser votre propre PKI (autorité d'entreprise existante). À titre indicatif, voici l'équivalent avec openssl (clés EC P-256) — à adapter avec vos propres clés et une durée de validité raisonnable, ces valeurs d'exemple n'étant pas destinées à un usage réel :
# Autorité du cluster
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out cluster-ca-key.pem
openssl req -new -x509 -key cluster-ca-key.pem -out cluster-ca.pem -days 3650 -sha256 \
-subj "/CN=Autorité du cluster gestium-prod" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign,cRLSign"
# Certificat d'un nœud (répéter pour chaque nœud, avec son adresse annoncée dans le SAN)
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out node-key.pem
openssl req -new -key node-key.pem -subj "/CN=n1" -out node.csr
openssl x509 -req -in node.csr -CA cluster-ca.pem -CAkey cluster-ca-key.pem -CAcreateserial \
-days 825 -sha256 -out node.pem \
-extfile <(printf "basicConstraints=critical,CA:FALSE\nkeyUsage=critical,digitalSignature,keyEncipherment\nextendedKeyUsage=serverAuth,clientAuth\nsubjectAltName=IP:10.0.0.1,DNS:n1.exemple.local\n")
rm node.csrDistribuez ensuite cluster-ca.pem à tous les nœuds, et à chaque nœud son propre node.pem/node-key.pem. Un certificat signé par une autre autorité que celle indiquée dans tls_ca est refusé au moment de la poignée de main entre-nœuds.
12.3 Mise en place pas à pas#
- Préparer les certificats de chaque nœud (§12.2.2) et un fichier
cluster.tomlpar nœud, avec le même[cluster] name, la mêmetls_ca, et une listeseedscouvrant les autres nœuds.
Démarrer chaque serveur avec
--cluster-config(ou en plaçantcluster.tomlà côté de l'exécutable), et toujours avec--log:miraj-server --root data-n1 --port 7007 --cluster-config n1/cluster.toml --log miraj-server --root data-n2 --port 7007 --cluster-config n2/cluster.toml --log miraj-server --root data-n3 --port 7007 --cluster-config n3/cluster.toml --logAu démarrage, chaque nœud sans état antérieur se présente en secondaire et tente de joindre ses
seedsen TLS mutuel.
Désigner le primaire, une seule fois, en se connectant à celui des nœuds choisi comme primaire (n'importe quel client SQL) :
SET GLOBAL cluster_role = 'primary';Ce nœud lève sa lecture seule, journalise désormais ses DDL, et se présente aux autres comme primaire d'une nouvelle époque (
@@cluster_epoch).La commande rend la main avant la fin de la promotion (§12.4.1) : une écriture envoyée aussitôt après reçoit l'erreur 1290. Attendez que
SELECT @@cluster_rolerendePRIMARYavant de créer des bases ou d'écrire.
- Faire rejoindre les secondaires : les autres nœuds, déjà secondaires par défaut, se connectent au primaire dès qu'ils le trouvent parmi leurs
seeds. S'ils n'ont aucune base en commun avec lui (nœud neuf) ou un journal trop en retard, ils sont amorcés automatiquement par copie complète des bases du primaire, puis rattrapent le flux du journal.
Vérifier l'état du cluster :
SHOW CLUSTER STATUS;+---------+----------------+-----------+-----------+-------+-----------+-------------+---------------------+------------+------+ | NODE_ID | ADDRESS | ROLE | STATE | EPOCH | LAG_BYTES | LAG_SECONDS | CONNECTED_SINCE | LAST_ERROR | SYNC | +---------+----------------+-----------+-----------+-------+-----------+-------------+---------------------+------------+------+ | n1 | 10.0.0.1:7107 | PRIMARY | SELF | 1 | 0 | NULL | NULL | | OFF | | n2 | 10.0.0.2:7107 | SECONDARY | CONNECTED | 1 | 0 | 0.021 | 2026-09-23 10:04:12 | | YES | | n3 | 10.0.0.3:7107 | SECONDARY | CONNECTED | 1 | 512 | 0.048 | 2026-09-23 10:04:15 | | YES | +---------+----------------+-----------+-----------+-------+-----------+-------------+---------------------+------------+------+STATEvautSELFpour le nœud interrogé lui-même (PROMOTINGpendant sa promotion), puisCONNECTING,BOOTSTRAPPING,CONNECTED,DISCONNECTED,REBOOTSTRAP_NEEDED,FENCED,REACHABLEouUNREACHABLEpour les autres nœuds vus par lui.LAG_BYTESretombe à 0 une fois le secondaire à jour ; cette valeur diffère d'un nœud à l'autre puisque chacun ne connaît que ses propres pairs directs.
12.4 Bascule#
12.4.1 Promotion d'un secondaire#
Sur le secondaire choisi :
SET GLOBAL cluster_role = 'primary';La promotion est contrôlée : avant de rendre la main, l'instruction interroge tous les pairs de seeds (quelques secondes au plus, en parallèle) et refuse la promotion, avec l'erreur 9003 (Promotion refused: …, motif en clair), dans les cas suivants :
| Motif (extrait du message) | Situation |
|---|---|
node … is still primary at epoch … | Le primaire est encore joignable : le rétrograder d'abord (§12.4.3), ou forcer. |
node … is already primary at epoch … | Un autre nœud a déjà été promu : ce secondaire le rejoindra de lui-même. |
node … is being promoted | Un autre nœud est en cours de promotion (deux promotions simultanées se refusent mutuellement). |
node … is still connected to primary … | Un pair voit encore le primaire (partition réseau probable) : ce nœud ne le voit plus, lui. |
node … is ahead on database … but no longer retains it | Un pair a reçu plus que ce nœud, mais ne garde plus la partie manquante de son journal (promotion_catchup_mb) : promouvoir ce nœud perdrait ces écritures. |
node … has database … that this node does not have / database … has another identity on node … | Les bases du pair et de ce nœud ne sont pas les mêmes. |
database … has writes confirmed to clients up to position … that no reachable node holds | Réplication semi-synchrone (§12.8) : des écritures confirmées aux clients ne sont tenues ni par ce nœud ni par un pair joignable capable de les servir ; promouvoir ce nœud les perdrait. |
lag of … bytes … exceeds max_promotion_lag_mb | Même après rattrapage, le retard envers la dernière position annoncée par le primaire dépasse le seuil configuré. |
cluster_role = FENCED, replication bootstrap in progress, cluster promotion in progress | Nœud écarté (le rétrograder d'abord), amorçage ou promotion en cours sur ce nœud. |
Un pair injoignable n'empêche pas la promotion : c'est le cas normal après la perte du primaire. Il est seulement consigné.
Si la promotion est acceptée, le client reçoit aussitôt OK, et la suite se déroule en arrière-plan (@@cluster_role passe à PRIMARY à la fin ; la ligne du nœud dans SHOW CLUSTER STATUS est à l'état PROMOTING pendant ce temps) :
- Le lien avec l'ancien primaire est coupé.
- Rattrapage : si un autre secondaire a reçu davantage du journal de l'ancien primaire, le nœud lui demande ce qui lui manque, base par base (journal, valeurs longues, DDL compris). Les secondaires d'un même primaire ont des journaux identiques à la position près : le rattrapage est exact. Il porte sur le secondaire le plus avancé (un seul) ; chaque secondaire garde à cet effet les derniers
promotion_catchup_mbMio de son journal. - L'applicateur termine d'appliquer tout ce qui est reçu.
- Une nouvelle époque (
@@cluster_epoch+ 1) est écrite dans l'état du nœud. - Le nœud lève sa lecture seule, restaure le planificateur d'événements (arrêté tant qu'il était secondaire) et se présente désormais comme primaire de cette nouvelle époque ; il garde son journal à partir de la position des autres secondaires sondés, pour qu'ils le rejoignent par le flux plutôt que par un amorçage.
- Les autres secondaires, dès que leur lien avec l'ancien primaire est rompu (ou que leurs
PINGéchouent), rejoignent parmi leursseedsle primaire de l'époque la plus haute.
Si le rattrapage échoue (pair devenu injoignable, journal local endommagé…), la promotion est abandonnée : le nœud reste secondaire et recommence à suivre un primaire, le motif est consigné dans le journal serveur et affiché dans la colonne LAST_ERROR de sa propre ligne de SHOW CLUSTER STATUS. Il faut donc vérifier @@cluster_role après la commande.
Promotion forcée. Quand la perte est acceptée en connaissance de cause (pair le plus avancé définitivement perdu, retard supérieur au seuil, primaire injoignable pour ce nœud mais pas pour les autres…) :
SET GLOBAL cluster_role = 'force_primary';Chaque refus devient un avertissement consigné (lignes Cluster : promotion de … (forcée) : …), le rattrapage est tenté quand il est possible et son échec n'arrête pas la promotion. Si l'ancien primaire est encore actif, il est écarté dès qu'il voit l'époque supérieure (§12.4.2) : ses écritures des derniers instants ne sont pas perdues silencieusement, elles sont mises en quarantaine à sa rétrogradation.
Il n'y a pas d'élection automatique : c'est l'administrateur qui choisit le nœud à promouvoir et qui exécute la commande.
12.4.2 Retour d'un ancien primaire#
Un nœud qui se croyait primaire et qui reçoit d'un pair une preuve d'une époque plus récente que la sienne passe à l'état FENCED (« écarté ») :
- il repasse en lecture seule (toute écriture de client reçoit l'erreur 1290) ;
@@cluster_rolevautFENCEDsur ce nœud ;- l'incident est consigné dans le journal serveur (
--log).
Un nœud FENCED ne rejoint pas le nouveau primaire automatiquement. Pour le remettre en service comme secondaire :
SET GLOBAL cluster_role = 'secondary';À partir de là :
- s'il a des bases dont le journal ne dépasse pas le point où la bascule a eu lieu, il rattrape simplement le flux du nouveau primaire ;
- s'il a des écritures locales que personne d'autre n'a reçues (parce qu'il a continué à accepter des écritures après la coupure, avant de redémarrer et de se découvrir écarté), ces bases sont réamorcées : leur journal local est déplacé dans
miraj/quarantaine/<horodatage>/<base>/, avec un rapport, puis la base est recopiée depuis le nouveau primaire. Aucune écriture non répliquée n'est donc perdue silencieusement — elle reste consultable dans son dossier de quarantaine, mais elle n'est plus dans la base active.
Procédure complète recommandée après la perte du primaire (pour une bascule planifiée, voir §12.4.3) :
- Confirmer que l'ancien primaire est arrêté ou injoignable.
- Promouvoir le secondaire choisi (
SET GLOBAL cluster_role = 'primary', voir §12.4.4 pour le choisir). - Repointer les applications clientes vers l'adresse du nouveau primaire (MIRAJ ne le fait pas à leur place, voir §12.6).
- Relancer l'ancien primaire : il démarre avec l'état qu'il avait, se connecte à ses
seeds, se découvreFENCEDdès qu'il voit l'époque plus récente. - Vérifier
SHOW CLUSTER STATUS: le nœud apparaît enFENCED. SET GLOBAL cluster_role = 'secondary'sur ce nœud pour le faire rejoindre : il rattrape ou se réamorce selon le cas, et son éventuel dossier de quarantaine peut être examiné puis archivé ou supprimé par l'administrateur.
12.4.3 Bascule planifiée sans perte#
Pour changer de primaire sans rien perdre (maintenance du serveur, migration) :
- Arrêter les écritures des applications, ou accepter qu'elles reçoivent l'erreur 1290 pendant la bascule.
- Sur le primaire actuel :
SET GLOBAL cluster_role = 'secondary'. Il repasse en lecture seule, coupe ses secondaires et garde son journal. - Vérifier
SELECT @@cluster_rolesur ce nœud (SECONDARY). - Sur le secondaire choisi :
SET GLOBAL cluster_role = 'primary'. La sonde trouve l'ancien primaire en secondaire ; si celui-ci a écrit des transactions que le candidat n'avait pas encore reçues, le candidat les rattrape depuis lui avant d'être promu. - Repointer les applications vers le nouveau primaire.
L'ancien primaire et les autres secondaires rejoignent ensuite le nouveau primaire par le flux du journal, sans réamorçage ni quarantaine.
12.4.4 Choisir le secondaire à promouvoir#
Tant qu'un secondaire n'a plus de primaire, il interroge ses pairs toutes les 10 secondes : sur chacun, SHOW CLUSTER STATUS montre les autres nœuds à l'état REACHABLE (avec leur rôle, leur époque et, dans LAG_BYTES, leur retard sur la dernière position annoncée par le primaire) ou UNREACHABLE. Sa propre ligne (SELF) donne son propre retard ; information_schema.MIRAJ_REPLICATION le détaille par base (WRITTEN_LSN et PRIMARY_LSN).
Il n'est pas nécessaire de choisir le secondaire le plus avancé : le nœud promu rattrape lui-même le plus avancé de ses pairs joignables. Mieux vaut choisir le nœud le mieux placé pour recevoir les écritures (réseau, capacité), à condition que les autres secondaires soient joignables au moment de la promotion. Avec promotion_catchup_mb = 0, aucun secondaire ne garde de quoi servir un rattrapage : promouvoir un secondaire en retard sur un pair est alors refusé, et il faut promouvoir le plus avancé (ou forcer en acceptant la perte).
12.5 Suivi et supervision#
12.5.1 Variables système#
| Variable | Portée | Description |
|---|---|---|
@@read_only | session/globale, dynamique | 1 sur un secondaire ou un nœud FENCED (écritures refusées), 0 sur le primaire. Toujours 0 hors édition Cluster. |
@@cluster_role | globale | PRIMARY, SECONDARY ou FENCED ; NONE hors réplication active. |
@@cluster_epoch | globale | Numéro de l'époque courante du nœud (entier croissant à chaque promotion). |
@@cluster_name | globale | Nom du cluster tel que défini dans cluster.toml. |
@@cluster_node_id | globale | Identifiant ([node] id) de ce nœud. |
@@cluster_primary | globale | Identifiant du primaire connu de ce nœud. |
@@server_id | globale | Dérivé de l'identifiant du nœud ([node] id) sous réplication active ; 1 hors cluster. |
@@cluster_sync_commit | session/globale, dynamique | Réplication semi-synchrone (§12.8) : OFF, RECEIVED ou APPLIED. Valeur de session modifiable par SET [SESSION] et SET STATEMENT … FOR ; SET GLOBAL fixe celle du serveur (valeur initiale : sync_commit de cluster.toml). |
@@cluster_sync_replicas | globale, dynamique | Nombre de secondaires qui doivent accuser réception (≥ 1). |
@@cluster_sync_timeout | globale, dynamique | Attente maximale des accusés, en millisecondes (≥ 1). |
@@cluster_sync_timeout_action | globale, dynamique | FALLBACK, ERROR ou WAIT : conduite quand les accusés n'arrivent pas à temps (§12.8.3). |
12.5.2 SHOW CLUSTER STATUS et information_schema.MIRAJ_NODES#
SHOW CLUSTER STATUS renvoie exactement les colonnes de information_schema.MIRAJ_NODES, une ligne par nœud connu du serveur interrogé (privilège REPLICATION CLIENT ou PROCESS) :
| Colonne | Description |
|---|---|
NODE_ID | Identifiant du nœud. |
ADDRESS | Adresse annoncée entre nœuds. |
ROLE | PRIMARY ou SECONDARY. |
STATE | SELF (le nœud interrogé ; PROMOTING pendant sa promotion ou sa prise de fonctions après une élection, CANDIDATE pendant une campagne en mode raft), CONNECTING, BOOTSTRAPPING, CONNECTED, DISCONNECTED, REBOOTSTRAP_NEEDED, FENCED, REACHABLE / UNREACHABLE (pair sondé par un secondaire sans primaire, ou lors d'une promotion). |
EPOCH | Époque du nœud. |
LAG_BYTES | Octets du journal du primaire pas encore appliqués par ce nœud, cumulés sur ses bases. Sur la ligne SELF d'un secondaire, comptés jusqu'à la dernière fin de journal annoncée par le primaire (ce qui n'est pas encore reçu compte aussi) ; pour un pair sondé, son retard annoncé. |
LAG_SECONDS | Ancienneté du dernier lot appliqué (secondes, NULL si sans objet). |
CONNECTED_SINCE | Horodatage de la connexion en cours (NULL sinon). |
LAST_ERROR | Dernière erreur de réplication rencontrée pour ce nœud, vide sinon ; sur la ligne du nœud lui-même, le motif de l'abandon de sa dernière promotion s'il y en a un (en mode raft, sinon, celui du dernier échec d'une élection : refus d'un votant, absence de majorité joignable). |
SYNC | Réplication semi-synchrone (§12.8), vue du primaire : sur sa propre ligne, OFF, RECEIVED, APPLIED ou MAJORITY (valeur globale de @@cluster_sync_commit), ou DEGRADED si une base au moins est en état dégradé ; sur la ligne d'un secondaire, YES s'il compte pour les accusés (connecté et en flux), NO sinon (amorçage, déconnexion). NULL sur un secondaire. |
12.5.3 information_schema.MIRAJ_REPLICATION#
Détail par nœud et par base :
| Colonne | Description |
|---|---|
NODE_ID | Nœud concerné. |
DATABASE | Nom de la base. |
BASE_ID | Identité interne de la base (utile pour distinguer un DROP suivi d'un CREATE du même nom). |
SENT_LSN | Dernier LSN expédié à ce nœud pour cette base (NULL si sans objet, par exemple côté secondaire). |
WRITTEN_LSN | Dernier LSN écrit dans le journal local de ce nœud pour cette base. |
APPLIED_LSN | Dernier LSN appliqué aux tables. |
RETAINED_LSN | LSN en dessous duquel le primaire ne garantit plus de pouvoir rattraper ce nœud sans réamorçage (NULL si sans objet). |
PRIMARY_LSN | Fin du journal du primaire pour cette base : sur le primaire, la fin de son journal ; sur un secondaire, la dernière fin annoncée par le primaire (chaque seconde), qui reste connue après la perte du primaire (NULL si inconnue). PRIMARY_LSN − WRITTEN_LSN est ce qu'un secondaire n'a pas encore reçu. |
SYNCED_LSN | Plus haute position de la base confirmée aux clients par la réplication semi-synchrone (§12.8) : sur le primaire, la sienne ; sur un secondaire, la dernière annoncée par le primaire, qui reste connue après sa perte (NULL si inconnue, 0 si aucune). |
Exemple :
SELECT NODE_ID, `DATABASE`, WRITTEN_LSN, APPLIED_LSN
FROM information_schema.MIRAJ_REPLICATION
WHERE `DATABASE` = 'gestium_prod'
ORDER BY NODE_ID;12.5.4 Journalisation#
Avec --log (toujours recommandé, voir le chapitre sur le démarrage du serveur), le niveau info reçoit la connexion et la déconnexion de chaque pair, le début et la fin d'un amorçage, et chaque promotion ; le journal serveur (fichier) reçoit toute erreur de réplication : refus de poignée de main (nom de cluster différent, autorité TLS étrangère, édition incompatible), rupture de séquence du journal, échec du rejeu d'un DDL, passage d'un secondaire à l'état « à réamorcer », ou table réécrite hors journal par le primaire (réamorçage demandé par le secondaire, §12.7).
Lignes propres à la promotion :
| Ligne | Niveau | Sens |
|---|---|---|
Cluster : promotion de n3 : rattrapage prévu depuis n2 (… octets sur … base(s)). / … : sans rattrapage. | info | Promotion acceptée, plan retenu. |
Cluster : promotion de n3 (forcée) : … | erreur | Avertissement : pair injoignable, ou refus levé par 'force_primary'. |
Cluster : promotion de n3 refusée : … | erreur | Promotion refusée (erreur 9003 rendue au client). |
Cluster : rattrapage depuis n2 terminé (… octets). | info | Rattrapage réussi. |
Cluster : promotion de n3 abandonnée : … | erreur | Échec en arrière-plan : le nœud reste secondaire. |
Cluster : promotion forcée de n3 : base … servie telle qu'avant la réécriture hors journal … | erreur | 'force_primary' sur un secondaire qui attendait le réamorçage de cette base (§12.7) : journal reçu au-delà mis de côté, modifications de l'ancien primaire perdues. |
Cluster : rattrapage demandé par n3 (… base(s)). / Cluster : rattrapage de n3 servi (… octets). | info | Côté du pair qui sert le rattrapage. |
Lignes propres à la réplication semi-synchrone (§12.8), sur le primaire, aux changements d'état seulement (jamais une ligne par écriture) :
| Ligne | Niveau | Sens |
|---|---|---|
Cluster : réplication synchrone dégradée sur la base shop : aucun accusé de 1 secondaire(s) en 10000 ms ; les écritures suivantes ne patientent plus jusqu'au rattrapage. | erreur | Délai dépassé : la base passe en état dégradé (§12.8.3). |
Cluster : réplication synchrone rétablie sur la base shop. | info | Les secondaires ont rattrapé : les écritures attendent de nouveau leurs accusés. |
Cluster : 2 session(s) en attente d'accusé interrompue(s) : demoted. | erreur | Le nœud a cessé d'être primaire (demoted, fenced) pendant que des sessions attendaient : elles reçoivent l'erreur 9004. |
12.6 Comportement pour les clients applicatifs#
- Lecture sur un secondaire : un client se connecte à un secondaire exactement comme à n'importe quel serveur MIRAJ, sur son port client habituel (7007 par défaut) — aucun changement de protocole ni d'outil côté client.
- Écriture sur un secondaire : toute instruction qui écrit (DML, DDL, gestion des comptes) reçoit l'erreur 1290 (
ER_OPTION_PREVENTS_STATEMENT, message « the server is running with the read_only option so it cannot execute this statement »), le code que les pilotes et répartiteurs de charge reconnaissent habituellement pour rediriger une écriture vers le primaire. Restent permis sur un secondaire : toutes les lectures, les tables temporaires de la session,SET,START TRANSACTION/COMMIT/ROLLBACK(sans écriture),KILL,FLUSH,LOCK TABLES … READ,CHECK TABLEetREPAIR TABLE(locaux au secondaire). - Stratégie applicative recommandée : n'écrire que sur le primaire, connu par sa configuration côté application (MIRAJ ne fournit pas de mécanisme de découverte automatique du primaire ni de repointage des clients — c'est hors du périmètre du moteur, voir §12.7) ; répartir les lectures qui tolèrent un léger retard (
LAG_SECONDS) vers un ou plusieurs secondaires ; sur réception d'une erreur 1290, considérer que le nœud contacté n'est plus (ou pas) le primaire et se reconnecter au primaire attendu, en interrogeant au besoinSHOW CLUSTER STATUSou@@cluster_primarysur un nœud connu du cluster pour le retrouver.
12.7 Limites actuelles connues#
Ces points sont établis dans le code et la documentation interne de conception du moteur, pas des intentions futures :
- Réplication asynchrone par défaut. Avec
@@cluster_sync_commit = OFF(valeur par défaut), le primaire ne consulte pas les secondaires avant de rendre la main au client qui écrit. Une écriture confirmée au client peut donc n'être encore arrivée à aucun secondaire ; si le primaire est perdu à ce moment, cette écriture reste sur l'ancien primaire, mise en quarantaine lors de son retour, jamais rejouée automatiquement sur le nouveau primaire. Le rattrapage à la promotion réduit cette fenêtre de perte aux écritures reçues par aucun secondaire joignable (et non plus à celles que le seul nœud promu n'avait pas reçues). La réplication semi-synchrone (§12.8) la ferme pour les écritures confirmées sans avertissement. - Semi-synchrone, visibilité avant accusé. En réplication semi-synchrone, une écriture est validée et visible des autres sessions du primaire avant l'arrivée des accusés ; un délai dépassé,
KILL QUERYou une rétrogradation pendant l'attente laissent l'écriture validée localement (§12.8.4). Les réglagesSET GLOBAL cluster_sync_*ne sont pas persistés (ceux decluster.tomls'appliquent au redémarrage). - Mode manuel : un seul primaire, promotion manuelle, sans consensus. Il n'y a pas d'élection automatique du nouveau primaire. La promotion contrôlée refuse de promouvoir un secondaire tant que le primaire est joignable par lui ou par un pair, mais un primaire injoignable de tous les nœuds sondés et pourtant actif (partition réseau complète) peut encore accepter des écritures jusqu'à ce qu'il voie la nouvelle époque ;
'force_primary'lève ces contrôles. - Mode raft (incrément 1) : élection par quorum, avec ses limites (§12.9.8) : un leader isolé accepte encore des écritures
OFFpendant son bail (elles finissent en quarantaine) ; les lectures ne sont pas linéarisables ; une élection peut rester bloquée (base absente chez les survivants, bases de marques d'époque incomparables) jusqu'au retour d'un nœud ou à'force_primary'; une base supprimée pendant l'absence d'un nœud peut réapparaître si ce nœud est élu ; les membres sont fixes (seeds, redémarrage pour en changer). - Rattrapage borné. Le rattrapage à la promotion porte sur un seul pair, le plus avancé, et sur ce que ce pair garde encore de son journal (
promotion_catchup_mb, 64 Mio par base par défaut) ; il n'y a pas d'amorçage entre secondaires. Deux promotions lancées en même temps sur deux nœuds se refusent l'une l'autre, sans départage automatique. - Chaque nœud porte une copie complète de chaque base. Il n'y a pas de répartition des données entre les nœuds à ce stade (pas de fragments répartis).
- Ce qui n'est pas répliqué : la base système des comptes est relayée par un canal séparé (hors journal), non par le mécanisme de réplication du journal lui-même ; les tables temporaires (de session et globales) ;
REPAIR TABLE(locale à chaque nœud ; sur le primaire, une réparation qui remplace des valeurs fait réamorcer la base sur les secondaires, voir « Table réécrite hors journal ») ; la dernière date d'exécution d'un événement planifié (les événements planifiés ne s'exécutent que sur le primaire, un secondaire ne les exécute jamais) ; les réglages positionnés parSET GLOBAL; le contenu des vues en cache ; le journal des requêtes lentes. - DDL non déterministe. Un DDL rejoué par SQL sur un secondaire qui évalue une expression sur les lignes existantes au moment de son exécution (par exemple une valeur par défaut calculée) peut produire un résultat différent de celui obtenu sur le primaire, puisqu'il est réexécuté plutôt que rejoué ligne à ligne.
- Table réécrite hors journal : réamorçage automatique. Quand le primaire réécrit en entier le fichier d'une table en y portant des modifications qu'aucun enregistrement du journal ne décrit (fichier sans position de journal, numéros de ligne au-delà de 2^32,
REPAIR TABLEqui remplace des valeurs, conversion d'une colonne vers un typeBLOBqui externalise ses valeurs), il écrit d'abord dans le journal un marqueur « table réécrite hors journal ». Chaque secondaire qui l'atteint sans disposer déjà de ce fichier s'arrête juste avant lui, sans rien appliquer de la suite, et se fait réamorcer : la base est recopiée depuis le primaire, puis le flux reprend. Le secondaire est donc en retard le temps de la copie, jamais faux ; le motif est dans la colonneLAST_ERRORde sa propre ligne jusqu'à la fin de la copie, la ligneCluster : base … : table … réécrite hors journal par le primaire (position …), réamorçage nécessaire : la base sera recopiée depuis le primaire.est consignée dans son journal serveur, etMiraj_cluster_unlogged_rebootstraps(§12.8.5) compte ces réamorçages. Tant que la copie n'est pas faite,'primary'refuse de promouvoir ce secondaire (erreur 9003,database(s) … waiting to be copied again from the primary). Si le primaire est perdu pendant cette attente,'force_primary'passe outre : chaque base en attente est servie telle qu'elle est, c'est-à-dire dans son état cohérent d'avant la réécriture ; le journal reçu au-delà (jamais appliqué) est recopié en quarantaine puis retiré, et les modifications de l'ancien primaire à partir du marqueur sont perdues (ligneCluster : promotion forcée de … : base … servie telle qu'avant la réécriture hors journal …). Comme après toute promotion forcée, l'ancien primaire qui revient est écarté (FENCED) puis réamorcé une fois rétrogradé, et les secondaires qui avaient reçu la suite divergent et sont réamorcés. En mode raft, un nœud qui attend ainsi son réamorçage abandonne l'élection qu'il gagnerait (motif dansLAST_ERROR) : une autre élection suit ; seul'force_primary'le promeut. Une table de plus de 2^32 lignes réécrit son fichier à chaque écriture : chacune porte un marqueur, et ses secondaires se réamorcent tant que ces écritures continuent. Un nœud seul, hors d'un cluster actif, n'écrit aucun marqueur. - Seuil des LOB abaissé. Tous les nœuds doivent avoir le même
--lob-threshold(poignée de main). Quand un primaire en mode manuel redémarre avec un seuil plus bas (ou lit le fichier d'un moteur antérieur), les valeursBLOBrestées dans ses tables qui atteignent le seuil rejoignent le magasin à l'ouverture par desUPDATEordinaires consignés au journal, par tranches de 10 000 lignes ou 4 Mio : les secondaires les appliquent et reçoivent les valeurs par le flux, sans réamorçage, pourvu qu'ils se reconnectent avant le premier point de sauvegarde du primaire relancé (checkpoint_interval, 60 s par défaut) : un primaire redémarré ne connaît la position d'un secondaire qu'à sa reconnexion, et un secondaire dont la position a quitté son journal est réamorcé. Un arrêt au milieu est repris au redémarrage suivant. Au-delà d'un plafond par table (256 Mio, oujournal_retention_mbs'il est plus petit), rien n'est déplacé : les valeurs restent dans la table, lisibles et modifiables, seules les valeurs écrites ensuite suivent le nouveau seuil, et le journal serveur reçoitAVERTISSEMENT : base.table : … valeur(s) BLOB … restent dans la table, au-delà du plafond de migration journalisée …. Un secondaire ne déplace jamais rien ; un nœud en mode raft redémarre toujours suiveur et ne migre donc pas ses valeurs. - Fenêtre de perte d'un DDL à l'arrêt brutal. Un DDL est journalisé après le succès durable de ses effets sur disque ; un arrêt brutal du primaire survenant exactement entre ces deux étapes laisse le DDL appliqué localement sans avoir été expédié aux secondaires (même classe de risque qu'une politique de sauvegarde relâchée pour une coupure de courant).
- Retard non borné en cas de secondaire durablement absent. Un secondaire injoignable au-delà de
journal_retention_mbde journal accumulé n'est plus rattrapable par le flux normal : il est réamorcé par copie complète des bases à sa reconnexion. - Aucune répartition de charge intégrée côté client. MIRAJ ne fournit ni proxy, ni mécanisme de découverte ou de redirection automatique du primaire pour les applications : la stratégie de connexion (quel nœud contacter pour écrire, comment réagir à un basculement) reste à la charge de l'application ou de son infrastructure.
- Journal en version 3, propre à un nœud de cluster. Une base d'un nœud de l'édition Cluster utilise un journal en version 3 (qui peut porter des enregistrements de DDL et de compactage de valeurs longues) ; une édition antérieure au support du cluster refuse ce journal proprement plutôt que de le lire de travers, tandis qu'une édition Entreprise ou Express le lit et l'ignore sans réplication.
12.8 Réplication semi-synchrone#
Par défaut, une écriture est confirmée au client dès qu'elle est validée sur le primaire (réplication asynchrone). La réplication semi-synchrone fait attendre, avant la confirmation, qu'un ou plusieurs secondaires aient reçu l'écriture (ou l'aient appliquée). Elle se règle par session : une application peut la demander pour ses écritures critiques seulement (une facture, un paiement) et laisser les autres en asynchrone.
12.8.1 Niveaux#
@@cluster_sync_commit | L'écriture est confirmée quand… | Garantie |
|---|---|---|
OFF (défaut) | …elle est validée sur le primaire. | Aucune au-delà du primaire. |
RECEIVED | …@@cluster_sync_replicas secondaires l'ont écrite dans leur journal (vidé sur disque avec ack = "durable"). | Une écriture confirmée sans avertissement survit à la perte du primaire : le secondaire promu la récupère (§12.8.6). |
APPLIED | …@@cluster_sync_replicas secondaires l'ont appliquée à leurs tables. | Idem, et lecture après écriture : une lecture faite ensuite sur ces secondaires la voit. |
MAJORITY | …une majorité des membres du cluster (les seeds, primaire compris) l'a écrite dans son journal : le nombre d'accusés est calculé (cluster_sync_replicas est ignoré). | En mode raft (où c'est le niveau par défaut), une écriture confirmée sans erreur survit à toute élection (§12.9.5). Juste après une élection, l'attente laisse aux suiveurs le temps de se reconnecter au nouvel élu (dans le délai). |
Les secondaires qui comptent sont ceux qui sont connectés au primaire et en flux (pas en cours d'amorçage) : la colonne SYNC de SHOW CLUSTER STATUS les montre à YES sur le primaire.
12.8.2 Portées#
SET [SESSION] cluster_sync_commit = 'received': pour les écritures suivantes de la session ;SET cluster_sync_commit = DEFAULTrend la valeur du serveur.SET STATEMENT cluster_sync_commit = 'applied' FOR INSERT …: pour une seule instruction.SET GLOBAL cluster_sync_commit = 'received': valeur du serveur, suivie par les sessions qui n'ont pas fixé la leur.cluster_sync_replicas,cluster_sync_timeout(millisecondes) etcluster_sync_timeout_actionn'existent qu'au niveau global (SET GLOBAL, erreur 1229 sinon) : ce sont des réglages d'exploitation.
Les valeurs initiales viennent de cluster.toml (sync_commit, sync_replicas, sync_timeout_ms, sync_timeout_action, §12.2.1) ; un SET GLOBAL agit immédiatement mais n'est pas persisté. Ces variables se lisent dans toutes les éditions (OFF, 1, 10000, FALLBACK) ; les modifier hors édition Cluster renvoie l'erreur 9002. Sans cluster.toml, elles sont acceptées sans effet. Réglées sur un secondaire, elles valent à sa promotion.
L'attente porte sur l'instruction entière : une instruction validée seule (auto-validation), un COMMIT, une validation implicite (DDL, SET autocommit = 1) ; un CALL attend une seule fois, à sa fin, pour toutes les écritures de la procédure (de même pour les écritures faites par des déclencheurs ou des fonctions stockées). Un ROLLBACK et une lecture n'attendent jamais. Les événements planifiés n'attendent jamais.
12.8.3 Délai, conduites et état dégradé#
Quand les accusés n'arrivent pas dans @@cluster_sync_timeout millisecondes (10 000 par défaut), ou quand moins de @@cluster_sync_replicas secondaires sont en flux (décision immédiate, sans attendre le délai), @@cluster_sync_timeout_action décide :
| Conduite | Effet |
|---|---|
FALLBACK (défaut) | L'écriture est confirmée avec un avertissement 9004 (SHOW WARNINGS) : Synchronous replication: transaction committed locally, 0 of 1 acknowledgement(s) received within 10000 ms ou …, only 0 secondary(ies) connected, 1 required. |
ERROR | Le client reçoit l'erreur 9004 avec le même texte. L'écriture reste validée sur le primaire : c'est un « résultat inconnu » pour la réplication, pas une annulation. L'application ne doit pas rejouer aveuglément l'écriture (risque de doublon) : relire, puis décider. |
WAIT | Attente sans limite, jusqu'aux accusés. Seuls KILL QUERY, KILL CONNECTION, une rétrogradation ou un écartement du nœud l'interrompent. |
Un délai dépassé met la base en état dégradé (journal serveur : réplication synchrone dégradée sur la base …, colonne SYNC à DEGRADED sur la ligne du primaire) : les écritures suivantes de cette base n'attendent plus et reçoivent directement l'avertissement (ou l'erreur), au lieu de payer le délai chacune. Dès que @@cluster_sync_replicas secondaires ont rattrapé la fin du journal de la base (écrite, ou appliquée si l'attente expirée était en APPLIED), l'état dégradé est levé (réplication synchrone rétablie sur la base …). La conduite WAIT ignore l'état dégradé.
12.8.4 Sémantique précise#
- Validation locale d'abord. L'écriture est validée sur le primaire (journal écrit, ou vidé selon
save_policy), ses verrous sont rendus, puis la session attend les accusés avant de répondre. Pendant cette attente, l'écriture est déjà visible des autres sessions du primaire. KILL QUERYpendant l'attente : l'instruction réussit avec l'avertissement 9004wait cancelled, transaction committed locally.KILL CONNECTION: la connexion est fermée (1927), l'écriture reste validée.- Rétrogradation ou écartement du nœud pendant l'attente : erreur 9004
node is no longer primary (demoted); transaction committed locally, acknowledgement unknown. Si aucun secondaire ne l'avait reçue, cette écriture finira en quarantaine au retour du nœud (§12.4.2). save_policy: enperiodic, une écriture en mode semi-synchrone attend au moins l'écriture du journal dans son fichier (seuls les octets écrits sont expédiés aux secondaires). Enmanual, rien n'est journalisé ni répliqué : le réglage est sans effet.APPLIEDet DDL : un DDL appliqué par un secondaire attend qu'aucune transaction de client ne tienne la table sur ce secondaire (lock_wait_timeout) ; une écriture enAPPLIEDqui le suit peut donc expirer. De même si l'application échoue sur un secondaire (voirLAST_ERROR), ou si une session du secondaire tient la table parLOCK TABLES … READ.- Coût : chaque écriture attend un aller-retour réseau et quelques millisecondes (le primaire sollicite un accusé immédiat du secondaire dès qu'une session attend). En
OFF, rien ne change par rapport à la réplication asynchrone. - Un secondaire arrêté brutalement est vu déconnecté immédiatement dans la plupart des cas (liaison fermée), mais une coupure réseau silencieuse n'est détectée qu'après 15 secondes sans trame : d'ici là, les écritures attendent le délai.
@@cluster_sync_replicassupérieur au nombre de secondaires : chaque écriture reçoit l'avertissement (ou l'erreur) immédiatement.
12.8.5 Compteurs#
SHOW STATUS LIKE 'Miraj_cluster_sync%' (réplication active seulement) :
| Variable | Sens |
|---|---|
Miraj_cluster_sync_waits | Attentes commencées (une par base et par instruction). |
Miraj_cluster_sync_fallbacks | Écritures confirmées sans les accusés, avec l'avertissement 9004. |
Miraj_cluster_sync_errors | Erreurs 9004 rendues (conduite ERROR, nœud plus primaire). |
Miraj_cluster_sync_timeouts | Délais dépassés (y compris les écritures d'une base dégradée). |
Miraj_cluster_sync_cancelled | Attentes interrompues par KILL. |
Miraj_cluster_sync_wait_avg_ms, Miraj_cluster_sync_wait_max_ms | Durée moyenne et maximale d'une attente. |
Miraj_cluster_sync_degraded_bases | Bases en état dégradé. |
Miraj_cluster_sync_secondaries | Secondaires en flux, qui comptent pour les accusés. |
Hors réplication semi-synchrone, SHOW STATUS LIKE 'Miraj_cluster_unlogged_rebootstraps' compte, sur un secondaire, les réamorçages de bases qu'il a demandés depuis son démarrage après une table réécrite hors journal par le primaire (§12.7).
12.8.6 Garantie à la promotion#
Avec RECEIVED (ou APPLIED) et @@cluster_sync_replicas = N, toute écriture confirmée sans avertissement est dans le journal de N secondaires. À la perte du primaire, la promotion contrôlée (§12.4.1) rattrape le secondaire le plus avancé : l'écriture n'est perdue que si ces N secondaires sont tous perdus ou injoignables. Le primaire annonce en outre à ses secondaires la plus haute position confirmée de chaque base (SYNCED_LSN, §12.5.3) ; une promotion qui ne pourrait pas l'atteindre (aucun nœud joignable ne la tient) est refusée (9003, writes confirmed to clients), sauf 'force_primary'. Cette position n'est gardée qu'en mémoire : un nœud redémarré ne la connaît plus.
Exemple, lecture après écriture entre nœuds :
-- Session de l'application, sur le primaire
SET SESSION cluster_sync_commit = 'applied';
INSERT INTO facture (id, montant) VALUES (1042, 250.00);
-- OK : la facture est appliquée sur le secondaire ; un rapport qui l'y lit maintenant la voit12.9 Élection automatique (mode raft)#
Avec mode = "raft" dans cluster.toml (tous les nœuds), le primaire n'est plus désigné par l'administrateur : il est élu par une majorité des membres, et un autre l'est automatiquement quand il disparaît. Le mécanisme suit le protocole de consensus Raft (termes, un vote par terme, pré-vote, bail du leader), adapté au journal par base de MIRAJ. C'est un premier incrément : il garantit la sûreté des écritures confirmées ; il ne change ni les membres à chaud, ni la répartition des données.
12.9.1 Membres, quorum, terme#
- Les membres votants sont les adresses de
seeds(dédoublonnées), qui doivent contenir l'adresse annoncée de chaque nœud. Avec N membres, une majorité estN / 2 + 1membres (2 sur 3, 3 sur 5). Trois membres tolèrent la perte d'un nœud, cinq celle de deux ; deux membres n'en tolèrent aucune. - Le terme est l'époque du nœud (
@@cluster_epoch) : un candidat l'incrémente, un nœud qui en apprend une supérieure l'adopte. Chaque nœud accorde au plus un vote par terme, écrit dansmiraj/cluster.mrsavant de répondre. - Un nœud redémarre toujours suiveur (
SECONDARY) ; son terme et son vote sont gardés.
12.9.2 Déroulement d'une élection#
- Un suiveur sans nouvelle du leader depuis un délai tiré au sort entre
election_timeout_mset le double fait d'abord un pré-vote : il demande aux membres s'ils voteraient pour lui, sans rien changer chez eux. Un nœud qui entend encore son leader répond non (adhérence) : un nœud isolé qui revient ne dépose pas le leader en place. - Avec une majorité de « oui », il incrémente son terme, vote pour lui-même et demande les votes. Un membre accorde son vote si son propre journal n'est pas plus à jour que celui du candidat, base par base : chaque base porte la marque d'époque du dernier leader qui y a écrit ; un candidat de marque inférieure est refusé, à marque égale sa position importe peu (il rattrapera), une base qui manque au candidat entraîne un refus.
- Élu, le nœud rattrape d'abord, base par base, le votant le plus avancé de même marque, applique tout, puis écrit sa propre marque d'époque dans chaque base avant d'accepter la moindre écriture :
@@cluster_rolepasse alors àPRIMARY. Un rattrapage impossible abandonne l'élection (motif dansLAST_ERROR). - Les autres nœuds suivent le nouveau leader ; ceux dont le journal n'en est pas un préfixe (ancien leader qui avait des écritures non confirmées) sont réamorcés, leur ancienne copie mise en quarantaine.
Les journaux du serveur retracent l'élection : Cluster : candidat au terme 4., Cluster : vote accordé à n1 (terme 4)., Cluster : nœud n1 élu leader (terme 4, 2 vote(s) sur 3)., Cluster : aucune majorité : 1 vote(s) sur les 2 requis au terme 4.
12.9.3 Bail du leader#
Un leader qui n'a reçu aucune trame d'une majorité des membres (lui compris) pendant election_timeout_ms (après un délai de grâce d'autant à son élection) rend son rôle : il repasse en lecture seule (1290), les sessions qui attendaient un accusé reçoivent l'erreur 9004 (quorum lost), et il le consigne (nœud n1 rétrogradé automatiquement (mode raft) : majorité perdue…). Un leader qui apprend un terme supérieur est rétrogradé de même, sans intervention (au lieu d'être écarté, FENCED, comme en mode manuel).
12.9.4 Niveau MAJORITY#
En mode raft, sync_commit vaut majority et sync_timeout_action error sauf réglage explicite : une écriture n'est confirmée au client qu'une fois écrite dans le journal d'une majorité des membres ; sinon le client reçoit l'erreur 9004 (l'écriture reste validée localement, son sort dépend de l'élection suivante). MAJORITY est aussi utilisable en mode manuel (majorité calculée sur les seeds).
12.9.5 Garanties#
- Un seul leader par terme. Deux leaders peuvent coexister brièvement à des termes différents (un ancien leader isolé, avant la fin de son bail), jamais au même terme ; seul le plus récent peut confirmer une écriture en
MAJORITY. - Aucune écriture confirmée en
MAJORITYn'est perdue par une élection : elle est dans le journal d'une majorité ; le nouvel élu l'est par une majorité, qui la croise ; le votant commun a refusé un candidat moins à jour, ou le candidat le rattrape avant de servir. - Ce qui peut être perdu : les écritures
OFF(ou enMAJORITYqui ont reçu l'erreur 9004) faites sur un leader qui perd la majorité ; elles restent en quarantaine sur l'ancien leader. - Les lectures ne sont pas linéarisables : un ancien leader lit encore ses données jusqu'à la fin de son bail, un suiveur est en retard.
12.9.6 Commandes manuelles en mode raft#
| Commande | Effet en mode raft |
|---|---|
SET GLOBAL cluster_role = 'primary' sur un suiveur | Campagne immédiate avec transfert : les votants passent outre l'adhérence à leur leader, qui rend son rôle. Refus 9003 avec le motif du premier refus (node n2 refused: not up to date on database shop (epoch 4 < 5)) ou cluster has no quorum: 1 of 3 members reachable. |
SET GLOBAL cluster_role = 'secondary' sur le leader | Le leader rend son rôle et ne se présente pas pendant deux délais d'élection : un autre membre est élu. |
SET GLOBAL cluster_role = 'force_primary' | Levier d'urgence hors quorum (deux nœuds sur trois perdus) : promotion sans vote au terme suivant, sans bail tant qu'une majorité ne l'a pas rejoint, consignée promotion forcée hors quorum : écritures confirmées possiblement perdues. Les écritures MAJORITY échouent (9004) tant qu'aucune majorité n'est revenue : passer au besoin SET GLOBAL cluster_sync_commit = 'off'. |
12.9.7 Passer du mode manuel au mode raft#
Faire converger le cluster (aucun retard), arrêter les nœuds, ajouter mode = "raft" (et au besoin election_timeout_ms) dans chaque cluster.toml en vérifiant que seeds contient tous les nœuds, puis les redémarrer : ils redémarrent suiveurs et élisent un leader. Le retour au mode manuel suit le même chemin (le nœud à désigner primaire l'est ensuite par 'primary').
12.9.8 Limites de l'incrément actuel#
- Une élection peut rester bloquée sans être dangereuse : base absente chez tous les candidats possibles (création pendant l'absence d'un nœud), bases dont les marques d'époque se contredisent entre survivants. Elle se débloque au retour du nœud manquant, ou par
'force_primary'; le motif est dansLAST_ERROR. - Une base supprimée pendant l'absence d'un nœud réapparaît si ce nœud est élu ensuite.
- Un suiveur dont le journal précède l'élection du leader d'une marque plus ancienne est réamorcé (copie complète) plutôt que rattrapé.
- Les membres sont fixes : en changer demande un redémarrage de tous les nœuds.
- Les délais supposent des écritures sur disque rapides : un disque très lent (vidage de plusieurs centaines de millisecondes) impose un
election_timeout_msplus grand.