Несмотря на то, что Elasticsearch наиболее известен своими возможностями полнотекстового поиска, во многих случаях используется еще одна очень мощная функция, которую Elasticsearch предоставляет "из коробки": фреймворк агрегации.
Повышение производительности агрегации в Elasticsearch
Агрегации используются в Kibana повсеместно. Каждая приборная панель с визуализацией, обобщающая данные, полученные от агентов Beats, использует агрегации. Приложение APM компании Elastic, которое является альтернативой инструментарию и мониторингу производительности приложений, имеет в Kibana приложение, которое также в значительной степени использует агрегации для представления данных пользователю. То же самое можно сказать о приложении Machine Learning и SIEM.
Даже если ваш сценарий поиска не включает секцию "aggs" непосредственно в теле запроса, вы все равно можете использовать агрегации, не замечая этого, в Kibana или любом другом инструменте визуализации.
Если вы все же используете агрегации в своем сценарии (или планируете это сделать), следует обратить внимание на некоторые предостережения, чтобы избежать вредных проектных решений, и внести коррективы, чтобы обеспечить максимально эффективную работу агрегаций.
Как повысить производительность агрегации Elasticsearch:
Ограничение области действия путем фильтрации документов
Прежде всего, чем больше документов можно отфильтровать, тем лучше, и именно этого можно добиться с помощью предложения запроса. Поскольку Elasticsearch является распределенной системой, чем больше данных ему необходимо получить для вычисления запрашиваемых агрегированных значений, тем больше усилий потребуется для оркестровки и сетевых накладных расходов, что увеличит конечную производительность и время обработки.
Даже если некоторые агрегированные значения не являются точным значением, а представляют собой скорее приближение, все равно стоит уменьшить объем данных, которые должен учитывать Elasticsearch. Поэтому, если ваш сценарий использования позволяет просто фильтровать, например, по диапазону, учитывая, что ваш пользовательский интерфейс даже не предоставляет пользователю опции фильтрации, представляющие выборки старше последних трех месяцев, воспользуйтесь этим и уменьшите объем запроса, чтобы улучшить обработку агрегированных данных.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | GET sales/_search { "size": 0, "query": { "range": { "sold_at": { "gte": "now-3M/y", "lte": "now" } } }, "aggs": { "by-category": { "terms": { "field": "category", "size": 10 } } } } |
Экспериментируйте с различными настройками шардов
Шарды - один из ключевых компонентов архитектуры Elasticsearch, обеспечивающий распределенную обработку и высокую доступность. Когда речь идет об агрегации, правильное количество шардов может значительно повысить производительность на этапе поиска запроса. Советы по выбору правильного количества шардов для индекса приведены в этом руководстве.
Попытка найти правильное количество шардов для индекса на самом деле является непростой задачей. Даже Elastic считает это "вопросом на миллион долларов", на который часто можно получить ответ "Это зависит от ситуации". Поскольку не существует единственно верного ответа для всех случаев использования, стоит поэкспериментировать.
Учитывая, что в соответствии с конструкцией индекса Elasticsearch невозможно изменить параметр индекса index.number_of_shards без переиндексации. Чтобы изменить количество шардов, необходимо переиндексировать индекс, но вы должны иметь возможность делать это по своему усмотрению, либо для адаптации к новым сценариям, либо просто для экспериментов, и делать это без простоев.
Одним из способов достижения этой цели является использование шаблона alias. В этом шаблоне фактические индексы скрываются за псевдонимами, что позволяет нам:
- Запуск длительных процессов переиндексации с сохранением доступности текущей версии индекса.
- Наличие различных версий индексов позволяет легко переходить от одной к другой, как в целях тестирования/эксперимента, так и для отката в связи с выявленной проблемой, когда новая версия попадает в производство.
- Проведение A/B-тестирования.
Когда речь идет об экспериментах с шардами, шаблон псевдонимов позволяет запустить полный реиндекс в другой индекс с другим параметром index.number_of_shards, дождаться завершения реиндекса, затем переключить псевдоним, некоторое время протестировать новую версию и, наконец, безопасно удалить старую версию индекса.
Другой способ изменить количество шардов в индексе - использовать API shrink или split. Первый из них используется для уменьшения количества первичных шардов индекса, а второй - для увеличения. Эти API абстрагируют процесс создания нового индекса, изменения его настроек и перемещения в него документов. Они пытаются достичь этого с помощью процесса, включающего жесткую привязку сегментов из исходного индекса к целевому индексу, когда это возможно. Несмотря на то, что эти API, вероятно, быстрее, чем реиндекс, они накладывают некоторые требования, такие как:
- Индекс должен быть доступен только для чтения
- Первичные шарды должны располагаться (или быть перемещены) на одном узле
- Индекс должен иметь зеленый статус
- Рекомендуется удалить все шарды-реплики (их можно вернуть после завершения операций).
- Новое количество шардов должно быть в несколько раз больше текущего установленного количества шардов
Другой распространенный подводный камень при задании количества шардов - это установка такого количества, при котором кластер фактически переходит границу, где шардинг дает преимущества при распределенной параллельной обработке, и начинает создавать узкие места в накладных расходах: oversharding.
Это одна из причин, по которой вы должны иметь возможность свободно экспериментировать с количеством шардов, задаваемым для индекса, а также быть готовым пересматривать их количество при росте числа узлов в кластере.
Оценка полей высокой степени кардинальности и глобальных ординалов
В большинстве случаев агрегирование терминов не вызывает проблем с производительностью. Обычно, когда вы применяете агрегацию терминов к полю, где количество генерируемых бакетов в целом невелико, кластер без проблем вычислит ваши агрегаты метрик для каждого бакета. Так происходит, если вы, например, агрегируете показатели по стране, категории товара, полу и т.д.
Но если вам необходимо выполнить агрегацию терминов по полю, для которого существует слишком много уникальных возможных значений, это создает проблему для кластера, известную как проблема агрегации терминов с высокой кардинальностью. Можно вспомнить пример из области электронной коммерции, когда кластеру необходимо выполнить агрегацию по электронным адресам отдельных пользователей (возможно, миллионов, если речь идет о действительно успешной электронной коммерции). В качестве примера из другой области можно привести решение по информационной безопасности, в котором необходимо производить букинг и формировать отчет по метрике для каждого IP-адреса источника.
Агрегация терминов опирается на внутреннюю структуру данных, известную как глобальные ординалы. Эти структуры хранят статистику для каждого уникального значения данного поля. Эта статистика вычисляется и хранится на уровне шардов, а затем объединяется на этапе уменьшения для получения конечного результата.
Производительность агрегирования терминов по заданному полю может ухудшаться по мере увеличения числа уникальных значений этого поля, а также потому, что по умолчанию эти статистики вычисляются лениво, то есть их приходится вычислять при первом запуске агрегирования после последнего обновления. Если у вас есть сценарий, в котором вы интенсивно индексируете документы, содержащие поля с высокой кардинальностью, и часто выполняете агрегацию терминов по этим полям, то ваш кластер может столкнуться с этой проблемой, поскольку глобальные ординалы будут часто пересчитываться.
Подробнее о глобальных ординатах и о том, как оценить их влияние на производительность агрегации, читайте в этом руководстве.
Увеличение интервала обновления
Каждый раз, когда документ создается, обновляется или даже удаляется, эта операция сначала выполняется во временной структуре, известной как буфер индексирования. Elasticsearch сбрасывает содержимое этих временных структур в другие структуры данных на диске, называемые сегментами, либо каждый раз, когда сегмент заполняется, либо когда достигается предел заданного интервала обновления. По умолчанию этот интервал обновления установлен на 1 секунду.
Проблема с объединением сегментов в более крупные сегменты заключается в том, что это приводит к дополнительным накладным расходам, которых невозможно избежать, если вам действительно необходимо, чтобы недавно поступившие данные были доступны для поиска как можно быстрее. С другой стороны, если увеличить интервал обновления индекса, то это не только повысит производительность при получении данных, но и позволит высвободить часть ресурсов кластера для выполнения запросов.
Кроме того, менее частое обновление означает менее частое вычисление глобальных ординалов, что также может способствовать повышению общей производительности выполнения агрегации.
Интервал обновления устанавливается для индекса путем изменения параметра refresh_interval следующим образом:
1 2 3 4 | PUT my_index/_settings { "index.refresh_interval": "5s" } |
Установите для параметра size значение 0
Elastic рекомендует в тех случаях, когда поисковые хиты не нужны, устанавливать параметр size равным 0. Это позволяет избежать заполнения кэша запросов шардов, что может улучшить производительность агрегации.
1 2 3 4 5 6 7 8 9 10 11 | POST my-index-000001/_search { "size": 0, "aggs": { "my-agg-name": { "terms": { "field": "my-field" } } } } |
Использование преимуществ кэширования узлов и шардов
По умолчанию каждый запрос к кластеру будет назначен на любой из доступных и подходящих для выполнения узлов и шардов.
В API _search может быть передан параметр preference, который может повлиять на выбор кластером узла/шардов, отвечающих за выполнение запроса. Этот параметр можно использовать для использования преимуществ кэширования узлов и шардов, поскольку кластер будет направлять запросы с одной и той же строкой предпочтения на одни и те же шарды в одном и том же порядке, пока состояние кластера и выбранных шардов не изменится.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | POST /my-index-000001/_search?preference=my-custom-shard-string { "query": { "match": { "user.id": "kimchy" } }, "aggs": { "my-agg-name": { "terms": { "field": "my-field" } } } } |
В качестве строки предпочтений можно задать любую строку, не начинающуюся с символа _. Если состояние кластера и выбранные шарды не меняются, то запросы, использующие одно и то же значение строки, направляются на те же шарды в том же порядке. Рекомендуется использовать значение, представляющее сессию пользователя, поскольку часто пользователь запускает несколько запросов подряд, лишь сужая результаты. Это позволит избежать распространения поискового запроса на другой узел и не использовать преимущества кэширования на уровне узла.
Для параметра preference можно задать специальное значение _local. При этом узел, который в данный момент решает запрос, будет стараться использовать только локально доступные шарды, если таковые имеются, чтобы избежать сетевых нагрузок. Если для решения запроса требуются данные из шардов, доступных только на другом узле, то в качестве запасного варианта используется адаптивный выбор реплики.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | POST /my-index-000001/_search?preference=_local { "query": { "match": { "user.id": "kimchy" } }, "aggs": { "my-agg-name": { "terms": { "field": "my-field" } } } } |
С помощью параметра preference можно также принудительно направить запрос на определенные узлы, что может быть полезно, например, если вы хотите предоставить особые условия пользователям и направить их запросы на узлы с самым быстрым оборудованием. Вот некоторые другие специальные значения, которые может принимать параметр preference:
- _only_local
- _local
- _only_nodes:<node-id>,<node-id>
- _prefer_nodes:<node-id>,<node-id>
- _shards:<shard>,<shard>
Агрегируйте только то, что вам нужно
Это кажется очевидным, но не помешает напомнить: агрегации могут быть очень требовательны к ресурсам кластера, и поэтому необходимо хорошо понимать, какие агрегации вы запрашиваете и действительно ли они вам нужны. В критических случаях, когда запрос состоит из секций запроса и агрегации, одна неработающая агрегация может привести к отказу всего запроса.
Поэтому убедитесь, что вам действительно нужны все те агрегации, которые вы просите кластер решить за вас.
Кроме того, вы запрашиваете агрегацию статистики по полю, но используете только среднее значение? Измените его на агрегацию avg. Опять же, знайте, что вам нужно, и просите кластер только об этом.