фильтровать и сортировать на основе агрегата с цепной картой Cloudant/CouchDB, уменьшать

Я хотел бы отфильтровать список и отсортировать его на основе совокупности; что-то, что довольно просто выразить в SQL, но я озадачен тем, как лучше всего сделать это с помощью итеративного сокращения карты. Я специально использую дополнение Cloudant «dbcopy» к CouchDB, но я думаю, что этот подход может быть аналогичен другим архитектурам map/reduce.

Псевдокод SQL может выглядеть так:

SELECT   grouping_field, aggregate(*)
FROM     data
WHERE    #{filter}
GROUP BY grouping_field
ORDER BY aggregate(*), grouping_field
LIMIT    page_size

Фильтр может искать совпадение или искать в диапазоне; например field in ('foo', 'bar') или field between 37 and 42.

В качестве конкретного примера рассмотрим набор данных электронных писем; поле группировки может быть «Идентификатор списка», «Отправитель» или «Тема»; агрегатная функция может быть count(*), max(date) или min(date); а предложение фильтра может учитывать флаги, диапазон дат или идентификатор почтового ящика. Документы могут выглядеть так:

{
  "id": "foobar", "mailbox": "INBOX", "date": "2013-03-29",
  "sender": "[email protected]", "subject": "Foo Bar"
}

Получить количество писем от одного и того же отправителя тривиально:

"map": "function (doc) { emit(doc.sender, null) }",
"reduce": "_count"

Кроме того, в Cloudant есть хороший пример сортировки по количеству при втором проходе операции уменьшения карты< /а>. Но когда я также хочу фильтровать (например, по почтовому ящику), все быстро становится грязным.

Если я добавлю фильтр к ключам представления (например, окончательный результат выглядит как {"key": ["INBOX", 1234, "[email protected]"], "value": null}, то сортировка по количеству в пределах одного значения фильтра тривиальна. Но сортировка этих данных по количеству с несколькими фильтрами потребует обхода весь набор данных (по ключу), что слишком медленно для больших наборов данных.

Или я мог бы создать индекс для каждого потенциального выбора фильтра; например окончательный результат выглядит как {"key": [["mbox1", "mbox2"], 1234, "[email protected]"], "value": null}, (если выбраны оба "mbox1" и "mbox2") или {"key": [["mbox1"], 1234, "[email protected]"], "value": {...}}, (если выбран только "mbox1"). Это легко запросить и быстро. Но похоже, что размер индекса на диске будет расти экспоненциально (с количеством отдельных отфильтрованных полей). И это кажется совершенно неприемлемым для фильтрации открытых данных, таких как диапазоны дат.

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

Есть ли способ лучше?


person nicholas a. evans    schedule 03.04.2013    source источник


Ответы (1)


Я думал об этом почти день, и я думаю, что нет лучшего способа сделать это, чем то, что вы предложили. Перед вами стоят следующие задачи:

1) Работа по агрегации (счетчик, сумма и т. д.) может выполняться только в CouchDB/Cloudant API с помощью механизма материализованного представления (mapreduce).

2) Хотя API-интерфейс group_level обеспечивает некоторую гибкость для указания степени детализации переменных во время запроса, он недостаточно гибок для произвольных логических запросов.

3) Произвольные логические запросы возможны в API Cloudant через API _search на основе lucene. Однако _search API не поддерживает агрегированный запрос post. Ограниченная поддержка того, что вы хотите сделать, возможна только в lucene с использованием фасетирования, которое еще не поддерживается в Cloudant. Даже в этом случае я считаю, что он может поддерживать только count, а не sum или более сложные агрегаты.

Я думаю, что лучший вариант, с которым вы сталкиваетесь, — это использовать _search API и использовать sort, group_by или group_sort, а затем выполнять агрегацию на клиенте. Несколько примеров URL-адресов для тестирования будут выглядеть так:

ПОЛУЧИТЬ /db/_design/ddoc/_search/indexname?q=name:mike AND age:[1.2 TO 4.5]&sort=["age","name"]

ПОЛУЧИТЬ /db/_design/ddoc/_search/indexname?q=name:mike AND group_by="mailbox" AND group_sort=["age","name"]

person Mike Miller    schedule 04.04.2013
comment
Я боялся этого. После вчерашних экспериментов с Cloudant _search API я пришел к выводу, что вся агрегация происходила перед запросом, поэтому не было возможности отсортировать объединенную сумму. - person nicholas a. evans; 05.04.2013
comment
Кроме того, я не уверен, что запросы к API _search и выполнение агрегирования на клиенте лучше, чем то, что мы делаем сейчас, то есть создание первого представления карты/уменьшения прохода и агрегирование на клиенте. Для небольших или средних наборов данных это работает просто отлично, но для больших наборов данных (например, десятков тысяч уникальных субъектов или отправителей) это может быть довольно медленным. - person nicholas a. evans; 05.04.2013
comment
Я не очень хорошо знаком с огранкой в ​​lucene. Но даже если он поддерживает только count, мы, вероятно, могли бы сделать это для наиболее важного случая (сортировка по количеству). - person nicholas a. evans; 05.04.2013
comment
re: я не уверен, что запрос API _search и выполнение агрегирования на клиенте лучше, чем то, что мы делаем сейчас, то есть создание первого представления карты/уменьшения прохода и агрегирование на клиенте; Если подумать, вероятно, было бы намного лучше обрабатывать фильтры диапазона (например, даты). - person nicholas a. evans; 05.04.2013