Статьи

Разбивка на страницы и запросы в Кассандре

Пагинация Кассандры была темой нескольких блогов в других местах. В частности, в
этом превосходном блоге Майкла Коппа подробно рассказывается, как это можно сделать с помощью API Cassandra. У нас также был собственный сценарий использования, когда нам требовался постраничный доступ. Однако для нашего варианта использования схема, изложенная Micheal, имеет несколько недостатков.

1. Что если мы хотим извлекать строки по пакетам вместо столбцов?

2. Если во время постраничного поиска есть обновления, есть вероятность, что некоторые элементы будут пропущены. Например, предположим, что последний доступ находится в столбце с ключом столбца с «Флоренс». Таким образом, при следующем извлечении будет получена партия, начинающаяся с «Флоренция» в палатах. Что если столбец с ключом «Кельн» был добавлен? Это не будет включено ни в один из будущих поисков.

3. Также может быть случай использования, когда требуется разбить на страницы результаты, полученные фильтрацией с помощью запроса диапазона, а не извлекать все строки по страницам в семействе столбцов. (На самом деле это был наш случай использования)

Итак, давайте посмотрим, как мы нанесли удар зверю, нумерация страниц Кассандры. Перед этим позвольте мне полностью объяснить наш вариант использования, чтобы было легче понять, что мы сделали и почему мы это сделали. Наш основной случай использования заключался в разбиении на страницы доступа к результатам, возвращаемым запросом диапазона, который может быть четко выражен в SQL-выражении следующим образом.

SELECT * FROM <column_family> WHERE <column_name_1> BETWEEN [from_1] AND [to_1] AND <column_name_2> BETWEEN [from_2] AND [to_2] .... AND <column_name_n> BETWEEN <from_n> AND <to_n>

Здесь каждое column_name является индексом. (На самом деле субиндекс сложного индекса. Описание нашей схемы индексации см. В моем предыдущем блоге « Кассандра: извлеченные уроки» ). Таким образом, наш вариант использования немного сложен в том смысле, что требуется разбить на страницы доступ к результирующему набору, полученному из запроса диапазона. Также нашим требованием было получить все строки, удовлетворяющие этому критерию, не пропуская ни одной строки, при условии, что будут новые добавления, пока мы будем получать строки в пакетах. На самом деле между двумя выборками может быть значительный промежуток времени, поскольку извлеченные данные обрабатываются с использованием запланированной задачи с настраиваемым интервалом в нашем случае использования. 

Кроме того, нам пришлось покинуть комнату для не пакетного доступа к результатам запроса диапазона. И, конечно, мы не использовали OrderedPartitioner. (Зла OrderedPartitioner хорошо документированы в другом месте. Неоптимальное распределение нагрузки, создание горячих точек и т. Д.). Если бы мы использовали OrderedPartitioner, наша жизнь была бы немного проще, так как мы могли бы сделать запрос диапазона по строкам. Но так как мы использовали RandomPartitioner, нельзя также предполагать упорядочение строк с использованием ключей строк.

Хорошо, этого достаточно для затруднения, с которым мы столкнулись пару месяцев назад, когда столкнулись с задачей  «Cassandrafication» нашего уровня данных. Надеюсь, у вас есть идея … Теперь давайте посмотрим, что мы сделали, чтобы улучшить ситуацию.

Сначала нам пришлось иметь дело с нашей неспособностью выполнить запрос диапазона по строкам. У Кассандры есть такая приятная оговорка, что столбцы определенной строки всегда сортируются с использованием ключей столбцов. Таким образом, мы использовали эту тонкость, чтобы наложить порядок на строки. Мы всегда поддерживаем мета-строку, в которой все ключи строки хранятся в виде столбцов. (На самом деле ключ строки является ключом столбца, а значение столбца пусто).

Допустим, этот ряд — RowIndex. (См. Рисунок 1). Теперь при выполнении запроса к семейству столбцов мы сначала запрашиваем эту строку, используя запрос диапазона, и получаем строки, соответствующие критериям, а затем выполняем выборку реальной строки одну за другой, используя выбранные ключи строки. Вам может быть интересно, как строится запрос диапазона, чтобы соответствовать предложениям where в приведенном выше SQL. В нашей схеме ключ строки состоит из конкатенации значения для каждого индекса. ( Индексна самом деле это столбец в определенной строке, и мы используем значение столбца в качестве значения индекса. Это станет более понятным, если взглянуть на первый шаг иллюстрации, показанный на рисунке 2). Так что это схема, которую мы использовали для не пакетного извлечения строк, удовлетворяющих определенному запросу.

 

Рисунок 1: Семейство столбцов с мета-строкой ‘RowIndex’

 

Но для случая использования с нумерацией страниц этого оказалось недостаточно из-за второго недостатка, описанного ранее. Мы поняли, что должен быть порядок из метки времени, чтобы перехватить вновь добавленную строку, даже если ее ключ строки поместил ее в расположение в отсортированном порядке, который находится перед последним доступным ключом строки. Поэтому мы ввели еще одну мета-строку, хранящую метку времени вставки каждой строки. Допустим, этот ключ строки этой мета-строки — TimeStampIndex. Каждый столбец этой строки будет содержать метку времени вставки в качестве ключа столбца, а соответствующий ключ строки строки, вставленной в эту конкретную метку времени, в качестве значения столбца. Итак, теперь нам нужно сделать четыре вещи, которые мы добавим в семейство столбцов.

 

Рисунок 2: Алгоритм вставки строк

1. Создайте ключ строки, используя определенные индексы. Здесь мы используем «сервер» и «время» в качестве индексов.

2. Вставьте ключ строки в RowIndex в виде столбца.

3. Вставьте метку времени вставки строки вместе с ключом строки в качестве столбца в TimeStampIndex.

4. Добавьте саму строку в семейство столбцов.

 

«RowIndex» должен использоваться для не пакетного доступа к результату запроса диапазона, тогда как «TimeStampIndex» должен использоваться для пакетного доступа к результату запроса диапазона.

Теперь, когда мы хотим извлечь строки в пакетах, удовлетворяющих критериям запроса диапазона, сначала мы получаем блок временных меток размера пакета из TimeStampIndex. Затем для каждой строки, связанной с отметкой времени, мы проверяем, соответствует ли строка критериям фильтра. Это простое сравнение строк, позволяющее проверить, находится ли ключ строки между первым и последним значениями диапазона.

Скажем, например, критерии фильтра для приведенной выше иллюстрации следуют предложению where

WHERE 'server' BETWEEN 'esb' and 'esb' and 'hour' BETWEEN '08:00' and '09:00'

Теперь первым значением диапазона запроса будет «esb-08: 00», а последним значением диапазона будет «esb-09: 00». Это выберет события для сервера ‘esb’ в часы с ’08: 00 ‘до ’09: 00’. Поэтому, если ключ строки «esb-08: 23», он будет выбран, а если «esb-09: 23» — нет.

Как видно из этого сценария, мы не использовали мета-строку ‘RowIndex’. Это только для пакетного использования. Таким образом, используя TimeStampIndex, мы можем перехватывать вновь добавленные строки, не пропуская ни одной строки.

Однако это не лишено своих недостатков.

1. Размер партии не соответствует. Даже если порция размера пакета выбрана из запроса, некоторые из этих строк будут отброшены, поскольку они не соответствуют критериям фильтрации. Решение состоит в том, чтобы получать несколько пакетов, пока не будет найдено число строк серии, удовлетворяющих критериям фильтра. Но сейчас мы согласны с несовместимыми размерами партии.

2. Что если существующая строка будет обновлена? Он будет получен во второй раз, так как алгоритм не пропустит ни одну вновь добавленную или обновленную строку. Это может или не может быть желательным в зависимости от варианта использования. Для нас это фактически необходимое поведение, так как нам нужны новые обновления для уже извлеченной строки. Так что с этим у нас тоже все в порядке.

На этом мы завершаем наш выход с нумерацией страниц Кассандры. (Любовная) история продолжается .. (Надеюсь, вы видели знак сарказма в отличие от Шелдона .. :))

Источник: http://chamibuddhika.wordpress.com/2011/12/11/pagination-and-querying-in-cassandra/