Очень медленная агрегация терминов elasticsearch. Как улучшить?

У нас есть ~ 20 миллионов документов (предложения отелей), хранящихся в эластичном (1.6.2), и цель состоит в том, чтобы сгруппировать документы по нескольким полям (duration, start_date, adults, kids) и выбрать одно самое дешевое предложение из каждой группы. Мы должны отсортировать эти результаты по полю стоимости.

Чтобы избежать субагрегации, мы объединили значения целевых полей в одно под названием default_group_field, соединив их точкой (.).

Отображение поля выглядит так:

  "default_group_field": {
    "index": "not_analyzed",
    "fielddata": {
      "loading": "eager_global_ordinals"
    },
    "type": "string"
  }

Выполняемый нами запрос выглядит так:

{
  "size": 0,
  "aggs": {
    "offers": {
      "terms": {
        "field": "default_group_field",
        "size": 5,
        "order": {
          "min_sort_value": "asc"
        }
      },
      "aggs": {
        "min_sort_value": {
          "min": {
            "field": "cost"
          }
        },
        "cheapest": {
          "top_hits": {
            "_source": {}
            },
            "sort": {
              "cost": "asc"
            },
            "size": 1
          }
        }
      }
    }
  },
  "query": {
    "filtered": {
      "filter": {
        "and": [
          ...
        ]
      }
    }
  }
}

Проблема в том, что такой запрос загружается за секунды (2-5сек).

Однако, как только мы выполним запрос без агрегации, мы получим умеренное количество результатов (скажем, "total": 490) менее чем за 100 мс.

{
  "took": 53,
  "timed_out": false,
  "_shards": {
    "total": 6,
    "successful": 6,
    "failed": 0
  },
  "hits": {
    "total": 490,
    "max_score": 1,
    "hits": [...

Но с агрегацией это занимает 2 секунды:

{
  "took": 2158,
  "timed_out": false,
  "_shards": {
    "total": 6,
    "successful": 6,
    "failed": 0
  },
  "hits": {
    "total": 490,
    "max_score": 0,
    "hits": [

    ]
  },...

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

Журнал заполнен строками, в которых говорится:

[DEBUG][index.fielddata.plain ] [Karen Page] [offers] Global-ordinals[default_group_field][2564761] took 2453 ms

Вот почему мы обновили наше сопоставление, чтобы выполнить быструю перестройку global_ordinals при обновлении индекса, однако это не оказало заметного влияния на время выполнения запросов.

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

А может есть другой источник столь долгого выполнения запроса? Любые идеи высоко ценятся!


person prikha    schedule 03.06.2016    source источник
comment
Можете ли вы удалить агрегацию top_hits и повторить попытку? (просто чтобы посмотреть, самый тяжелый он или нет, как я предполагаю)   -  person Andrei Stefan    schedule 06.06.2016
comment
Хм... не могли бы вы объяснить, почему вы добавили самую дешевую деталь? Что самое дешевое и как вы это используете? Похоже, вам не нужна эта часть. И, если возможно, вы можете предоставить более подробную информацию о вашем сопоставлении или запросе?   -  person govindpatel    schedule 07.06.2016
comment
@AndreiStefan top_hits - это не проблема, на самом деле ее можно устранить. Но это было оставлено для полноты картины.   -  person prikha    schedule 08.06.2016
comment
@prikha можете ли вы предоставить образец документа?, если возможно, я попытаюсь решить его снова. Спасибо,   -  person govindpatel    schedule 08.06.2016
comment
@govindpatel Вот оно: gist.github.com/prikha/6b117574284c3e4744169f0813386d13 решение основной проблемы с таймингами.   -  person prikha    schedule 08.06.2016


Ответы (3)


еще раз спасибо за усилия.

Наконец-то мы решили основную проблему, и наша производительность вернулась в норму.

Короче говоря, мы сделали следующее: - обновили отображение для default_group_field, чтобы оно имело тип Long - сжали значения default_group_field, чтобы они соответствовали типу Long

Некоторые пояснения:

Агрегации строковых полей требуют выполнения над ними некоторой работы. Как видно из логов, построить Global Ordinals для этого поля с очень большой дисперсией было очень дорого. Фактически мы делаем только агрегации по упомянутому полю. При этом не очень эффективно использовать тип String.

Итак, мы изменили сопоставление на:

default_group_field: {
  type: 'long',
  index: 'not_analyzed'
}

Таким образом, мы не затрагиваем эти дорогостоящие операции.

После этого и того же тайминг запроса сократился до ~100 мс. Это также снизило загрузку процессора.

PS 1

У меня много информации из документов на глобальные порядковые номера

PS 2

Тем не менее я понятия не имею, как обойти эту проблему с полем типа String. Пожалуйста, прокомментируйте, если у вас есть идеи.

person prikha    schedule 08.06.2016
comment
RE: PS 2. Если вы используете тип String, вы могли бы улучшить производительность, активно создавая глобальные порядковые номера- elastic.co/guide/en/elasticsearch/reference/current/ и alexmarquardt.com/ - person Alexander Marquardt; 13.10.2018

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

В следующем блоге рассматривается вероятная причина низкой производительности и несколько подходов к ее устранению.

https://www.elastic.co/blog/improving-the-performance-of-high-cardinality-terms-aggregations-in-elasticsearch

person Alexander Marquardt    schedule 10.05.2019

В порядке. Я попытаюсь ответить на этот вопрос. В вопросе есть несколько частей, которые я не смог понять, например -

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

Я не уверен, что вы на самом деле имеете в виду, потому что вы сказали, что

Вы добавили это поле, чтобы избежать агрегации (но как? а также как вы избегаете агрегации, если вы joining them with dot(.)?)

В порядке. Даже я новичок в эластичном поиске. Так что, если я что-то пропустил, вы можете прокомментировать этот ответ. Спасибо,

Я продолжу отвечать на этот вопрос.

Но перед этим я предполагаю, что у вас есть поле (default_group_field) для различения записей duration, start_date, adults, kids.

Я постараюсь привести один пример ниже после моего решения.

Мое решение:

{
  "size": 0,
  "aggs": {
    "offers": {
      "terms": {
        "field": "default_group_field"
      },
      "aggs": {
        "sort_cost_asc": {
          "top_hits": {
            "sort": [
              {
                "cost": {
                  "order": "asc"
                }
              }
            ],
            "_source": {
              "include": [ ... fields you want from the document ... ]
            },
            "size": 1
          }
        }
      }
    }
  },
  "query": {
"... your query part ..."
   }
}

Я попытаюсь объяснить, что я пытаюсь сделать здесь:

Я предполагаю, что ваш документ выглядит так (может быть, есть и вложенность, но, например, я пытаюсь сделать документ максимально простым):

документ1:

{
"default_group_field": "kids",
"cost": 100,
"documentId":1
}

документ2:

{
"default_group_field": "kids",
"cost": 120,
"documentId":2
}

документ3:

{
"default_group_field": "adults",
"cost": 50,
"documentId":3
}

документ4:

{
"default_group_field": "adults",
"cost": 150,
"documentId":4
}

Итак, теперь у вас есть эти документы, и вы хотите получить мин. документ затрат для adults и kids:

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

    {
      "size": 0,
      "aggs": {
        "offers": {
          "terms": {
            "field": "default_group_field"
          },
          "aggs": {
            "sort_cost_asc": {
              "top_hits": {
                "sort": [
                  {
                    "cost": {
                      "order": "asc"
                    }
                  }
                ],
                "_source": {
                  "include": ["documentId", "cost", "default_group_field"]
                },
                "size": 1
              }
            }
          }
        }
      },
      "query": {
         "filtered":{ "query": { "match_all": {} } }   
       }
    }

Чтобы объяснить приведенный выше запрос, я группирую документ по "default_group_field", а затем мне sorting each group by cost, а size:1 помогает мне получить только один документ.

Поэтому результат для этого запроса будет мин. документ затрат в каждой категории (adults и kids)

Обычно, когда я пытаюсь написать запрос для эластичного поиска или db. Я стараюсь минимизировать количество документов или строк.

Я предполагаю, что я прав в понимании вашего вопроса. Если я неправильно понял ваш вопрос или сделал какую-то ошибку, пожалуйста, ответьте и дайте мне знать, где я ошибся.

Спасибо,

person govindpatel    schedule 07.06.2016
comment
Еще раз спасибо за усилия! К сожалению, это не помогает. При этом теряется пагинация и сортировка по стоимости top_hits. - person prikha; 08.06.2016