Cassandra - Travaux Pratiques

Les exercices qui suivent sont à effectuer sur machine, avec Cassandra.

Après avoir lancé votre machine Cassandra (avec docker, voir chapitre Modélisation de bases NoSQL), vous aurez besoin d’une interface cliente pour y accéder. Pour cela, nous utiliserons DevCenter de DataStax:

Vous pourrez également interagir en ligne de commande avec cqlsh avec la commande :

sudo docker exec -it mon-cassandra cqlsh
# ceci suppose que mon-cassandra est le nom de votre container
# it pour disposer d'un terminal interactif persistant
# cqlsh pour lancer cette commande au démarrage

Le sujet des travaux pratiques est la mise en place d’une base de données représentant des restaurants, et des inspections de ces restaurants. Un échantillon de données est disponible ici:

Note

Avant de vous lancer dans le travail proprement dit, vous êtes invités fortement à prendre le temps d’ouvrir cette archive zip et d’en examiner le contenu (au moins les en-têtes, pour avoir une première idée de la structure des données initiales).

Bien entendu, on supppose qu’à terme cette base contiendra tous les restaurants du monde, et toutes les inspections, ce qui justifie d’utiliser un système apte à gérer de grosses volumétries.

Partie 1: Approche relationnelle

Nous allons étudier ici la création d’une base de données (appelée Keyspace), puis son interrogation. Cette première phase du TP consiste à créer la base comme si elle était relationnelle, et à effectuer des requêtes simples. Une fois les limites atteintes, nous utiliserons les spécificités de Cassandra pour aller plus loin.

Création de la base de données

Avant d’interroger la base de données, il nous la créer. Pour commencer :

CREATE KEYSPACE IF NOT EXISTS resto_NY WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor': 1};

Nous créons ainsi une base de données resto_NY pour laquelle le facteur de réplication est mis à 1, ce qui suffit dans un cadre centralisé.

Sous csqlsh, vous pouvez maintenant sélectionner la base de données pour vos prochaines requêtes.

USE resto_NY;

L’équivalent existe dans une interface graphique bien entendu.

Tables

Nous pouvons maintenant créer les tables (Column Family pour Cassandra) Restaurant et Inspection à partir du schéma suivant :

 CREATE TABLE Restaurant (
   id INT, Name VARCHAR, borough VARCHAR, BuildingNum VARCHAR, Street VARCHAR,
   ZipCode INT, Phone text, CuisineType VARCHAR,
   PRIMARY KEY ( id )
 ) ;

 CREATE INDEX fk_Restaurant_cuisine ON Restaurant ( CuisineType ) ;

 CREATE TABLE Inspection (
   idRestaurant INT, InspectionDate date, ViolationCode VARCHAR,
   ViolationDescription VARCHAR, CriticalFlag VARCHAR, Score INT, GRADE VARCHAR,
   PRIMARY KEY ( idRestaurant, InspectionDate )
 ) ;

CREATE INDEX fk_Inspection_Restaurant ON Inspection ( Grade ) ;

Nous pouvons remarquer que chaque inspection est liée à un restaurant via l’identifiant de ce dernier.

Pour vérifier si les tables ont bien été créées (sous cqlsh).

DESC Restaurant;
DESC Inspection;

Nous pouvons voir le schéma des deux tables mais également des informations relatives au stockage dans la base Cassandra.

Import des données

Maintenant, nous pouvons importer les fichiers CSV pour remplir les Column Family :

  1. Décompresser le fichier “restaurants.zip” (il contient le fichier “restaurants.csv” et “restaurants_inspections.csv”)

    Note

    En mode console, sur le répertoire de téléchargement du fichier restaurants.zip, il suffit de mettre la commande :

    unzip restaurants.zip
    
  2. Importer un fichier CSV :

  • Dans votre console (machine locale, pas docker), copier les fichiers sous « Docker » (container “Cassandra”)

    docker cp path-to-file/restaurants.csv    docker-container-ID:/
    docker cp path-to-file/restaurants_inspections.csv    docker-container-ID:/
    

    Note

    Le chemin « path-to-file » correspond à l’endroit où a été décompressé le fichier restaurants.zip

    le docker-container-ID peut être récupéré grâce à la commande « docker ps ».

    CONTAINER ID        IMAGE                     COMMAND              CREATED             STATUS              PORTS                                                                                                                                                                                                   NAMES
    b1fa2c7c255d        poklet/cassandra:latest   "/bin/sh -c start"   6 minutes ago       Up 6 minutes        0.0.0.0:32787->22/tcp, 0.0.0.0:32786->7000/tcp, 0.0.0.0:32785->7001/tcp, 0.0.0.0:32784->7199/tcp, 0.0.0.0:32783->8012/tcp, 0.0.0.0:32782->9042/tcp, 0.0.0.0:32781->9160/tcp, 0.0.0.0:32780->61621/tcp   cassandra
    

    le container-ID est : b1fa2c7c255d

  1. Dans la console cqlsh, importer les fichiers “restaurants.csv” et “restaurants_inspections.csv

    use resto_NY ;
    COPY Restaurant (id, name, borough, buildingnum, street,
                        zipcode, phone, cuisinetype)
       FROM '/restaurants.csv' WITH DELIMITER=',';
    COPY Inspection (idrestaurant, inspectiondate, violationcode,
                        violationdescription, criticalflag, score, grade)
       FROM '/restaurants_inspections.csv' WITH DELIMITER=',';
    

    Note

    les fichiers sont copiés à la racine du container, si vous le changez il faut l’impacter dans l’instruction précédente.

    Vous pouvez vérifier l’existence des fichiers dans le container avec :

    ls /*.csv
    

Pour vérifier le contenu des tables:

SELECT count(*) FROM Restaurant;
SELECT count(*) FROM Inspection;

Interrogation

Les requêtes qui suivent sont à exprimer avec CQL (pour Cassandra Query Language) qui est fortement inspirée de SQL. Vous trouverez la syntaxe complète ici :

<https://cassandra.apache.org/doc/latest/cql/dml.html#select>).

Requêtes CQL simples

Pour la suite des exercices, exprimer en CQL les requêtes suivantes :

  1. Liste de tous les restaurants.

  2. Liste des noms de restaurants.

  3. Nom et quartier (borough) du restaurant N° 41569764.

  4. Dates et grades des inspections de ce restaurant.

  5. Noms des restaurants de cuisine Française (French).

  6. Noms des restaurants situés dans BROOKLYN (attribut borough).

  7. Grades et scores donnés pour une inspection pour le restaurant n° 41569764 avec un score d’au moins 10.

  8. Grades (non nuls) des inspections dont le score est supérieur à 30.

  9. Nombre de lignes retournées par la requête précédente.

CQL Avancé

  1. Pour la requête ci-dessous faites en sorte qu’elle soit exécutable sans ALLOW FILTERING.

    SELECT Name FROM Restaurant WHERE borough='BROOKLYN' ;
    
  2. Utilisons les deux indexes sur Restaurant (borough et cuisineType). Trouvez tous les noms de restaurants français de Brooklyn.

  3. Utiliser la commande TRACING ON avant la d’exécuter à nouveau la requête pour identifier quel index a été utilisé.

  4. On veut les noms des restaurants ayant au moins un grade “A” dans leurs inspections. Est-ce possible en CQL?

Partie 2: modélisation spécifique NoSQL

La jointure n’est pas possible avec CQL, mais ce manque est partiellement compensé par la possibilité d’imbriquer les données pour créer des documents qui représentent, d’une certaine manière, le résultat pré-calculé de la jointure.

Cela suppose au préalable la détermination des requêtes à soumettre à la base puisque les données ne sont plus symétriques, et privilégient de fait certains types d’accès (cf. le cours sur la modélisation dans le chapitre Interrogation de bases NoSQL). Plusieurs possibilités s’offrent à vous :

  • Type imbriqué

  • Utilisation d’un map.

Les exercices suivants vous proposent des besoins (requêtes). À vous de définir la bonne modélisation en utilisant l’une des possibilités ci-desssus, et de vérifier qu’elle permet de satisfaire ce type de recherche.

Note

Pour importer un gros fichier de documents JSon, nous avons implémenté une application permettant de lire le document et de l’importer dans une base (présent dans le fichier restaurants.zip) ;

java -jar JSonFile2Cassandra [-host <host>] [-port <port>]
            [-keyspace <keyspace>] [-columnFamily <columnFamily>] [file]

Exemple :

java -jar JSonFile2Cassandra.jar -host 192.168.99.100 -port 32783
    -keyspace resto_NY -columnFamily InspectionRestaurant
    -file InspectionsRestaurant.json

Premier besoin

Notre besoin ici est de pouvoir sélectionner les restaurants en fonction de leur grade. On voudrait par exemple répondre à la question:

noms des restaurants ayant au moins un grade 'A' dans leurs inspections

Voici les étapes à suivre.

  1. Définir le modèle de document associant les restaurants et leurs inspections, en utilisant les types imbriqués, et créer la table.

  2. Insérer un document dans la table.

  3. Faire l’import avec l’utilitaire d’insertion de documents JSON.

  4. Créer un index sur le Grade de la table InspectionRestaurant, puis trouver les restaurants ayant reçu le grade “A” au moins une fois.

Second besoin

Note

Il semble qu’il y ait quelques corrections à effectuer dans les … corrections que nous proposons ci-dessous. Ce sera fait prochainement.

Maintenant, on veut pouvoir rechercher les restaurants par leur quartier (borough).

  1. Est-ce possible sur le schéma précédent?

  2. Proposer un modélisation adaptée, et créer la table. Utiliser cette fois la solution du map avec la date d’insertion comme clé.

  3. Insérer des données dans la nouvelle table, soit directement, soit avec l’utilitaire d’import.

  4. Trouver tous les restaurants du Bronx.

  5. Maintenant, on veut, sur cette second table, trouver tous les restaurants ayant reçu une note “A”. Est-ce possible? Chercher une solution permise par le fait que nous avons utilisé le type map.

Bonus

Pour pouvoir développer une application au dessus de Cassandra, il est nécessaire d’avoir un pilote ou Driver. Vous pourrez les trouver sur la page de DataStaX : <https://academy.datastax.com/all-downloads>