← tous les articles

Cassandra

Mes premières impressions après avoir découvert Cassandra

En venant de SQL Server, les surprises ne portaient pas sur l'échelle. Pas de joins, dénormaliser exprès, concevoir la primary key avant que la table existe, et un query language qui ressemble à SQL mais refuse des choses que SQL autorise.

Mes premières impressions après avoir découvert Cassandra

J'ai utilisé Cassandra pour la première fois dans un URL shortener que j'ai construit pour travailler sur une vidéo de system design. Tout ce que j'avais lu à son sujet auparavant parlait d'échelle et de distribution. Les choses qui m'ont vraiment surpris en l'utilisant étaient bien plus basiques que ça.

CQL ressemble à SQL, ce qui est un piège

Le query language est assez proche de SQL pour que j'aie tapé mes premières statements sans réfléchir :

var stmt = await _session.PrepareAsync(
    "SELECT code, id, long_url, short_url, created_on_utc " +
    "FROM shortened_urls WHERE code = ?");

C'est juste du SQL pour autant que je puisse le dire en le lisant. La familiarité m'a fait supposer que je savais ce que je faisais, jusqu'à ce que j'essaie quelque chose d'ordinaire et que ça refuse.

Il n'y a pas de joins. Ce n'est pas « les joins sont lents, évitez-les » comme on le dit des bases de données relationnelles. Ils n'existent tout simplement pas. Même chose pour les clauses WHERE arbitraires : vous ne pouvez pas filtrer par n'importe quelle colonne qui vous arrange, parce que vous ne pouvez faire des queries que dans les façons que la structure de key de la table prend en charge. En venant de SQL Server, où n'importe quelle colonne est permise et le query planner s'en occupe, ça ressemblait moins à une limitation qu'au database qui était cassé.

Il n'est pas cassé. Il ne vous laisse simplement pas écrire une query à laquelle il ne peut pas répondre efficacement sur un cluster distribué, ce qui est sans doute plus honnête que de vous laisser l'écrire et la laisser être silencieusement catastrophique.

La primary key est une décision de design, pas une colonne d'ID

En SQL, j'ajoute une colonne Id surtout par habitude et je réfléchis aux queries après. Dans Cassandra, la key détermine sur quel node vit la donnée, et donc ce que vous pouvez rechercher efficacement.

Ma table a fini aussi simple que possible :

CREATE TABLE IF NOT EXISTS url_shortener.shortened_urls
(code text PRIMARY KEY, id uuid, long_url text, short_url text, created_on_utc timestamp)

Le short code est la primary key, pas le uuid. C'est l'inverse de ce que j'aurais fait instinctivement en SQL, où le GUID aurait été la key et code aurait reçu un index. Mais chaque lecture dans cette application, c'est « donne-moi la ligne pour ce code », donc le code doit être la key. Le uuid n'est qu'une donnée qui se trouve être là.

Prendre cette décision correctement était facile ici parce que le pattern d'accès est trivial. Sur quelque chose avec plus d'une forme de query, j'aurais dû y réfléchir bien plus en amont, et je soupçonne que c'est là que Cassandra devient vraiment difficile.

Vous dénormalisez exprès

Le conseil sur lequel je tombais sans cesse, c'est que si vous avez besoin de la même donnée disponible via deux keys différentes, vous la stockez deux fois, dans deux tables, chacune avec la key dont cette query a besoin.

Tout instinct que je me suis construit dit que dupliquer des données, c'est comme ça que ça part en vrille, parce que maintenant il y a deux copies qui peuvent être en désaccord. Dans Cassandra, c'est le design attendu, et garder les copies synchronisées est le travail de l'application plutôt que du database. Je comprends pourquoi, puisqu'il n'y a pas de joins pour rassembler les choses entre les nodes, mais ça faisait quand même bizarre à taper.

Le setup implique des keyspaces, et la replication est un nombre que vous choisissez

Avant de créer une table, il y a un keyspace, qui est à peu près un database, et il veut savoir combien de copies de la donnée garder :

session.Execute(
    "CREATE KEYSPACE IF NOT EXISTS url_shortener " +
    "WITH replication = {'class':'SimpleStrategy','replication_factor':1}");

Un replication factor de un signifie une seule copie, ce qui va bien sur mon ordinateur portable et serait une mauvaise idée en production, où vous voudriez la donnée sur plusieurs nodes pour qu'en perdre un ne perde pas la donnée.

Ce qui m'a frappé, c'est que c'est une décision que j'ai dû prendre à la première ligne du setup. Dans SQL Server, combien de copies de mes données existent est une question d'infrastructure que je ne touche jamais dans le code de l'application. Ici, ça fait partie du schema.

Était-ce le bon choix pour ce projet ?

Pour apprendre, oui. Pour le projet sur ses propres mérites, honnêtement non. Un URL shortener avec mon trafic serait parfaitement servi par SQLite. Cassandra est construit pour une échelle que je n'ai pas, et toute la raison pour laquelle je l'ai utilisé était de travailler avec le database qui correspond à la version réelle du problème plutôt qu'à la version que j'avais vraiment.

Ce que j'en ai retenu

Le changement utile n'était pas d'apprendre Cassandra en soi. C'était de réaliser que dans un database relationnel je conçois la donnée et je trouve les queries après, et qu'ici je conçois les queries et l'agencement de la donnée suit. C'est une façon vraiment différente de commencer, et ça explique la plupart des restrictions qui semblaient arbitraires au début.