Modélisation et Requêtage en CQL (Cassandra Query Language)#
Pour manipuler des données sur un cluster distribué, Apache Cassandra propose le langage CQL (Cassandra Query Language).
Bien que la syntaxe de CQL rappelle immédiatement le langage SQL (SELECT, INSERT, UPDATE, WHERE), sa philosophie sous-jacente est diamétralement opposée : en Cassandra, il n'existe aucune jointure (JOIN), aucune sous-requête et aucune agrégation globale non partitionnée.
Ce chapitre détaille les règles fondamentales de la modélisation « Query-First », l'anatomie cruciale de la clé primaire et la manipulation des structures de données en colonnes larges.
1. La philosophie de modélisation « Query-First »#
Dans le monde relationnel classique (SQL), on conçoit un modèle de données normalisé (3NF) pour éliminer toute redondance, puis on s'en remet aux jointures pour répondre aux requêtes futures de l'application.
En Cassandra, cette approche est vouée à l'échec : sur un cluster de 100 nœuds répartis dans le monde, une jointure obligerait à déplacer des gigaoctets de données sur le réseau entre les serveurs, anéantissant les temps de réponse.
Les règles d'or de la modélisation Cassandra :#
- Une table par requête applicative : Vous devez d'abord lister précisément les requêtes que votre application exécutera, puis concevoir exactement la table sur mesure qui servira chaque requête en une seule opération de lecture.
- Dénormaliser sans culpabilité : La duplication contrôlée des données entre plusieurs tables n'est pas un défaut, c'est la norme en Cassandra pour garantir des lectures en temps constant \(\mathcal{O}(1)\).
- Optimiser pour les lectures rapides : Le disque dur est bon marché ; la latence réseau et les temps de calcul processeur ne le sont pas.
flowchart TD
subgraph Approche_SQL ["Approche Relationnelle (Data-First)"]
D1[Modéliser les entités réelles] --> D2[Normaliser en 3NF]
D2 --> D3[Ecrire les requêtes avec des JOIN complexes]
end
subgraph Approche_Cassandra ["Approche Cassandra (Query-First)"]
C1[Identifier les requêtes de l'application] --> C2[Déterminer la Partition Key et le tri]
C2 --> C3[Créer une table dédiée par motif de requête]
end
2. Création et gestion des Keyspaces#
Un Keyspace est le conteneur logique de données de plus haut niveau dans Cassandra (l'analogue d'une base de données en SQL). C'est au niveau du Keyspace que l'on définit la stratégie de réplication des données sur le cluster.
Connectez-vous à votre invite interactive cqlsh :
Stratégies de réplication courantes :#
SimpleStrategy: Réservée aux environnements de développement ou aux clusters comportant un seul datacenter. Elle distribue les répliques consécutivement sur l'anneau.NetworkTopologyStrategy: Indispensable en production pour les architectures multi-datacenters. Elle permet de fixer le nombre de copies physiques pour chaque zone géographique (ex: 3 copies en Europe, 3 copies aux USA).
-- Création d'un Keyspace avec SimpleStrategy et facteur de réplication 1
CREATE KEYSPACE IF NOT EXISTS commerce_db
WITH REPLICATION = {
'class': 'SimpleStrategy',
'replication_factor': 1
};
-- Basculer sur le Keyspace actif
USE commerce_db;
3. L'anatomie de la Clé Primaire (PRIMARY KEY)#
La clé primaire est l'élément technique le plus important dans Cassandra. Elle détermine à la fois sur quelle machine physique sera stockée la donnée et dans quel ordre physique elle sera écrite sur le disque dur.
flowchart LR
PK["Clé Primaire : ((user_id, date_jour), timestamp_mesure)"]
PK -->|"(user_id, date_jour)"| PartKey["Partition Key<br/>Hash Murmur3 -> Détermine le nœud sur l'anneau"]
PK -->|"timestamp_mesure"| ClustKey["Clustering Key<br/>Tri physique séquentiel sur disque"]
1. Clé primaire simple (Partition Key unique)#
CREATE TABLE utilisateurs (
user_id uuid,
nom text,
email text,
date_inscription timestamp,
PRIMARY KEY (user_id)
);
2. Clé primaire composite (Partition Key + Clustering Key)#
Imaginons que nous souhaitions stocker les commandes d'un client et toujours les récupérer triées de la plus récente à la plus ancienne :
CREATE TABLE commandes_par_client (
client_id uuid,
commande_id uuid,
date_commande timestamp,
montant decimal,
statut text,
-- client_id = Partition Key (localisation du nœud)
-- date_commande = Clustering Key (tri sur disque)
-- commande_id = Clé d'unicité
PRIMARY KEY (client_id, date_commande, commande_id)
) WITH CLUSTERING ORDER BY (date_commande DESC);
[!NOTE] Grâce à la clause
WITH CLUSTERING ORDER BY (date_commande DESC), les commandes de chaque client sont stockées physiquement pré-triées sur disque dans les fichiers SSTables. Lors d'une lecture, Cassandra lit les lignes de manière contiguë et séquentielle sans solliciter aucun algorithme de tri en mémoire !
3. Clé de partition composée (Compound Partition Key)#
Si un client passe 50 000 commandes par an, une partition unique risque de dépasser la taille recommandée de 100 Mo (Wide Partition anti-pattern). On peut découper la partition en ajoutant par exemple l'année dans la clé de partition :
-- Partition Key composée de deux champs entourés de parenthèses
PRIMARY KEY ((client_id, annee), date_commande, commande_id)
4. Types de données et Collections natives#
En plus des types scalaires usuels (text, int, bigint, float, boolean, timestamp, uuid), Cassandra propose trois types de collections natives particulièrement utiles pour la dénormalisation :
1. SET (Ensemble non ordonné d'éléments uniques)#
CREATE TABLE articles (
article_id uuid PRIMARY KEY,
titre text,
prix decimal,
tags set<text> -- Ensemble de mots-clés uniques
);
-- Insertion avec des accolades
INSERT INTO articles (article_id, titre, prix, tags)
VALUES (uuid(), 'Clavier Mécanique', 129.90, {'informatique', 'gaming', 'rgb'});
-- Ajout d'un nouvel élément sans écraser le reste
UPDATE articles SET tags = tags + {'usb-c'} WHERE article_id = ...;
-- Retrait d'un tag
UPDATE articles SET tags = tags - {'rgb'} WHERE article_id = ...;
2. LIST (Liste ordonnée avec conservation de l'ordre d'insertion)#
CREATE TABLE historiques (
user_id uuid PRIMARY KEY,
connexions list<timestamp>
);
-- Ajout d'une connexion en fin de liste
UPDATE historiques SET connexions = connexions + [toTimestamp(now())] WHERE user_id = ...;
3. MAP (Dictionnaire de paires clé-valeur)#
CREATE TABLE produits_spec (
ref_id text PRIMARY KEY,
caracteristiques map<text, text>
);
INSERT INTO produits_spec (ref_id, caracteristiques)
VALUES ('ECRAN-4K', {'resolution': '3840x2160', 'dalle': 'IPS', 'taux': '144Hz'});
-- Mise à jour d'un attribut spécifique du dictionnaire
UPDATE produits_spec SET caracteristiques['garantie'] = '3 ans' WHERE ref_id = 'ECRAN-4K';
5. Manipulation de données & Règles d'or du filtrage#
Particularité fondamentale : L'équivalence INSERT / UPDATE (Upsert)#
Dans l'architecture de Cassandra, il n'existe aucune différence technique entre une instruction INSERT et une instruction UPDATE.
Lorsqu'une écriture survient, Cassandra ne vérifie pas au préalable si la ligne existe sur le disque (ce qui nécessiterait une lecture coûteuse). Il écrit immédiatement la nouvelle cellule avec son horodatage à la microseconde dans le journal et en mémoire vive.
Expiration automatique des données : Le paramètre TTL (Time To Live)#
Cassandra intègre un mécanisme natif d'auto-nettoyage des données après une durée spécifiée en secondes :
-- Le panier expirera et sera supprimé automatiquement après 2 heures (7200 secondes)
INSERT INTO paniers (panier_id, total)
VALUES (uuid(), 89.50)
USING TTL 7200;
La règle d'or du filtrage SELECT ... WHERE#
[!WARNING] En Cassandra, vous ne pouvez pas filtrer sur n'importe quelle colonne à volonté. Toute condition
WHEREdoit obligatoirement respecter l'une des deux règles suivantes : 1. Cibler la Partition Key complète avec un opérateur d'égalité (WHERE client_id = ...). 2. Si la Partition Key est spécifiée, cibler les Clustering Columns dans leur ordre de déclaration avec des opérateurs d'égalité ou d'intervalle (AND date_commande >= '2026-01-01').
L'antipattern ALLOW FILTERING#
Si vous tentez d'exécuter une requête sans renseigner la clé de partition (ex: SELECT * FROM commandes_par_client WHERE statut = 'LIVREE';), Cassandra refuse la requête :
InvalidRequest: Cannot execute this query without index or ALLOW FILTERING.
Bien qu'ajouter ALLOW FILTERING à la fin de la requête force l'exécution, cette pratique est strictement déconseillée en production : elle oblige le coordinateur à contacter l'intégralité des nœuds du cluster pour lire l'ensemble des données sur disque, dégradant brutalement les performances.
6. Suppression de données et le piège des Tombstones#
Dans une architecture basée sur les arbres LSM, lorsqu'une commande DELETE est exécutée, Cassandra ne supprime rien immédiatement sur le disque physique.
À la place, il écrit un marqueur spécial d'effacement horodaté appelé Tombstone (pierre tombale).
- Lors des futures lectures, Cassandra lit la cellule et son tombstone, et sait qu'il ne doit pas renvoyer la valeur.
- Les tombstones ne sont physiquement purgés du disque que lors de la phase de compaction en arrière-plan, une fois écoulé le délai de sécurité gc_grace_seconds (10 jours par défaut, pour s'assurer que tous les nœuds déconnectés ont bien pris connaissance de la suppression).
Vous maîtrisez à présent la syntaxe et les subtilités architecturales de CQL. Dans le chapitre suivant, nous allons appliquer ces principes à un cas concret d'ingestion massive de données de télémétrie automobile avec Python !
Commentaires