SELECT DISTINCT cql игнорирует предложение WHERE

Выполнение двух одинаковых запросов, но ключевое слово DISTINCT дает неожиданные результаты. Без ключевого слова результат нормальный, но с DISTINCT похоже, что предложение where игнорируется. Почему ?

Версия 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]

Таблица рассмотрена:

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'};

Содержание таблицы:

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: запрос без 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: тот же запрос с 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)

РЕДАКТИРОВАТЬ 1. Вот какой-то контекст.
Эта таблица "события" подвергается множеству операций записи, она получает около 1000 вставок в секунду, и у меня есть пакетный сценарий, который проверяет эти события каждые 5 минут.
У этого пакетного сценария 2 потребности:
1 - получить все идентификаторы пользователей, которые были активны за последние 5 минут (то есть каждый идентификатор пользователя, присутствующий в событиях за последние 5 минут);
2 - получить все события, связанные с этими идентификаторами пользователей (не только за последние 5 минут)

Раньше у меня было две разные таблицы, чтобы справиться с этим. Одна таблица «activeusers» для первого запроса и таблица «events», как я описал здесь, для второго запроса. Моя проблема в том, что от моего сервера требуется запись в две разные таблицы, когда он получает событие. Поэтому я попробовал это, используя только таблицу событий.


person Diplow    schedule 24.10.2014    source источник
comment
Просто наблюдение, но вы используете timestamp как часть составного ключа. Я бы предложил использовать здесь timeuuid, чтобы предотвратить коллизии и перезапись записей. timestamp, если все в порядке за пределами первичного ключа.   -  person dtoux    schedule 23.04.2015
comment
Ваш столбец метки времени DESC? Похоже, вам нужно, чтобы это было заказано таким образом.   -  person Chris Gerlt    schedule 01.10.2015


Ответы (2)


Так происходит, потому что в Cassandra CQL DISTINCT предназначен для возврата только ключей раздела (строки) вашей таблицы (семейства столбцов) ... которые должны быть уникальными. Следовательно, предложение WHERE может работать только с ключами раздела при использовании с DISTINCT (что в вашем случае не очень полезно). Если вы уберете DISTINCT, тогда WHERE можно будет использовать для оценки ключей кластеризации (столбца) внутри каждого ключа раздела (хотя и с ALLOW FILTERING).

Я чувствую себя обязанным упомянуть, что ALLOW FILTERING - это не то, чем вы должны много заниматься ... и определенно не в производстве. Если вам нужно часто выполнять этот запрос (запрос событий для userids после определенного timestamp), я бы предложил вместо этого разделить ваши данные по event_type:

PRIMARY KEY (event_type, "timestamp", userid)

Тогда вы сможете выполнить этот запрос без ALLOW FILTERING.

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

Если вы ничего не знаете о вашем приложении или сценарии использования, это может быть вам полезно или бесполезно. Но рассмотрите это как пример и как указание на то, что может быть лучший способ построить вашу модель для удовлетворения ваших шаблонов запросов. Ознакомьтесь с статьей Патрика МакФадина о моделировании данных временных рядов, чтобы больше идей о том, как моделировать эту проблему.

person Aaron    schedule 24.10.2014
comment
Спасибо за ответ, я добавил контекст для своего вопроса. Я немного удивлен, что cqlsh не сообщает мне, что я делаю что-то неправильное при фильтрации по ключу, не являющемуся разделом, хотя тогда я использую ключевое слово DISTINCT. Что касается разрешающей фильтрации, я предполагаю, что два ожидания, которые у меня есть от моей таблицы событий (которую я описал в моем недавнем редактировании), то есть получение события по идентификаторам пользователя и по метке времени, скомпрометированы. - person Diplow; 24.10.2014

Как объяснил Аарон, при использовании ключевого слова DISTINCT вы можете фильтровать только по ключам раздела. Причина этого заключается в алгоритме запросов DISTINCT и в том, как Cassandra хранит данные на диске / в памяти.

Чтобы понять это, я проведу аналогию:

Кассандра хранит информацию аналогично книжному индексу. Если вы выполняете поиск в главе под названием «Моя третья глава», вам нужно только посмотреть на первый уровень индекса, поэтому вам нужно выполнить итеративный поиск только в относительно небольшом наборе. Однако, если вы ищете подраздел под названием «Моя четвертая часть», принадлежащий «Моей второй главе», вам придется выполнить 2 итеративных поиска в 2 разных наборах, оба небольших, при условии, что в указателе есть не менее 2 уровни. Чем глубже вам нужно пройти, тем дольше это может занять (вам все равно может повезти, и вы найдете его очень быстро, если он находится в начале индекса, но в таких алгоритмах вы должны проверить среднее значение и наихудший сценарий) и тем сложнее должен быть индекс.

Кассандра делает нечто подобное: Keyspace -> Table -> Partition Key -> Clustering Key -> Column Чем глубже вам нужно углубиться, тем больше наборов вам нужно иметь в памяти, и потребуется больше времени, чтобы что-то найти. Индекс, используемый для выполнения запросов DISTINCT, может даже содержать только наборы до уровня ключа раздела, что позволяет искать только ключи раздела.

Вы должны понимать, что для поиска в любой главе, в которой есть подраздел под названием «Моя вторая часть» (что было бы аналогией с вашим запросом), по-прежнему требуется двухуровневый глубокий индекс и двухуровневый итеративный поиск.

Если они решат поддерживать использование DISTINCT для ключей кластеризации, тогда ваш запрос будет в порядке. Между тем вам придется фильтровать их в приложении, возможно, используя встроенный тип с именем set или что-то подобное, которое само обрабатывает повторяющиеся значения.

Ни решение, предложенное Аароном (с использованием идентификатора пользователя в качестве ключа кластеризации после отметки времени), ни это решение (фильтрация на стороне клиента) не использует быстрый механизм DISTINCT. Его предложение не требует фильтрации на стороне клиента, поскольку оно уже выполняет это за вас, но предлагает два основных недостатка: оно не предлагает обратной совместимости, поскольку вам придется воссоздать таблицу и использовать постоянный ключ раздела и, следовательно, не позволяет Cassandra для распределения этих данных между своими узлами. Помните, что все значения одного и того же ключа раздела хранятся в одном узле.

person Adirio    schedule 31.05.2016
comment
Между тем вам придется фильтровать их в приложении, возможно, используя встроенный тип с именем set или что-то подобное, которое само обрабатывает повторяющиеся значения. Ха, я нахожусь в Node и new Set(results) это то, что я собираюсь делать. - person Jazzy; 12.07.2019
comment
@Jazzy Конечно, использование встроенных типов на вашем языке помогает. Я просто старался, чтобы мой ответ не зависел от языка. - person Adirio; 15.07.2019