BT

Diffuser les Connaissances et l'Innovation dans le Développement Logiciel d'Entreprise

Contribuez

Sujets

Sélectionner votre région

Accueil InfoQ Articles Cassandra : Introduction à CQL3

Cassandra : Introduction à CQL3

Dans cet article, nous présenterons le langage CQL3 et son mapping vers le modèle de données physique, si vous avez raté l'article précédent sur la modélisation physique en détail dans Cassandra, c'est par ici.

Présentation

CQL veut dire Cassandra Query Language, et nous sommes à la version 3. La première version a été une tentative expérimentale d'introduire un langage de requêtage pour C*. La deuxième version de CQL a été conçue pour requêter les wide rows mais n'était pas assez flexible pour s'adapter à tous les types de modélisation qui existent dans C*.

La troisième version fut la bonne. Cette version existe depuis 2 ans environ et évolue sans cesse pour supporter les nouvelles fonctionnalités introduites côté serveur.

Prise en main

CQL3 se veut assez proche du SQL standard du point de vue syntaxe pour une prise en main rapide par les développeurs tout en présentant des particularités propres à C* que l'on ne retrouve pas dans le monde relationnel.

Sans plus attendre, voyons comment ça marche.

DDL (Date Definition Language)

Tout comme SQL, CQL3 permet de créer des column family/table en utilisant la syntaxe classique CREATE TABLE. Considérons la table user ci-dessous :

    CREATE TABLE user(
        user_id bigint,
        firstname text,
        lastname text,
        age int,
        PRIMARY KEY(user_id)); 

Inutile de dire que cela ressemble comme 2 gouttes d'eau à la syntaxe SQL qu'on a l'habitude de manipuler. Le mot clé PRIMARY KEY permet de définir la clé primaire (clé de partition) dans C*. Dans l'exemple c'est user_id.

Outre la création de table, il est possible de modifier et de supprimer les tables avec la syntaxe ALTER TABLE / DROP TABLE, mais nous aurons le temps d'y revenir en détail plus tard.

DML (Data Manipulation Language)

Pour manipuler les données, CQL3 propose la même chose que SQL pour les 3 types d'opérations : insertion, mise à jour et suppression.

    INSERT INTO user(user_id, firstname, lastname, age) VALUES(10, 'Jean', 'MARTIN', 33);
    UPDATE user SET age = 33 WHERE user_id = 10;
    DELETE FROM user WHERE user_id=10;

Jusque là, les habitués de SQL se retrouvent en terrain connu.

Différences CQL3/SQL

Bien que très ressemblant au SQL, CQL3 reste un habillage cosmétique du modèle de stockage physique de C*. Cela veut dire qu'il y a beaucoup plus de différences fondamentales entre les 2 langages qu'il n'y paraît.

Pas de JOINTURES

La première grande différence avec du SQL, c'est l'absence de jointures. C'est plutôt prévisible, C* n'est pas du tout une base relationnelle, amis des jointures, passez votre chemin !

L'absence de jointures force également à faire un travail en amont sur la modélisation des tables. La plupart du temps, il est conseillé de dénormaliser, i.e. dupliquer les données pour pouvoir récupérer tout ce dont on a besoin en 1 seule requête.

Pas de GROUP BY

Les opérations d'aggrégation telles que GROUP BY ne sont pas disponibles dans Cassandra. Il vous faudra gérer ces aggrégations côté client. Pour des besoins analytiques, on peut se tourner vers les frameworks de traitement de données comme Apache Spark qui offre une panoplie très complète d'outils pour le traitement de données. Cerise sur le gâteau, depuis quelque temps il existe un connecteur open-source Spark/Cassandra pour lire et écrire les données depuis Spark vers C* et vice-versa.

Clause WHERE limitée

Avec CQL3, on peut utiliser la clause WHERE pour filtrer les données, mais avec beaucoup de limitations :

  • relation d'égalité ( = ) sur les #partition, y compris les #partition composite
  • relation d'égalité ( = ) ou d'inégalité (<, ≤, ≥, >) sur les #colonne

Et c'est à peu près tout. CQL3 ne supporte pas les fonctions de conversions comme to_char/to_date, ni de recherche textuelle comme les like %.

Clause ORDER BY limitée

Il est possible d'utiliser la clause ORDER BY, mais seulement sous certaines conditions (nous verrons ceci dans les prochains articles).

Pas de contraintes

Les contraintes d'intégrité/d'unicité telles qu'on les connaît en SQL n'existent pas. Le mot clé PRIMARY KEY vu précédemment n'est là que pour spécifier la #partition (clé de partition). En aucun cas C* n'ira vérifier lors d'un INSERT si la clé primaire existe déjà. Vous pouvez créer plusieurs INSERT différents en utilisant la même clé primaire, C* ne se plaindra pas et se contentera de surcharger les valeurs existantes.

Dualité de INSERT/UPDATE

Une autre différence est l'équivalence du INSERT/UPDATE. A proprement parler, le modèle de données physique de C* n'autorise que des upserts du coup un INSERT ou un UPDATE dans CQL3 se traduira par la même opération côté base. La différence entre ces 2 opértions est purement cosmétique.

Mapping CQL3/stockage physique

Dans ce chapitre, nous allons voir comment C* traduit les requêtes CQL3 en modèle de stockage physique.

Prenons l'exemple de la table user :

    CREATE TABLE user(
        user_id bigint,
        firstname text,
        lastname text,
        age int,
        PRIMARY KEY(user_id)); 

Avec ce script, C* créera une table dont :

  • la #partition sera du type LongType, correspondant au bigint de user_id
  • la #colonne (clé de colonne) sera du type CompositeType(UTF8Type) pour stocker les noms des colonnes ne faisant pas partie de la clé primaire (firstname, lastname et age)
  • le type des cellules sera du BytesType

Maintenant, insérons quelques données dans la table :

    INSERT INTO user(user_id, firstname, lastname, age) VALUES(10, 'Jean', 'MARTIN', 32);
    INSERT INTO user(user_id, firstname, lastname, age) VALUES(11, 'Elise', 'DUCROS', 26);

Voyons comment C* stocke ces données :


-------------------
RowKey: 10
=> (name=, value=, timestamp=1409142151539000)  ???
=> (name=age, value=00000020, timestamp=1409142151539000)
=> (name=firstname, value=4a65616e, timestamp=1409142151539000)
=> (name=lastname, value=4d415254494e, timestamp=1409142151539000)

-------------------
RowKey: 11
=> (name=, value=, timestamp=1409142151542000)  ???
=> (name=age, value=0000001a, timestamp=1409142151542000)
=> (name=firstname, value=456c697365, timestamp=1409142151542000)
=> (name=lastname, value=445543524f53, timestamp=1409142151542000)

Remarquons tout d'abord que la #colonne permet de stocker le nom de colonnes définies dans la table. Ces colonnes sont triées par ordre alphabétique (age vient avant firstname et lastname). Ensuite, nous voyons des valeurs binaires pour les données des cellules. En effet, le type des cellules doit être défini à la création de la table et ne peut plus changer. Si l'on avait choisi le type texte ( UTF8Type ) pour pouvoir visualiser le nom et prénom plus facilement, on aurait du alors convertir l'âge, qui s'exprime en entier, en texte également.

Pour s'accomoder de la variété de types différents définis sur chaque colonne, CQL3 a choisi le type le plus générique possible : BytesType, quitte à sérialiser/désérialiser les valeurs de cellule dans leur type respectif à l'écriture ou à la lecture.

Dernière remarque importante, et non des moindres, on observe l'existence d'une colonne supplémentaire dont la #colonne et la cellule sont vides (name=, value=). Il s'agit de ce qu'on appelle une colonne marqueur. Elle est là pour garantir l'existence de la clé primaire.

Explication : avec la table user définie précédemment, rien ne nous empêche de créer un utilisateur juste avec son user_id sans fournir aucun autre détail (INSERT INTO user(user_id) VALUES(12)). La définition de la table n'impose que user_id comme clé primaire, donc comme colonne obligatoire à fournir lors de l'insertion.

Dans ce cas là, comment C* va t-il stocker les données physiquement ? Va t-il créer une ligne physique juste avec la #partition qui correspond à user_id ?

Cela n'est pas possible, car avec C* pour créer une partition physiquement, il faut obligatoirement une #partition et au minimum une colonne physique. La colonne marqueur est là pour satisfaire cette contrainte d'existence d'une colonne physique minimale.

Conclusion

Avec cet article, nous avons présenté le langage CQL3 et vu comment il se traduit en termes de stockage physique. Dans le prochain article, nous verrons comment CQL3 gère les types composites et les clés primaires composées.

Au sujet de l'Auteur

DuyHai DOAN est Technical Advocate pour Cassandra. Il partage son temps entre les présentations techniques/hand-ons de Cassandra aux conférences/meetups, le développement des outils open-source pour soutenir la communauté et du consulting technique pour des projets utilisant Cassandra. Avant cela, Duy Hai a été développeur freelance sur Cassandra pour le projet Libon chez Orange.

Evaluer cet article

Pertinence
Style

Contenu Éducatif

BT