Une précision avant d’entrer dans le vif de l’article : contrairement à ce que vous pourriez croire, ce blog n’est pas un blog dédié à Cassandra. Il se trouve que ce produit a fait sa place dans le système d’informations des entreprises, qu’il soulève beaucoup de questions et que, depuis quelques mois, c’est notre quotidien.
De plus, il nous fallait un endroit où historiser nos notes glanées au cours d’un long et douloureux apprentissage. Alors autant les partager avec vous. Nous sommes comme ça : toujours disposés à aider lorsque cela recoupe notre propre intérêt. « Charité bien ordonnée… », vous connaissez le principe.
Ceci étant dit, le sujet aujourd’hui est le suivant : comment extraire un keyspace Cassandra d’un cluster mutualisé vers un cluster dédié ?
Nous allons commencer par un cas simple : les deux clusters ont le même nombre de membres et l’application peut être interrompue le temps de l’opération. Dans un article prochain, nous verrons comment recopier une table ou un keyspace vers un cluster totalement différent grâce à l’outil SSTableLoader. Mais il nous reste quelques doutes à lever sur son fonctionnement avant de vous partager notre expérience.
Notez que la méthode détaillée aujourd’hui est globalement celle à suivre en cas de restauration complète d’un cluster.
Trêve de circonvolutions : au travail !
Contexte
La plateforme source (notée « SRC » pour la suite) est constituée de 6 nœuds en version 2.1.14. Elle héberge plusieurs applications dont l’une d’elles a obtenu un cluster dédié. Les machines seront nommées : origine01, origine02, origine03, origine04, origine05 et origine06. Le keyspace à déplacer sera « keyspace01 ». Il contient une dizaine de tables et pèsent 20 Go. C’est une chance car le temps total de l’opération dépend de ces deux facteurs. Il est indispensable de scripter au maximum (à coup de boucle « for… in… », de « while read… » et de Perl) sous peine de vous infliger un laborieux travail de moine copiste. Sans parler du risque d’erreur.
La plateforme de destination (notée : « DEST » pour la suite) : 6 nœuds également, même version. Les machines seront : cible01, cible02, cible03, cible04, cible05 et cible06.
Les deux ensembles de machines se voient sur le réseau. La connexion SSH est autorisée pour le compte propriétaire du processus et des fichiers de données. Ce compte doit avoir le droit de relancer le service Cassandra si besoin. Ou de connaître quelqu’un qui en a le droit.
La configuration Cassandra et JVM est identique. Le nom du cluster (« cluster_name » dans le cassandra.yaml) est identique des deux cotés : « CLUSTER ONE » (1). C’est obligatoire car, dans le cas où le nom diffère, il y a une erreur après copie :
ERROR [main] 2016-11-02 11:19:16,415 CassandraDaemon.java:294 - org.apache.cassandra.service.CassandraDaemon - Fatal exception during initialization org.apache.cassandra.exceptions.ConfigurationException: Saved cluster name Cluster One != configured name Cluster Two at org.apache.cassandra.db.SystemKeyspace.checkHealth(SystemKeyspace.java:613) ~[apache-cassandra-2.1.14.jar:2.1.14] at org.apache.cassandra.service.CassandraDaemon.setup(CassandraDaemon.java:290) [apache-cassandra-2.1.14.jar:2.1.14] at org.apache.cassandra.service.CassandraDaemon.activate(CassandraDaemon.java:564) [apache-cassandra-2.1.14.jar:2.1.14] at org.apache.cassandra.service.CassandraDaemon.main(CassandraDaemon.java:653) [apache-cassandra-2.1.14.jar:2.1.14]
Sur les machines de DEST, les binaires Cassandra ont été installés depuis le package officiel pour Redhat. Authentification et chiffrement SSL sont activés.
Le code de création des objets (« create keyspace… », « create table… »…) doit être disponible (et à jour !).
Plan d’action
Un survol de l’enchaînement des actions. Elles sont détaillées dans la suite de l’article.
- DEST : configuration manuelle des tokens.
- DEST : démarrer les nœuds vides. Créer le schéma (utilisateurs et objets). Arrêt des noeuds.
- SRC : sauvegarde coordonnée du keyspace01.
- SRC : copie des fichiers de sauvegardes d’une machine SRC vers une machine DEST.
- DEST : démarrage des nœuds. Vérification des données.
- DEST : synchronisation finale.
- DEST : Conclusion et derniers conseils.
Voyons tout cela de plus près…
DEST : Configuration manuelle des tokens
Le problème : depuis la version 2.0, Cassandra propose une répartition automatique des portions de données (« token range ») . un vrai soulagement pour les aventuriers – les héros, presque ! – qui ont fait leurs armes sur la version 1. Plus besoin de recalculer la distribution manuellement à chaque ajout ou retrait de nœuds (2). Mais cela n’empêche pas Cassandra de toujours faire ce découpage. Or, qui dit changement de cluster dit également changement de découpage. Il faut donc s’assurer qu’il est exactement identique sinon les données seront rejetées après copie.
Attention : C’est nécessaire seulement si vous restaurez sur un cluster différent ! Maigre consolation si vous devez restaurer votre cluster unique depuis sa sauvegarde mais consolation néanmoins. Haut les chœurs !
L’opération est assez simple. Tâchons d’être synthétiques :
Sur SRC, pour chaque nœud, le découpage est récupéré par « nodetool ring ». La documentation Datastax vous indique même la commande exacte pour mettre en forme directement :
nodetool ring | grep ip_addresse_du_noeud_source | awk '{print $NF ","}' | xargs
Où « ip_addresse_du_nœud_source » est à remplacer par l’adresse sur laquelle écoute le service Cassandra.
Le résultat est a mi-chemin entre une hallucination malveillante et un tableau d’Art Moderne.
Il faut le reporter dans le nœud DEST correspondant.
Pour être plus clair : passez la commande sur source01 et ajouter le résultat dans le cassandra.yaml, paramètre « initial_token: » de cible01, faire la même chose pour source02 et cible02,etc. Vous voyez le principe.
Et attention à la syntaxe: il y a un espace après le « initial_token: « . Il est facile de l’oublier lors des copiers/collers et le serveur refusera de démarrer.
Dernière précaution si les nouveaux nœuds ont déjà été démarrés : supprimer le répertoire « system » (oui, oui : « rm -rf $DATA_DIR/system ») afin de forcer Cassandra à relire sa configuration. C’est le genre de détail pour lequel on touche la différence fondamentale de ce type d’architecture par rapport aux SGBDR de ces dernières décénies. Lequel d’entre vous supprimerait sans frémir le catalogue Oracle ou la base « master » en pensant « Ça va se recréer au démarrage » ? Un autre monde, certainement.
DEST : Premier démarrage et création du schéma
Une fois le découpage fixé, nous pouvons démarrer Cassandra. Selon votre version d’OS, ce sera par script ou par le service. Quelque chose du style :
sudo service cassandra start
Ensuite, il faut jouer le script CQL de création du schéma.
cqlsh -f creation_des_objets.cql
Selon l’environnement, d’autres options sont à donner (nom du host, « –ssl » pour chiffrement, « -u nom_utilisateur » pour authentification.
Ceci fait, il faut arrêter tous les nœuds de DEST. Tant que vous êtes sur les machines DEST, récupérez également le chemin complet des dossiers créés pour les objets de votre schéma. Ils sont composés du nom de la table suivi d’un identifiant unique (UUID pour « Unique Universal IDentifier) pour le cluster… et donc différent de celui existant sur SRC (3). Nous en aurons besoin pour la copie des fichiers.
Pour notre exemple, les chemins seront de la forme :
/chemin/vers/données/keyspace01/table01-c56f4330916011e6ad6b61545353fb24 /chemin/vers/données/keyspace01/table02-db0b7d30916011e6ad6b61545353fb24 /chemin/vers/données/keyspace01/tableXX-5229dc80915d11e6b42453e525cff6c3
Parfait. Vérifiez bien que les processus CassandraDaemon sont arrêtés sur DEST (parfois, systemd est taquin et le relance dans votre dos) et vous êtes prêt à passer la sauvegarde de SRC proprement dite.
SRC : sauvegarde coordonnée du keyspace01
Après les complications des phases préparatoires, cette partie est beaucoup plus simple. Le but est de déclencher des sauvegardes (« snapshots ») sur tous les membres de SRC, de la manière la plus synchronisée possible.
Pour cela, vous avez plusieurs solutions. Une boucle :
while read _host; do ssh -q -n ${host} nodetool snapshot keyspace01 -t backup01 ; done < liste_machines_SRC.txt
… fait l’affaire car les snapshots sont immédiats (ce sont des « liens directs » (« hard links ») vers les SSTables.
Si vous êtes quelqu’un de raffiné (4), nous vous conseillons l’excellent dsh, dont l’usage est aussi efficace que le site officiel est laid.
Et il est très laid.
L’avantage de dsh : l’option « -c » qui lance les commandes en mode « concurrent » et minimise ainsi le décalage. Exemple :
dsh -c -g list_machine_SRC -o '-luser_cassandra' -- nodetool snapshot -t backup01 keyspace01
« -t backup01 » demande à ce que le répertoire accueillant les fichiers de sauvegarde soit nommé spécifiquement « backup01 ». Sans lui, le nom sera aléatoire, différent entre les machines et, franchement, vous n’avez pas besoin de ça.
Si tout se passe bien, vous devez trouver un sous-répertoire « backup01 » dans chacun des dossiers des tables, sur chaque nœuds de SRC.
La sauvegarde est faite. Reste à transférer les fichiers. Si vous voulez prendre une pause, c’est le moment.
SRC : copie des fichiers de sauvegarde des machines SRC vers les machines DEST
Il faut garder en mémoire la notion de paire entre SRC et DEST : source01 vers cible01, source02 vers cible02, etc.
A cette étape, le but est de recopier les fichiers contenus dans $DATA_DIR/keyspace01/table01-162538276ggggsjdhgç/snapshot/backup01 vers son partenaire cible, dans $DATA_DIR/keyspace01/table01-c56f4330916011e6ad6b61545353fb24
Il faut construire une commande scp. Pour chacune des tables. Heureusement, les noms sont identiques au sein d’un cluster. Une fois que vous avez les commandes pour un nœud, vous les avez pour tous. Pensez juste à adapter les cibles et tout se passera bien.
Exemple de commandes, d’après une histoire vraie, lancées depuis source01:
scp /data/cassandra/data/keyspace01/table01-3f0100f0fcc711e58ad7c3e089b5a927/snapshots/backup01/* cassandra@cible01:/data/cassandra/data/keyspace01/table01-db0b7d30916011e6ad6b61545353fb24/ scp /data/cassandra/data/keyspace01/table02-3f23cb30fcc711e5b3ef75a7a67d5548/snapshots/backup01/* cassandra@cible01:/data/cassandra/data/keyspace01/table02-cbd6aab0916011e6ad6b61545353fb24/ scp /data/cassandra/data/keyspace01/table03-403932d0fcc711e5a74fadac95eed38d/snapshots/backup01/* cassandra@cible01:/data/cassandra/data/keyspace01/table03-5229dc80915d11e6b42453e525cff6c3/
De machines en machines, patiemment, en changeant source et cible, vous aurez recopié tous les fichiers dans leur répertoire de destination.
A partir de maintenant, tout se passe sur DEST.
DEST : démarrage des nœuds. Vérification des données.
Le plus gros est fait. Il vous reste à démarrer les nœuds en utilisant la même méthode que pour les arrêter.
Vérifier le cassandra.log et le server.log (selon votre configuration) pour un éventuel message d’erreur mais c’est du « tout ou rien » : si les fichiers sont corrects, le serveur démarre. Sinon, il ne démarre pas.
Simple.
La vérification des données est également simple car nous sommes dans le cas d’une copie physique. Aucune chance de perdre des données en cours de route.
Tentez un « describe schema; » après connexion en cqlsh. Si vous avez une requête précise à tester (de préférence autre chose qu’un « select count(*)… » car c’est une mauvaise idée sur ce type d’architecture), vous pouvez également le faire.
DEST : synchronisation finale
Voilà. Vous y êtes. Le moment attendu où les données sont disponibles. Si c’était un déplacement, il vous reste à prévenir les utilisateurs du nouvel emplacement. Si c’était une restauration complète du cluster, vous revenez de loin. Vous êtes sûr de ne pas vouloir faire une pause ? Après tout, vous venez (5) de triompher d’une épreuve considérable.
Dernière opération, la synchronisation des données pour compenser le décalage des snapshots. Sur chaque nœud DEST :
nodetool repair -par keyspace01
Le « -par » autorise des actions en parallèle. Si c’est votre politique habituelle, vous pouvez activer l’incrémentale en ajoutant « -inc ». Le « nodetool repair » fera l’objet d’un article complet. C’est un sujet vaste et brumeux comme une mer de nuages s’étendant jusqu’à l’horizon d’un horizon incertain. Bref : c’est flou.
Cette action est longue et gourmande en ressources. Elle se fera en arrière plan.
DEST : Pour conclure.
Si la longueur de cet article peut laisser penser le contraire, la restauration d’un keyspace est relativement simple pour peu que vous soyez attentif sur les noms des répertoires. Le problème des tokens à fixer manuellement est à prendre en compte pour une copie (cas où vous aurez normalement du temps devant vous). Dans le cas d’une restauration d’un cluster après une catastrophe, vous aurez seulement à remettre les différents fichiers dans leur répertoire d’origine.
Faite un cadeau à votre « moi » futur : sauvegardez les informations de « nodetool ring », préparez les commandes de restauration en même temps que votre commande de sauvegarde. Vous vous remercierez lorsque vous serez dans l’urgence.
Et, puisque nous sommes sur le dbSQWare tech blog, saviez-vous que dbSQWare gère les sauvegardes du cluster ? Non ? Et qu’il simplifie les restaurations en conservant les informations utiles et en générant les commandes de remise en place ? C’est normal puisque l’article n’est pas encore écrit. En l’attendant, si le sujet vous intéresse, nous vous invitons à poser vos questions au support.
Merci de votre attention. En espérant avoir éclairci cette opération heureusement peu fréquente, nous nous retirons avec la satisfaction du devoir accompli et allons pouvoir enfin prendre, sans vouloir trop insister , une pause !
Des problèmes ? des questions ? Exprimez-vous ! Les commentaires sont ouverts. Coquilles et fautes de grammaires sont notre lot quotidien : signalez-les nous à m.capello@dbsqware.com !
(1) vous pouvez écouter le morceau éponyme de Pink Floyd, sur l’album « Division Bell », pour bénéficier d’une expérience techno-multimédia inédite durant votre lecture !
(2) si vous êtes curieux (ou nostalgique), l’utilitaire de calcul est toujours fournis. Il se trouve dans $Cassandra_dir/tools/bin et s’appelle « token-generator ».
(3) pour les chanceux qui trouvent des répertoires pourtant simplement le nom de leur table, c’est qu’ils sont en version 2.0. L’identifiant unique est une nouveauté de la 2.1 et suivantes.
(4) et vous l’êtes forcément puisque vous lisez ces lignes.
(5) en tout cas, nous l’espérons sincèrement.