Elasticsearch — это механизм поиска и анализа с открытым исходным кодом, основанный на Apache Lucene, который позволяет пользователям хранить, искать и анализировать данные почти в реальном времени. Pronto — это платформа eBay для размещения кластеров Elasticsearch, упрощающая внутренним клиентам eBay развертывание, эксплуатацию и масштабирование Elasticsearch для полнотекстового поиска, аналитики в реальном времени и мониторинга событий журнала. Сегодня Pronto управляет более чем 60 кластерами Elasticsearch с более чем 2000 узлов. Ежедневный объем сбора данных достигает 18 миллиардов документов, а средний ежедневный объем запросов достигает 3,5 миллиардов. Платформа предоставляет полный набор возможностей для создания, восстановления, обеспечения безопасности, мониторинга, оповещения и диагностики.
Хотя Elasticsearch предназначен для быстрых запросов, его производительность сильно зависит от сценария приложения, объема проиндексированных данных и скорости, с которой приложения и пользователи запрашивают данные. В этой статье кратко описаны проблемы, с которыми столкнулась команда Pronto, а также процессы и инструменты, созданные для их решения, а также представлены некоторые результаты сравнительного анализа нескольких конфигураций.
испытаниеПроблемы, с которыми столкнулись варианты использования Pronto/Elasticsearch, включают:
-
Высокая пропускная способность: некоторые кластеры собирают до 5 ТБ данных в день, а некоторые кластеры превышают 400 миллионов поисковых запросов в день. Если Elasticsearch не сможет своевременно обработать эти запросы, возникнет отставание от восходящих запросов.
-
Низкая задержка поиска: для кластеров, критически важных для производительности, особенно онлайн-ориентированных систем, требуется низкая задержка поиска, иначе это повлияет на взаимодействие с пользователем.
-
Поскольку данные или запрос являются переменными, оптимальные настройки также могут быть переменными. Не существует оптимальной настройки для всех ситуаций. Например, разбиение индекса на большее количество сегментов может быть полезным для запросов, отнимающих много времени, но это может повлиять на производительность других запросов.
Чтобы помочь нашим клиентам решить эти проблемы, команда Pronto разработала стратегический подход к тестированию производительности, настройке и мониторингу развертывания вариантов использования и всего жизненного цикла кластера.
-
Предполагаемый размер кластера: соберите предоставленную клиентом информацию, такую как пропускная способность, размер документа, количество документов и типы поиска, чтобы оценить начальный размер кластера Elasticsearch до запуска нового варианта использования.
-
Оптимизируйте дизайн индекса: оцените дизайн индекса вместе с клиентами.
-
Настройка производительности индекса. Выполните настройку производительности индекса и производительности поиска на основе пользовательских сценариев.
-
Настройка производительности поиска: запускайте тесты производительности с реальными пользовательскими данными/запросами, сравнивайте и анализируйте результаты тестов для различных параметров конфигурации Elasticsearch.
-
Запустите тесты производительности: после того, как история пользователя будет запущена, кластер отслеживается, и пользователи могут повторно запускать тесты производительности при изменении данных, запросов или увеличении трафика.
Команда Pronto проводит тесты для каждого типа машин и каждой поддерживаемой версии Elasticsearch, собирает данные о производительности, а затем объединяет информацию, предоставленную клиентами, для оценки начального размера кластера, в том числе:
-
Индексная пропускная способность
-
размер документа
-
пропускная способность поиска
-
тип запроса
-
Количество проиндексированных документов в горячих точках
-
Политика удержания
-
требования к времени отклика
-
Уровень SLA
Прежде чем мы начнем индексировать данные и выполнять запросы, давайте подумаем об этом. Что именно означает индекс? Официальный ответ Elastic: «набор документов с некоторыми схожими характеристиками». Итак, следующий вопрос: «Какие функции следует использовать для группировки данных? Должны ли все документы помещаться в один индекс или в несколько индексов?» Ответ заключается в том, что это зависит от используемого запроса. Вот несколько советов по организации индексов на основе наиболее распространенных запросов.
Если в запросе есть поле фильтра и его значения перечислимы, то разбить данные на несколько индексов. Например, в Elasticsearch загружается много глобальной информации о продуктах, в большинстве запросов есть фильтр по «региону», и очень мало возможностей для выполнения межрегиональных запросов. Следующее тело запроса можно оптимизировать:
{
"query": {
"bool": {
"must": {
"match": {
"title": "${title}"
}
},
"filter": {
"term": {
"region": "US"
}
}
}
}
}
скопировать код
В этом случае, если индекс разделен на несколько меньших индексов по регионам, таким как США, ЕС и т. д., предложение фильтра можно удалить из запроса, и производительность запроса будет выше. Если нам нужно выполнить межрегиональный запрос, мы можем передать Elasticsearch несколько индексов или подстановочных знаков.
Если в запросе есть поле фильтра и его значение не перечислимо, рекомендуется использовать маршрутизацию. Используя значение поля фильтра в качестве ключа маршрутизации, мы можем индексировать документы с одним и тем же значением поля фильтра в один и тот же сегмент и удалить предложение фильтра.
Например, при наличии миллионов заказов, хранящихся в кластере Elasticsearch, большинство запросов содержат идентификатор покупателя в качестве определяющего условия. Невозможно создать индекс для каждого покупателя, поэтому мы не можем разделить данные на несколько индексов по идентификатору покупателя. Подходящее решение — использовать маршрутизацию, чтобы поместить все заказы с одним и тем же идентификатором покупателя в один сегмент. Тогда почти все запросы можно будет выполнять внутри сегмента, соответствующего ключу маршрутизации.
Строить данные по дате, если в запросе есть предложение фильтра диапазона дат. Это работает для большинства сценариев ведения журналов и мониторинга. Мы можем упорядочить индексы по дням, неделям или месяцам, а затем получить список индексов в указанном диапазоне дат, чтобы Elasticsearch запрашивал только меньший набор данных, а не весь набор данных. Кроме того, старые индексы легко удалить по истечении срока действия данных.
Явно установить сопоставление. Хотя Elasticsearch может динамически создавать сопоставления, созданные сопоставления могут не подходить для всех сценариев. Например, сопоставления строковых полей по умолчанию в Elasticsearch 5.x имеют тип «ключевое слово» и «текст». В этом нет необходимости во многих случаях.
Избегайте дисбаланса сегментов, если документы индексируются с помощью пользовательских идентификаторов или маршрутов.. Elasticsearch использует генераторы случайных идентификаторов и алгоритмы хеширования, чтобы обеспечить равномерное распределение документов по сегментам. При использовании определяемых пользователем идентификаторов или маршрутов идентификаторы или ключи маршрутизации могут быть недостаточно случайными, в результате чего некоторые осколки будут значительно больше других. В этом случае операции чтения/записи на этом шарде будут намного медленнее, чем на других. Мы можем оптимизировать ключи ID/маршрутизации или использовать index.routing_partition_size (доступно в 5.3 и более поздних версиях).
Убедитесь, что осколки равномерно распределены по узлам.. Если узел имеет больше шардов, чем другие узлы, он будет нести большую нагрузку, чем другие узлы, и станет узким местом всей системы.
Настройка производительности индексаДля тяжелых сценариев индексирования, таких как ведение журнала и мониторинг, производительность индексирования является ключевым показателем. Вот несколько предложений:
-
Используйте пакетные запросы.
-
Отправляйте запросы, используя несколько потоков.
-
Увеличьте интервал обновления. Каждый раз, когда происходит событие обновления, Elasticsearch создает новый сегмент Lucene и объединяет его позже. Увеличение интервала обновления уменьшит накладные расходы на создание и слияние. Обратите внимание, что документы доступны для поиска только после обновления.
Связь между производительностью и интервалом обновления
Как видно из приведенного выше графика, с увеличением интервала обновления увеличивается пропускная способность и уменьшается время отклика. Мы можем использовать приведенный ниже запрос, чтобы проверить, сколько сегментов у нас есть и сколько времени потребовалось для обновления и объединения.
Index/_stats?filter_path= indices..refresh,indices..segments,indices.**.merges
Уменьшите количество копий. Для каждого запроса на индексирование Elasticsearch должен записывать документы в основной сегмент и все сегменты-реплики. Очевидно, что слишком большое количество реплик замедлит индексацию, но, с другой стороны, улучшит производительность поиска. Мы обсудим этот вопрос позже в этой статье.
Взаимосвязь между производительностью и количеством реплик
Как видно из приведенного выше графика, по мере увеличения количества реплик снижается пропускная способность и увеличивается время отклика.
Используйте автоматически сгенерированные идентификаторы, когда это возможно. Идентификаторы, автоматически сгенерированные Elasticsearch, гарантированно уникальны и позволяют избежать поиска версий. Если клиенту действительно нужно использовать собственный идентификатор, мы рекомендуем выбрать удобный для Lucene идентификатор, например последовательный идентификатор с нулевым дополнением, UUID-1 или время в наносекундах. Эти идентификаторы имеют согласованный последовательный шаблон и хорошо сжимаются. Напротив, такие идентификаторы, как UUID-4, по своей сути являются случайными, имеют низкое сжатие и замедляют работу Lucene.
Настройка производительности поискаОсновная причина использования Elasticsearch — поддержка поиска данных. Пользователи должны иметь возможность быстро находить нужную им информацию. Эффективность поиска зависит от многих факторов.
По возможности используйте контекст фильтра (Filter) вместо контекста запроса (Query). Предложение запроса используется для ответа «насколько хорошо этот документ соответствует этому предложению», а предложение фильтра используется для ответа «соответствует ли этот документ этому предложению». Elasticsearch нужно только ответить «да» или «нет», без оценок релевантности. должны быть рассчитаны для предложений фильтра, а результаты фильтра могут быть кэшированы. Дополнительные сведения см. в разделе Запросы и фильтрация контекстов.
Сравнение производительности запросов и фильтров
Увеличить интервал обновления. Как мы упоминали в разделе о настройке производительности индекса, Elasticsearch создает новый сегмент при каждом обновлении. Увеличение интервала обновления поможет уменьшить количество сегментов и снизить затраты на ввод-вывод при поиске. Кроме того, после сброса и изменения данных кеш станет недействительным. Увеличение интервала обновления позволяет Elasticsearch более эффективно использовать кеш.
Увеличить количество копий. Elasticsearch может выполнять поиск в основных осколках или репликах. Чем больше реплик, тем больше узлов доступно для поиска.
Взаимосвязь между эффективностью поиска и количеством реплик
Как видно из графика выше, производительность поиска почти линейно зависит от количества реплик. Обратите внимание, что в этом тесте в тестовом кластере достаточно узлов данных, чтобы гарантировать, что каждый сегмент имеет выделенный узел. Если это условие не соблюдается, скорость поиска будет не такой хорошей.
Попробуйте другое количество осколков. «Сколько осколков нужно настроить для индекса?» Это, наверное, самый распространенный вопрос. К сожалению, универсального количества осколков не существует. Все зависит от вашей ситуации.
Слишком мало осколков может привести к невозможности масштабирования поиска. Например, если количество осколков установлено равным 1, все документы в индексе будут храниться в одном осколке. Для каждого поиска в вычислении может участвовать только один узел. Если количество документов в индексе велико, запрос может занять много времени. С другой стороны, создание слишком большого количества осколков индекса также может нанести ущерб производительности, поскольку Elasticsearch необходимо выполнить запрос на всех осколках (если в запросе не указан ключ маршрутизации), а затем получить и объединить все возвращенные результаты.
По нашему опыту, если индекс меньше 1G, количество шардов можно установить равным 1. Для большинства сценариев мы можем оставить количество сегментов по умолчанию равным 5, но если размер сегмента превышает 30 ГБ, мы должны увеличить количество сегментов, чтобы разделить индекс на большее количество сегментов. После создания индекса количество сегментов изменить нельзя, но мы можем создавать новые индексы и мигрировать данные с помощью API переиндексации.
Мы протестировали индекс размером около 150 ГБ со 100 миллионами документов. Мы использовали 100 потоков для отправки поисковых запросов.
Взаимосвязь между эффективностью поиска и количеством сегментов
Как видно из рисунка выше, оптимальное количество осколков равно 11. Скорость поиска сначала увеличивается (время отклика уменьшается), но снижается по мере увеличения количества осколков (увеличивается время отклика).
Обратите внимание, что в этом тесте, как и в тесте количества реплик, каждый сегмент имеет эксклюзивный узел. Если это условие не выполняется, то скорость поиска будет не такой хорошей, как на этом графике.
В этом случае мы рекомендуем вам попробовать меньшее количество осколков, чем оптимальное значение, потому что, если осколков много и каждый осколок имеет эксклюзивный узел данных, то потребуется много узлов.
Кэш запросов узла. Кэш запросов узла кэширует только запросы, используемые в контексте фильтра. В отличие от предложений запроса, предложения фильтра представляют собой вопрос «да» или «нет». Elasticsearch использует механизм установки битов для кэширования результатов фильтрации, чтобы можно было ускорить последующие запросы с использованием того же фильтра. Обратите внимание, что Elasticsearch включает кэширование запросов только для сегментов, содержащих более 10 000 (или 3% от общего количества документов, в зависимости от того, что больше) документов. Дополнительные сведения см. в разделе Кэширование (ссылка в конце).
Мы можем использовать следующий запрос, чтобы проверить, действует ли кеш запросов узла.
GET index_name/_stats?filter_path=indices.**.query_cache
{
"indices": {
"index_name": {
"primaries": {
"query_cache": {
"memory_size_in_bytes": 46004616,
"total_count": 1588886,
"hit_count": 515001,
"miss_count": 1073885,
"cache_size": 630,
"cache_count": 630,
"evictions": 0
}
},
"total": {
"query_cache": {
"memory_size_in_bytes": 46004616,
"total_count": 1588886,
"hit_count": 515001,
"miss_count": 1073885,
"cache_size": 630,
"cache_count": 630,
"evictions": 0
}
}
}
}
}
скопировать код
Разделенный кеш запросов. Если большинство запросов являются агрегированными, следует рассмотреть сегментированный кеш запросов. Сегментированный кеш запросов кэширует агрегированные результаты, чтобы Elasticsearch мог обрабатывать запросы напрямую с минимальными затратами. Есть несколько замечаний:
-
Установите «размер» на 0. Сегментированный кеш запросов кэширует только агрегированные результаты и рекомендации. Он не попадает в кэш, поэтому, если вы установите размер ненулевым, вы не получите выгоды от кэширования.
-
Полезная нагрузка запроса запроса должна быть точно такой же. Сегментированный кеш запросов использует загрузку запросов в качестве ключа кеша, поэтому необходимо следить за тем, чтобы загрузка последующих запросов запросов была точно такой же, как и предыдущая. Поскольку изменения в порядке ключей JSON в полезных данных приведут к изменениям в полезных данных, рекомендуется отсортировать ключи полезных данных, чтобы обеспечить постоянный порядок.
-
Хорошо обрабатывать дату и время. Не используйте такие переменные, как Date.now, непосредственно в запросе. В противном случае тело запроса отличается для каждого запроса, что приводит к тому, что кеш всегда становится недействительным. Мы рекомендуем упорядочивать дату и время в часах или днях для более эффективного использования кэширования.
Мы можем использовать следующий запрос, чтобы проверить, действителен ли сегментированный кеш запросов.
GET index_name/_stats?filter_path=indices.**.request_cache
{
"indices": {
"index_name": {
"primaries": {
"request_cache": {
"memory_size_in_bytes": 0,
"evictions": 0,
"hit_count": 541,
"miss_count": 514098
}
},
"total": {
"request_cache": {
"memory_size_in_bytes": 0,
"evictions": 0,
"hit_count": 982,
"miss_count": 947321
}
}
}
}
}
скопировать код
Получить только необходимые поля. Если документ большой и вам нужно всего несколько полей, используйте Stored_fields для получения необходимых полей, а не всех полей.
Избегайте поиска стоп-слов. Стоп-слова, такие как «a» и «the», могут вызвать всплеск запросов. Допустим, у вас есть миллион документов. Поиск по слову «лиса» может вернуть десятки найденных документов, но поиск по слову «лиса» может вернуть все документы в индексе, поскольку «the» встречается почти во всех документах. Elasticsearch должен подсчитывать и сортировать все совпадения, чтобы такой запрос, как «лиса», замедлял работу всей системы. Вы можете использовать фильтр стоп-слов, чтобы удалить стоп-слова, или использовать оператор «и», чтобы изменить запрос на «И лиса» для получения более точных результатов.
Если некоторые слова часто используются в указателе, но не входят в список стоп-слов по умолчанию, вы можете использовать частоту среза для их динамической обработки.
Сортировать по _doc, если вас не волнует порядок, в котором возвращаются документы. Elasticsearch по умолчанию использует поле «_score» для сортировки по количеству баллов. Если вас не волнует порядок, вы можете использовать «sort»: «_doc», чтобы позволить Elasticsearch возвращать найденные документы в порядке индекса, что может сократить накладные расходы на сортировку.
Избегайте использования скриптового запроса для расчета динамических полей, рекомендуется вычислять и добавлять поле в документ во время индексации.. Например, у нас есть индекс с большим количеством информации о пользователях, и нам нужно запросить всех пользователей, начинающихся с «1234». Вы можете запустить запрос сценария, например "source":"doc['num'].value.startsWith('1234')". Этот запрос очень ресурсоемкий и замедляет работу всей системы. Рассмотрите возможность добавления поля с именем "num_prefix" при индексировании. Затем мы можем запросить «name_prefix»: «1234».
Избегайте запросов с подстановочными знаками.
Запустите тесты производительностиДля каждого изменения необходимо запустить тесты производительности, чтобы убедиться, что изменение применимо. Поскольку Elasticsearch — это служба RESTful, для запуска тестов производительности можно использовать такие инструменты, как Rally, Apache Jmeter и Gatling. Поскольку команде Pronto нужно было выполнить большое количество тестов на каждом типе машины и версии Elasticsearch, а также тесты производительности на многих кластерах Elasticsearch для разных параметров конфигурации Elasticsearch, эти инструменты не соответствовали нашим требованиям.
Команда Pronto создала онлайн-сервис анализа производительности на основе Gatling, чтобы помочь клиентам и нам проводить тесты производительности и регрессионные тесты. Функции, предоставляемые этим сервисом:
-
Легко добавляйте и редактируйте тесты. Пользователям не нужны знания Gatling или Scala для создания тестов на основе входного запроса или структуры документа.
-
Запустите несколько тестов последовательно без вмешательства человека. Сервис может проверять статус и изменять настройки Elasticsearch до/после каждого теста.
-
Помогите пользователям сравнить и проанализировать результаты тестов. Результаты тестирования и статистика кластера во время тестирования сохраняются и могут быть проанализированы с помощью предопределенных визуализаций Kibana.
-
Запускайте тесты из командной строки или веб-интерфейса. Сервис предоставляет Rest API для интеграции с другими системами.
Архитектура выглядит следующим образом:
Архитектура службы тестирования производительности
Пользователи могут просматривать отчеты Gatling для каждого теста и просматривать предопределенные визуализации Kibana для дальнейшего анализа и сравнения, как показано ниже.
СуммироватьВ этой статье кратко описывается дизайн индекса/сегмента/реплики и некоторые другие конфигурации, которые следует учитывать при проектировании кластера Elasticsearch, отвечающего высоким требованиям к производительности загрузки и поиска. тестирование производительности. На сегодняшний день команда Pronto помогла многочисленным клиентам, включая системы управления заказами (OMS) и поисковую оптимизацию (SEO), добиться высоких показателей производительности, внося свой вклад в критически важный бизнес eBay.
Производительность Elasticsearch зависит от многих факторов, включая структуру документа, размер документа, настройку/сопоставление индекса, частоту запросов, размер набора данных и количество попаданий запроса, и это лишь некоторые из них. Рекомендации для одной ситуации могут не обязательно применяться к другой, поэтому важно тщательно тестировать производительность, собирать данные, настраивать конфигурации в зависимости от нагрузки и оптимизировать кластеры в соответствии с требованиями к производительности.
Посмотреть оригинальный текст на английском языке: https://www.ebayinc.com/stories/blogs/tech/elasticsearch-performance-tuning-practice-at-ebay/