агрегация и сортировка в elasticsearch

Недавно я начал использовать ElasticSearch, я планирую придерживаться его для сервиса, который я создаю.

В основном у меня есть следующие типы:

  • поиски
  • предложения
  • цены предложений

Каждый поиск имеет набор информации плюс SID (идентификатор поиска), каждое предложение имеет OID (идентификатор предложения) плюс SID поиска и набор цен.

Я получаю данные асинхронно, чтобы не использовать _update, вместо того, чтобы иметь массив цен в предложении и обновлять его, каждая цена хранится в отдельном документе и содержит идентификатор поиска, идентификатор предложения и саму цену.

Я бы:

  • фильтровать по SID
  • агрегировать по OID
  • сортировать агрегаты по цене

Как я мог это сделать? Любой намек? Я читаю документацию о том, как агрегировать, но понятия не имею :(

ИЗМЕНИТЬ:

Вот пример набора данных

ПОИСКИ (uuid — это sid)

{
    'sid_1': { 'q': 'bread', 'sid': 'sid_1' },
    'sid_2': { 'q': 'milk', 'sid': 'sid_2' },
    'sid_3': { 'q': 'donuts', 'sid': 'sid_3' }
}

ПРЕДЛОЖЕНИЯ (uuid — это sid#oid)

{
    'sid_1#kamut-bread': { 'name': 'kamut bread', 'sid': 'sid_1', 'oid': 'kamut-bread' },
    'sid_1#chocolate-bread': { 'name': 'chocolate bread', 'sid': 'sid_1', 'oid': 'chocolate-bread' },
    'sid_1#plastic-bread': { 'name': 'plastic bread', 'sid': 'sid_1', 'oid': 'plastic-bread' },
    'sid_2#soya-milk': { 'name': 'soya milk', 'sid': 'sid_2', 'oid': 'soya-milk' },
    'sid_2#vaccine-milk': { 'name': 'vaccine milk', 'sid': 'sid_2', 'oid': 'vaccine-milk' },
    'sid_2#milk': { 'name': 'milk', 'sid': 'sid_2', 'oid': 'milk' },
    'sid_3#cream-donuts': { 'name': 'cream donuts', 'sid': 'sid_3', 'oid': 'cream-donuts' },
    'sid_3#chocolate-donuts': { 'name': 'chocolate donuts', 'sid': 'sid_3', 'oid': 'chocolate-donuts' },
    'sid_3#square-donuts': { 'name': 'square donuts', 'sid': 'sid_3', 'oid': 'square-donuts' }
}

OFFERS_PRICES (uuid — партнер sid#oid#)

{
    'sid_1#kamut-bread#amazon': { 'partner': 'amazon', 'sid': 'sid_1', 'oid': 'kamut-bread', 'price': 10.1, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
    'sid_1#kamut-bread#store2': { 'partner': 'store2', 'sid': 'sid_1', 'oid': 'kamut-bread', 'price': 11.1, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
    'sid_1#kamut-bread#store3': { 'partner': 'store3', 'sid': 'sid_1', 'oid': 'kamut-bread', 'price': 10.4, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
    'sid_1#kamut-bread#store4': { 'partner': 'store4', 'sid': 'sid_1', 'oid': 'kamut-bread', 'price': 10.8, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
    'sid_1#chocolate-bread#amazon': { 'partner': 'amazon', 'sid': 'sid_1', 'oid': 'chocolate-bread', 'price': 7.1, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
    'sid_1#chocolate-bread#store2': { 'partner': 'store2', 'sid': 'sid_1', 'oid': 'chocolate-bread', 'price': 7.1, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
    'sid_1#chocolate-bread#store3': { 'partner': 'store3', 'sid': 'sid_1', 'oid': 'chocolate-bread', 'price': 8.4, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
    'sid_1#chocolate-bread#store4': { 'partner': 'store4', 'sid': 'sid_1', 'oid': 'chocolate-bread', 'price': 9.8, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
    'sid_1#plastic-bread#amazon': { 'partner': 'amazon', 'sid': 'sid_1', 'oid': 'plastic-bread', 'price': 70.1, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
    'sid_1#plastic-bread#store2': { 'partner': 'store2', 'sid': 'sid_1', 'oid': 'plastic-bread', 'price': 75.1, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
    'sid_1#plastic-bread#store3': { 'partner': 'store3', 'sid': 'sid_1', 'oid': 'plastic-bread', 'price': 88.4, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
    'sid_1#plastic-bread#store4': { 'partner': 'store4', 'sid': 'sid_1', 'oid': 'plastic-bread', 'price': 97.8, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } }
    ...
}

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

После извлечения поиска и предложений я хотел бы:

  • для извлечения цен на SID sid_1
  • группировать цены по OID
  • для сортировки агрегатов по цене (или по цене + конкретная плата, но я могу справиться с этим с groovy)

person Daniele Salvatore Albano    schedule 23.08.2016    source источник
comment
Не могли бы вы дополнить свой вопрос более конкретным примером того, как выглядят данные и какие запросы и агрегации вы хотите выполнять? Какую цель вы хотите достичь?   -  person Andreas Jägle    schedule 23.08.2016


Ответы (1)


Я обнаружил существование типа агрегации scripted_metric и, поиграв с ним, придумал такой запрос

{
    "size": 0,
    "query" : {
        "match_all" : {}
    },

    "aggs": {
        "offer_prices": {
            "scripted_metric": {
                "init_script" : "_agg[\"offers_prices\"] = [:].withDefault{[:]}",

                "map_script" : "def parent = doc._parent.value; def partner = doc.partner.value; def price = doc.price.value; if (!_agg.offers_prices.containsKey(parent)) { _agg.offers_prices[parent] = [ parent: parent, sid: doc.sid.value, oid: doc.oid.value, bestPrice: Double.MAX_VALUE, bestPartner: null, partners: [:] ]; }; _agg.offers_prices[parent].partners[partner] = [ \"partner\": partner, \"price\": price, \"ccfees\": _source.ccfees ]; if (_agg.offers_prices[parent].bestPrice > price) { _agg.offers_prices[parent].bestPrice = price; _agg.offers_prices[parent].bestPartner = partner; }", 

                "combine_script" : "return _agg.offers_prices;",

                "reduce_script" : "def offers_prices_all = [:]; _aggs.each { offers_prices_per_shard -> offers_prices_per_shard.each { oid, offers_prices -> offers_prices_all[oid] = offers_prices}; }; offers_prices_all = offers_prices_all.sort { a, b -> a.value.bestPrice <=> b.value.bestPrice }; return offers_prices_all;"

            }
        }
    }
}

Это не окончательная версия, мне нужно внести некоторые исправления и проверить производительность, но это кажется возможным решением:

  • запрос группирует данные, используя _parent
  • рассчитать лучшую цену агрегации
  • сортировать агрегации по лучшей цене

Еще нужно сделать:

  • сортировать агрегаты по лучшей цене + комиссия
  • отсортировать список партнеров отдельных агрегаций по цене
  • протестировать производительность и потребление ресурсов

Примечание:

  • Я добавил сопоставление _parent и использую свойство _parent документа для группировки данных, но его можно создать вручную, объединив sid и oid.
  • Сценарий использует свойство ccfees, но в примере набора данных, который я разместил выше, это называется сборами.
person Daniele Salvatore Albano    schedule 24.08.2016