Масштабируем Elasticsearch на примере кластера с индексами в несколько терабайт

Низкая скорость поисковых запросов


Работая над поисковым движком по социальной информации (ark.com), мы остановили свой выбор на Elasticsearch, так как по отзывам он был очень легок в настройке и использовании, имел отличные поисковые возможности и, в целом, выглядел как манна небесная. Так оно и было до тех пор, пока наш индекс не вырос до более-менее приличных размером ~ 1 миллиарда документов, размер с учетом реплик уже перевалил за 1,5 ТБ.

Даже банальный Term query мог занять десятки секунд. Документации по ES не так много, как хотелось бы, а гуглинг данного вопроса выдавал результаты 2х-летней давности по совсем не актуальным версиям нашего поискового движка (мы работаем с 0.90.13 — что тоже не достаточно старая вещь, но мы не можем позволить себе опустить весь кластер, обновить его, и запустить заново на текущий момент — только роллинг рестарты).

Низкая скорость индексации



Вторая проблема — мы индексируем больше документов в секунду (порядка 100к), чем Elasticsearch может обрабатывать. Тайм-ауты, огромная нагрузка на Write IO, очереди из процессов в 400 единиц. Все выглядит очень страшно, когда смотришь на это в Marvel.

Как решать эти проблемы — под катом

Масштабируем кластер Elasticsearch



Исходная ситуация:

  • 5 data nodes, http enabled:
    • 100 GB RAM
    • 16 cores
    • 4 TB HDD (7200 RPM, seagate)

  • Индексы:
    • от 500 до 1 млрд документов, всего 5 штук
    • количество primary шардов от 50 до 400 (здесь мы тестировали разные стратегии индексирования — эта настройка очень важна)
    • реплики — от 2 до 5
    • размер индекса до 1,5 терабайт



Увеличиваем скорость индексирования в Elasticsearch

Эта проблема оказалось не такой сложной и информации в интернете по ней чуть больше.

Чеклист, который нужно проверить:
  • refresh_interval — как часто обновляются данные для поиска, чем чаще, тем больше Write IO вам требуется
  • index.translog.flush_threshold_ops — через сколько операций скидывать данные на диск
  • index.translog.flush_threshold_size — сколько данных должны быть добавлены в индекс перед скидыванием на диск


Подробная документация здесь: www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-update-settings.html

В первую очередь мы увеличили refresh_interval до 30 секунд, и фактически увеличили пропускную способность практически до 5000 документов в секунду. Позже поставили flush_threshold_ops в 5000 операций, а размер до 500 мб. Если хотите, то можно поиграться с количеством реплик, шардов и так далее, но это не будет давать настолько большой разницы. Так же обратите внимание на threadpool, если вам необходимо увеличить количество параллельных запросов к базе, хотя чаще всего этого не требуется.

Увеличиваем скорость запросов в Elasticsearch

Теперь переходим к сложной части. Зная размер нашего индекса и постоянные потребности в перезагрузке кластера (обновления версий, мейнтенанс машин), а также принимая во внимание посты вроде этого: gibrown.wordpress.com/2014/02/06/scaling-elasticsearch-part-2-indexing мы решили, что размер шарда в нашем индексе не будет превышать 1-2 ГБ. С учетом RF3, наш индекс (мы рассчитываем на 1,5 млрд документов), учитывая что 0,5 млрд наших документов занимают порядка 300 ГБ без учета реплик, мы создали в индексе 400 шардов и посчитали что все будет хорошо — скорость ребута будет достаточно высока: нам не нужно будет читать блоки данных по 50-60 ГБ, а также реплицировать их, блокируя таким образом восстановление маленьких шардов, да и скорость поиска по маленьким шардам выше.

По началу, количество документов в индексе было небольшим (100-200 млн) и скорость запроса составляла всего 100-200 мс. Но как только практически все шарды были заполнены хотя бы небольшим количеством документов, мы начали значительно терять в производительности запросов. Комбинируя все это с высокой нагрузкой на IO из-за постоянной индексации, мы могли и вообще не выполнить его.

В данном случае мы совершили 2 ошибки:

1. Создали очень много шардов (идеальная ситуация 1 ядро — 1 шард)
2. Наши дата ноды были и нодами-балансерами с включенным http — сериализация и десериализация данных занимает достаточно много времени

Поэтому мы начали экспериментировать.

Добавялем ноды-балансировщики в Elaticsearch

Первым и очевидным шагом для нас было добавлением, так называемых, balancer nodes в Elasticsearch. Они могут производить агрегированние результатов запросов по другим шардам, у них никогда не будет перегружен IO, так как они не выполняют чтения и записи на диск, и мы разгрузим наши data nodes.

Для деплоя мы используем chef и соответствующий elasticsearch cookbook, поэтому создав всего пару дополнительных ролей, со следующими настройками:

name "elasticsearch-balancer"
description "Installs and launches elasticsearch"

default_attributes(
	"elasticsearch" => {
		"node" => {
			"master" => false,
			"data" => false
		}
	}
)

run_list("services::elasticsearch")


Мы благополучно запустили 4 балансировщика. Картина немного улучшилась — мы больше не наблюдали перегруженных нод с дымящимися жесткими дисками, но скорость запросов была все еще низка.

Увеличиваем количество data nodes в Elasticsearch

Теперь мы вспомнили, что количество шардов, которое было у нас (400) никоим образом не сказывается на улучшении производительности, а лишь усугубляет ее, так как слишком больше количество шардов находится на 1 машине. Проведя простые вычисления мы получаем, что 5 машин адекватно поддержат только 80 шардов. Учитывая количество реплик, то их у нас вообще 1200.

Так как наш общий парк машин (80 нод) позволяет добавление достаточно большого количества нод и основная проблема в них — это размер HDD (всего 128гб), то мы решили добавить сразу порядка 15 машин. Это позволит работать с еще 240 шардами более эффективно.

Помимо этого мы наткнулись на несколько любопытных настроек:

* index.store.type — по умолчанию ставится в niofs, а по бенчмаркам производительность ниже чем у mmapfs — мы переключили его на mmapfs (дефолтный стор в 1.x)
* indices.memory.index_buffer_size — увеличили до 30%, а количество RAM под Java Heap наоборот уменьшили до 30 ГБ (было 50%), так как с mmapfs нам нужно намного больше оперативки для кеша операционной системы

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

curl -XPUT localhost:9200/_cluster/settings -d '{
    "transient" : {
        "cluster.routing.allocation.disk.threshold_enabled" : true
    }
}'


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

Что еще следует учесть при масштабировании кластера

При роллинг рестарте кластера, обязательно выключайте возможность переноса шардов: cluster.routing.allocation.enable = none, в старых версиях чуть другая настройка.

Если возникли вопросы во время прочтения — буду рад обсудить.
Share post

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 14

    +1
    а количество RAM под Java Heap наоборот уменьшили до 30 ГБ

    А вас при таком хипе GC-паузы не мучают?
      0
      Где-то видел график (от разработчиков Кассандры, кажется) — после 8ГБ задержки растут экспоненциально, в связи с этим общая производительность падает.
        0


        Примерно так это все выглядит в разрезе ноды, так что нет — не мучает
        0
        Несколько вопросов:
        1. Что у вас используется для мониторинга и управления кластером?
        2. Сколько суммарно используется оперативки на мастер-нодах без учёта реплик?
        3. Какая у вас ширина индекса?
        4. Как долго вставляется документ?
        Спасибо за ответы!
          0
          Для 1, мне нравятся плагины ES Head и Paramedic.
            0
            1. www.elasticsearch.org/overview/marvel/
            2. Всего 18 мастер нод, на каждой ~ 100 гб RAM. 30% -> Java Heap (для всяких кешей фильтров, сортировок и тп), 70% -> OS, RAM забивается под 90%, из-за того что при использовании mmapfs данные с диска кешируются именно туда, за счет этого сильно растет производительность
            3. уточните вопрос
            4. 10к документов балком ~ 90 мс. Но это все равно что пальцем в небо — все зависит от размера документа, анализаторов и тп вещей.
              0
              3. Помимо индексирования текста документа используются дополнительные индексированные атрибуты: дата создания, категория, код, владелец/автор, etc?
              4. Да, согласен про палец :) Задал вопрос, смотря на тему со своей колокольни. Среднюю температуру по больнице понял, спасибо!
                0
                3. Маппинг, который мы используем занимает порядка 300 строчек, типы разнятся, и включают в себя nested объекты, в них есть просто термины, даты, комбинации всего перечисленного.
            0
            в эластике есть такая штука как роутинг, думаю, его стоит посмотреть.

            blog.qbox.io/launching-and-scaling-elasticsearch
              +1
              Роутинг — отличная вещь, когда есть хотя бы 1 признак, по которому можно из-вне создать уникальный ключ для группы документов. К примеру у вас есть база пользователей и логично что все, что относится к этому пользователю должно быть на 1 шарде — производительность растет экспоненциально, ведь мы не опрашиваем 400 шардов как в нашем случае, а всего 1.

              При всех плюсах рутинга в нашем случае очень сложно выделить какой-то один признак (много емейлов, много телефонов, много социальных сеток — а профиль 1)
              0
              а что представляют из себя ваши документы? если исходить из «поисковый движок по социальной информации», то это например профиль в твиттере, отдельный твит или всё вместе?
                0
                Сейчас мы обрабатываем ~ 50 соц сетей. Профиль — это агрегированные данные по конкретному человеку исходя из наших алгоритмов.
                0
                Честно говоря, я никогда не работал с ElasticSearch, поэтому не знаю насколько мои вопросы корректны, но всё же есть пару вопросов:
                1) у вас шарды дифференцированы по типу индекса? Например: эти 20 шардов для FB, это 30 для Twitter и т.д.? Мне кажется, что если их так разделить, то можно настроить роутинг внутри группы шардов.
                2) может вообще стоить разделить индексацию из разных типов источников данных по разным кластерам ElasticSearch, тогда вы во-первых сможете делать параллельные запросы по одному и тому же юзеру, но по разным источникам данных, а во вторых роутинг внутри каждого кластера сократит время запроса. Тогда получится, что суммарное время выполнения запроса будет не больше, чем самый долгий запрос по одному источнику (но и он будет не такой уж и долгий за счет того, что путь к шарде будет известен заранее)
                  0
                  1. В эластике схема несколько другая. Есть индекс -> фиксированное количество шардов -> далее либо автоматом распределяется эластиком, либо опять же «автоматом», но с использованием routing key. У нас проблема в том, что нет 10 профилей для каждой соц сети, а есть 1 большой профиль со всеми сетями. Все «условно-уникальные» идентификаторы — это массивы таких идентификаторов. Поэтому для рутинга так ничего и не было придумано. Зато можно кешировать выборки по данным соц сетям внутри nested документов, что в свою очередь тоже повышает скорость индексирования

                  2. Много разных кластеров или 1 — это не принципиально. По сути в эластике индекс, шард, сегмент и так далее — это самостоятельные Lucene индексы.

                Only users with full accounts can post comments. Log in, please.