## Niveau de service et consistence

> Note: Versions requises

> Les niveaux de services ont été ajoutés dans mysqlnd_ms version 1.2.0-alpha. mysqlnd_ms_set_qos() est disponible pour PHP 5.4.0 ou plus récent.

Differents types de solutions de cluster MySQL offrent différents niveaux de consistence des données à leurs utilisateurs. Un cluster asynchrone offre une consistence éventuelle par défaut. Une lecture exécutée sur un esclave peut retourner une donnée fraiche, dépassée, ou pas de donnée du tout en fonction de l'état d'avancement de la réplication de l'esclave sur le maitre.

Les applications utilisant le cluster de replication MySQL doivent être conçues pour fonctionner avec une consistence éventuelle des données. Dans certains cas, des données non fraiches ne sont pas acceptables. Dans ces cas-là, seuls certains esclaves voire même uniquement le maitre sont autorisés pour atteindre la qualité de service nécessaire.

Depuis mysqlnd_ms 1.2.0, le plugin peut sélectionner un noeud automatiquement pour assurer une consistence de session ou une consistence forte. La consistence de session implique qu'un client peut lire ses écritures, les autres clients peuvent voir ou non les écritures. La consistence forte assure que tous les autres clients verront les écritures.

Exemple #1 Consistence de session: lire ses écritures

    {
        "myapp": {
            "master": {
                "master_0": {
                    "host": "localhost",
                    "socket": "\/tmp\/mysql.sock"
                }
            },
            "slave": {
                "slave_0": {
                    "host": "127.0.0.1",
                    "port": "3306"
                }
            }
        }
    }

Exemple #2 Requêtage en consistence de session

`<?php
$mysqli = new mysqli("myapp", "username", "password", "database");
if (!$mysqli) {
/* Utilisez ici votre propre gestion des erreurs... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
}
/* Séparation des lectures/écritures: maitre utilisé */
if (!$mysqli->query("INSERT INTO orders(order_id, item) VALUES (1, 'christmas tree, 1.8m')")) {
/* Utlisez ici votre propre gestion des erreurs... */
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* Activation de la consistence de session */
if (!mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_SESSION)) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* Le plugin sélectionne un noeud qui possède le changement, ici : le maitre */
if (!$res = $mysqli->query("SELECT item FROM orders WHERE order_id = 1")) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
var_dump($res->fetch_assoc());
/* Retour à la consistence éventuelle : des données non fraiches sont tolérées */
if (!mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_EVENTUAL)) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* Le plugin choisit un esclave, des données non fraiches peuvent être retournées */
if (!$res = $mysqli->query("SELECT item, price FROM specials")) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
?>`

Les niveaux de service peuvent être indiqués dans le fichier de configuration et durant l'exécution via mysqlnd_ms_set_qos(). Dans l'exemple la fonction est utilisée pour forcer la consistence de session pour toutes les requêtes futures jusqu'à nouvel ordre. La requête SELECT sur la table orders est lancée sur le maitre pour s'assurer que l'écriture précédente peut être vue par le client. La logique de spération des lectures/écritures a donc été adaptée pour satisfaire le niveau de service demandé.

Après que l'application n'ait lue ses changements depuis la table orders elle retourne dans le niveau de service par défaut, c'est-à-dire la consistence éventuelle. Celle-ci ne propose aucune restriction quant au choix du noeud, ainsi, la requête SELECT sur la table specials est exécutée sur l'esclave.

Cette fonctionnalité est supérieure aux astices SQL et à l'option de configuration master_on_write. Dans beaucoup de cas, mysqlnd_ms_set_qos() est plus facile à utiliser, plus puissante et plus portable.

Exemple #3 Age maximum / retard de l'esclave

    {
        "myapp": {
            "master": {
                "master_0": {
                    "host": "localhost",
                    "socket": "\/tmp\/mysql.sock"
                }
            },
            "slave": {
                "slave_0": {
                    "host": "127.0.0.1",
                    "port": "3306"
                }
            },
            "failover" : "master"
        }
    }

Exemple #4 Limiter le retard de l'esclave

`<?php
$mysqli = new mysqli("myapp", "username", "password", "database");
if (!$mysqli) {
/* Utilisez ici votre propre gestion des erreurs... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
}
/* Lit depuis des esclaves ayant un retard de 4 secondes maximum */
$ret = mysqlnd_ms_set_qos(
$mysqli,
MYSQLND_MS_QOS_CONSISTENCY_EVENTUAL,
MYSQLND_MS_QOS_OPTION_AGE,
4
);
if (!$ret) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* Le plugin sélectionne un des esclaves, qui a ou pas la donnée */
if (!$res = $mysqli->query("SELECT item, price FROM daytrade")) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* Retour au comportement par défaut : utilisation de tous les esclaves et du maitre */
if (!mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_EVENTUAL)) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
?>`

Le niveau de consistence éventuelle peut utiliser un paramètre optionnel permettant d'indiquer l'âge maximum de retard des esclaves pour leur sélection. Ainsi, le plugin vérifiera SHOW SLAVE STATUS pour tous les esclaves configurés. Dans notre exemple, seuls les esclaves pour qui Slave_IO_Running=Yes, Slave_SQL_Running=Yes et Seconds_Behind_Master <= 4 est vrai sont considérés dans le choix pour l'éxecution de SELECT item, price FROM daytrade.

La vérification SHOW SLAVE STATUS est transparente coté application. Si des erreurs surviennent, elles seront reportées sous forme de Warning. Aucune erreur ne sera émise sur la connexion. Même si tous les SHOW SLAVE STATUS échouent, l'éxecution de la requête de l'utilisateur aura quand même lieu si la reprise sur incident du maitre est activée. Ainsi, aucun changement côté applicatif n'est nécessaire.

> Note: Opérations couteuses et lentes

> Vérifier SHOW SLAVE STATUS pour tous les esclaves ajoute de la charge. C'est une opération couteuse. Malheureusement, le cluster de réplication MySQL n'offre pas d'autre moyen de procéder pour vérifier le retard d'un esclave.

> Notez les limites et les propriétés de SHOW SLAVE STATUS en lisant le manuel de référence de MySQL.

Pour évoter que le plugin ne génère un Warning si aucun esclave satisfaisant le critère de retard ne peut être trouvé, il est nécessaire d'activer la reprise sur incident du maitre dans le fichier de configuration. Dans ce cas, le plugin sélectionnera un maitre.

Si aucun esclave n'est trouvé et que la reprise sur incident est désactivée, le plugin génèrera un Warning, il n'exécutera pas la requête et placera la connexion en erreur.

Exemple #5 Reprise sur incident non activée

    {
        "myapp": {
            "master": {
                "master_0": {
                    "host": "localhost",
                    "socket": "\/tmp\/mysql.sock"
                }
            },
            "slave": {
                "slave_0": {
                    "host": "127.0.0.1",
                    "port": "3306"
                }
            }
        }
    }

Exemple #6 Pas d'esclave avec le critère de retard

`<?php
$mysqli = new mysqli("myapp", "username", "password", "database");
if (!$mysqli) {
/* Utilisez ici votre propre gestion des erreurs... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
}
/* Lit depuis les esclaves dont le retard n'est pas supérieur à 4 secondes */
$ret = mysqlnd_ms_set_qos(
$mysqli,
MYSQLND_MS_QOS_CONSISTENCY_EVENTUAL,
MYSQLND_MS_QOS_OPTION_AGE,
4
);
if (!$ret) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* Le plugin choisit un esclave au hasard, il peut avoir une donnée non fraiche */
if (!$res = $mysqli->query("SELECT item, price FROM daytrade")) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* Cas par défaut: utilisation de tous les esclaves et maitres */
if (!mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_EVENTUAL)) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
?>`

L'exemple ci-dessus va afficher :

    PHP Warning:  mysqli::query(): (mysqlnd_ms) Couldn't find the appropriate slave connection. 0 slaves to choose from. Something is wrong in %s on line %d
    PHP Warning:  mysqli::query(): (mysqlnd_ms) No connection selected by the last filter in %s on line %d
    [2000] (mysqlnd_ms) No connection selected by the last filter