web-dev-qa-db-fra.com

SELECT DISTINCT cql ignore la clause WHERE

Exécution de deux requêtes identiques mais le mot clé DISTINCT donne des résultats inattendus. Sans le mot-clé, le résultat est correct mais avec DISTINCT, il ressemble à la clause where est ignorée. Pourquoi ?

Version Cqlsh:

Connected to Test Cluster at localhost:9160.
[cqlsh 4.1.1 | Cassandra 2.0.6 | CQL spec 3.1.1 | Thrift protocol 19.39.0]

Tableau considéré:

DESCRIBE TABLE events;

CREATE TABLE events (
  userid uuid,
  "timestamp" timestamp,
  event_type text,
  data text,
  PRIMARY KEY (userid, "timestamp", event_type)
) WITH
  bloom_filter_fp_chance=0.010000 AND
  caching='KEYS_ONLY' AND
  comment='' AND
  dclocal_read_repair_chance=0.000000 AND
  gc_grace_seconds=864000 AND
  index_interval=128 AND
  read_repair_chance=0.100000 AND
  replicate_on_write='true' AND
  populate_io_cache_on_flush='false' AND
  default_time_to_live=0 AND
  speculative_retry='99.0PERCENTILE' AND
  memtable_flush_period_in_ms=0 AND
  compaction={'class': 'SizeTieredCompactionStrategy'} AND
  compression={'sstable_compression': 'LZ4Compressor'};

Contenu du tableau:

SELECT * FROM events;

 userid                               | timestamp                | event_type | data
--------------------------------------+--------------------------+------------+------
 aaaaaaaa-be1c-44ab-a0e8-f25cf6064b0e | 1970-01-17 09:06:17+0100 |       toto | null
 4271a78f-be1c-44ab-a0e8-f25cf6064b0e | 1970-01-17 09:06:17+0100 |       toto | null
 4271a78f-be1c-44ab-a0e8-f25cf6064b0e | 1970-01-17 09:07:17+0100 |       toto | null
 4271a78f-be1c-44ab-a0e8-f25cf6064b0e | 1970-01-17 09:08:17+0100 |       toto | null
 4271a78f-be1c-44ab-a0e8-f25cf6064b0e | 1970-01-17 09:09:17+0100 |       toto | null
 4271a78f-be1c-44ab-a0e8-f25cf6064b0e | 1970-01-17 09:10:17+0100 |       toto | null

(6 rows)

Request1: demande sans DISTINCT

SELECT userid FROM events WHERE timestamp > '1970-01-17 09:07:17+0100' ALLOW FILTERING;

 userid
--------------------------------------
 4271a78f-be1c-44ab-a0e8-f25cf6064b0e
 4271a78f-be1c-44ab-a0e8-f25cf6064b0e
 4271a78f-be1c-44ab-a0e8-f25cf6064b0e

(3 rows)

Request2: même requête avec DISTINCT

SELECT DISTINCT userid FROM events WHERE timestamp > '1970-01-17 09:07:17+0100' ALLOW FILTERING;

 userid
--------------------------------------
 aaaaaaaa-be1c-44ab-a0e8-f25cf6064b0e
 4271a78f-be1c-44ab-a0e8-f25cf6064b0e

(2 rows)

MODIFIER 1
voici un peu de contexte.
Ce tableau "événements" est soumis à de nombreuses écritures, il reçoit environ ~ 1 000 insertions par seconde et j'ai un script batch qui vérifie ces événements toutes les 5 minutes.
Ce script batch a 2 besoins:
1- Obtenez tous les ID utilisateur qui ont été actifs au cours des 5 dernières minutes (c'est-à-dire chaque ID utilisateur présent dans les événements des 5 dernières minutes)
2- Obtenez tous les événements liés à ces ID utilisateur (pas seulement pour les 5 dernières minutes)

J'avais l'habitude d'avoir deux tables différentes pour gérer cela. Une table "activeusers" pour la première requête et la table "events" comme je l'ai décrit ici pour la deuxième requête. Mon problème avec cela est simplement qu'il nécessite de mon serveur d'écrire dans deux tables différentes lorsqu'il reçoit un événement. J'ai donc essayé ceci en utilisant uniquement la table des événements.

14
Diplow

Cela se produit de cette façon car dans Cassandra CQL DISTINCT est conçu pour renvoyer uniquement les clés de partition (ligne) de votre table (famille de colonnes) ... qui doivent être uniques. Par conséquent , la clause WHERE ne peut fonctionner que sur les clés de partition lorsqu'elle est utilisée avec DISTINCT (ce qui, dans votre cas, n'est pas très utile). Si vous supprimez le DISTINCT, WHERE peut ensuite être utilisé pour évaluer les clés de clustering (colonne) au sein de chaque clé de partition (bien qu'avec ALLOW FILTERING).

Je me sens obligé de mentionner que ALLOW FILTERING n'est pas quelque chose que vous devriez faire beaucoup de ... et certainement pas en production. Si cette requête est une que vous devez exécuter souvent (requête d'événements pour userids après un certain timestamp), je vous suggère de partitionner vos données par event_type au lieu:

PRIMARY KEY (event_type, "timestamp", userid)

Vous pourrez ensuite exécuter cette requête sans ALLOW FILTERING.

SELECT userid FROM events WHERE event_type='toto' AND timestamp > '1970-01-17 09:07:17+0100'

Sans rien savoir de votre application ou de votre cas d'utilisation, cela peut ou non vous être utile. Mais considérez-le comme un exemple et comme une indication qu'il peut y avoir une meilleure façon de construire votre modèle pour satisfaire vos modèles de requête. Consultez article de Patrick McFadin sur la modélisation des données de série temporelle pour plus d'idées sur la façon de modéliser ce problème.

20
Aaron

Comme expliqué par Aaron, lorsque vous utilisez le mot clé DISTINCT, vous ne pouvez filtrer que par clés de partition. La raison derrière cela est l'algorithme derrière les requêtes DISTINCT et la façon Cassandra stocke les données sur disque/mémoire.

Pour comprendre cela, je vais faire une analogie:

Cassandra stocke les informations similaires à un index de livre. Si vous recherchez un chapitre intitulé "Mon troisième chapitre", il vous suffit de le rechercher au premier niveau de l'index, vous n'avez donc qu'à effectuer une recherche itérative dans un ensemble relativement petit. Cependant, si vous recherchez un sous-chapitre appelé "Mon quatrième sous-chapitre" appartenant à "Mon deuxième chapitre", vous devrez effectuer 2 recherches itératives dans 2 ensembles différents, tous deux de petite taille, à condition que l'index ait au moins 2 les niveaux. Plus vous devez approfondir, plus cela peut prendre du temps (vous pouvez toujours avoir de la chance et le trouver très rapidement s'il se trouve au début de l'indice, mais dans ce type d'algorithmes, vous devez tester la moyenne et le pire des cas) et plus l'indice devra être complexe.

Cassandra fait quelque chose de similaire: Keyspace -> Table -> Clé de partition -> Clustering Key -> Column Plus vous avez besoin d'aller plus loin, plus vous devez avoir d'ensembles en mémoire et il faudra plus de temps pour trouver quoi que ce soit. L'index utilisé pour exécuter les requêtes DISTINCT peut même simplement contenir des ensembles jusqu'au niveau de clé de partition, permettant ainsi uniquement de rechercher des clés de partition.

Vous devez vous rendre compte que la recherche dans n'importe quel chapitre qui a un sous-chapitre appelle "Mon deuxième sous-chapitre" (quelle serait l'analogie avec votre requête) nécessite toujours un index profond à 2 niveaux et des recherches itératives à 2 niveaux.

S'ils décident de prendre en charge l'utilisation de DISTINCT sur les clés de clustering, votre requête conviendrait. Pendant ce temps, vous devrez les filtrer dans l'application, probablement en utilisant un type intégré appelé set ou quelque chose de similaire qui gère les valeurs répétées par lui-même.

Ni la solution proposée par Aaron (utilisant l'ID utilisateur comme clé de cluster après l'horodatage) ni celle-ci (filtrage côté client) n'utilise le mécanisme rapide DISTINCT. Sa proposition ne nécessite pas de filtrage côté client car elle gère déjà cela pour vous, mais offre deux inconvénients principaux: elle n'offre pas de compatibilité descendante car vous devrez recréer la table et utiliser une clé de partition constante et ne permet donc pas Cassandra pour répartir ces données entre ses nœuds. N'oubliez pas que chaque valeur de la même clé de partition est stockée dans le même nœud.

5
Adirio