200TB+ Elasticsearch Cluster

200TB+ Elasticsearch Cluster

Много хора се сблъскват с Elasticsearch. Но какво се случва, когато искате да го използвате за съхраняване на регистрационни файлове "в особено голям обем"? Да, и безболезнено да преживеете повредата на някой от няколко центъра за данни? Каква архитектура трябва да се направи и на какви клопки ще се натъкнете?

Ние от Odnoklassniki решихме да решим проблема с управлението на регистрационни файлове с помощта на elasticsearch и сега споделяме нашия опит с Habr: както за архитектурата, така и за капаните.

Аз съм Пьотър Зайцев, работя като системен администратор в Odnoklassniki. Преди това е бил и администратор, работил е с Manticore Search, Sphinx search, Elasticsearch. Може би ако се появи друго ... търсене, вероятно ще работя и с него. Също така участвам в редица проекти с отворен код на доброволни начала.

Когато дойдох в Odnoklassniki, безразсъдно казах на интервюто, че мога да работя с Elasticsearch. След като свикнах и изпълних някои прости задачи, ми беше поставена голяма задача да реформирам системата за управление на регистрационни файлове, която съществуваше по това време.

Изисквания

Изискванията към системата бяха формулирани, както следва:

  • Graylog трябваше да се използва като интерфейс. Тъй като компанията вече имаше опит с използването на този продукт, програмистите и тестерите го знаеха, беше познат и удобен за тях.
  • Обем на данни: средно 50-80 хиляди съобщения в секунда, но ако нещо се счупи, тогава трафикът не е ограничен от нищо, може да бъде 2-3 милиона реда в секунда
  • След като обсъдихме с клиенти изискванията за скоростта на обработка на заявките за търсене, разбрахме, че типичен модел за използване на такава система е следният: хората търсят регистрационните файлове на своите приложения за последните два дни и не искат да чакат повече от секунда за резултат за формулирана заявка.
  • Администраторите настояха системата да бъде лесно мащабирана, ако е необходимо, без да се изисква от тях да разбират задълбочено как работи.
  • Така че единствената задача по поддръжката, от която тези системи се нуждаеха периодично, беше смяната на някакъв вид хардуер.
  • Освен това Odnoklassniki има голяма техническа традиция: всяка услуга, която стартираме, трябва да преживее повреда в центъра за данни (внезапна, непланирана и абсолютно по всяко време).

Последното изискване при изпълнението на този проект ни беше дадено с най-голямо кръвопролитие, за което ще говоря по-подробно.

сряда

Работим в четири центъра за данни, докато възлите за данни на Elasticsearch могат да бъдат разположени само в три (поради редица нетехнически причини).

В тези четири центъра за данни има приблизително 18 хиляди различни източника на регистрационни файлове - парчета желязо, контейнери, виртуални машини.

Важна характеристика: клъстерът се стартира в контейнери Подман не на физически машини, а на собствен облачен продукт с един облак. Контейнерите имат гарантирани 2 ядра, подобни на 2.0 Ghz v4, с възможност за рециклиране на останалите ядра, ако са неактивни.

С други думи:

200TB+ Elasticsearch Cluster

Топология

Общият изглед на решението, което първоначално видях, е следният:

  • 3-4 VIP стоят зад A-записа на Graylog домейна, това е адреса, на който се изпращат логовете.
  • всеки VIP е LVS балансьор.
  • След него регистрационните файлове отиват към батерията Graylog, някои от данните отиват във формат GELF, някои във формат syslog.
  • След това всичко това се записва на големи партиди в батерията от координаторите на Elasticsearch.
  • А те от своя страна изпращат заявки за запис и четене към съответните възли за данни.

200TB+ Elasticsearch Cluster

терминология

Може би не всеки разбира терминологията в детайли, така че бих искал да се спра малко на нея.

В Elasticsearch има няколко вида възли – главен, координатор, възел за данни. Има още два типа за различни трансформации на логове и свързване на различни клъстери един с друг, но ние използвахме само изброените.

Майстор
Той проверява всички възли, присъстващи в клъстера, поддържа актуална карта на клъстера и я разпределя между възлите, обработва логиката на събитията и извършва различни видове поддръжка на целия клъстер.

Координатор
Той изпълнява една единствена задача: получава заявки от клиенти за четене или писане и маршрутизира този трафик. В случай, че има заявка за запис, най-вероятно ще попита master в кой шард от съответния индекс да я постави и ще пренасочи заявката по-нататък.

възел за данни
Съхранява данни, извършва заявки за търсене и операции върху разположените върху него фрагменти, пристигащи отвън.

Грейлог
Това е нещо като сливане на Kibana с Logstash в стека ELK. Graylog съчетава както потребителския интерфейс, така и тръбопровода за обработка на регистрационни файлове. Под капака Graylog работи с Kafka и Zookeeper, които осигуряват връзка с Graylog като клъстер. Graylog може да кешира регистрационни файлове (Kafka) в случай, че Elasticsearch е недостъпен и да повтаря неуспешни заявки за четене и запис, да групира и маркира регистрационни файлове според определени правила. Подобно на Logstash, Graylog има функционалността да променя низове, преди да запише в Elasticsearch.

В допълнение, Graylog има вградено откриване на услуги, което позволява, въз основа на един наличен възел Elasticsearch, да получите цялата карта на клъстера и да я филтрирате по конкретен етикет, което прави възможно изпращането на заявки до конкретни контейнери.

Визуално изглежда така:

200TB+ Elasticsearch Cluster

Това е екранна снимка от конкретен случай. Тук изграждаме хистограма въз основа на заявката за търсене и показваме съответните редове.

индекси

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

В диаграмата по-горе това е най-ниското ниво: възли с данни Elasticsearch.

Индексът е голям виртуален обект, съставен от сегменти Elasticsearch. Сам по себе си всеки от шардовете не е нищо повече от индекс на Lucene. И всеки индекс на Lucene от своя страна се състои от един или повече сегменти.

200TB+ Elasticsearch Cluster

Когато проектирахме, решихме, че за да отговорим на изискванията за скорост на четене на голямо количество данни, трябва равномерно да „размажем“ тези данни между възлите на данни.

Това доведе до факта, че броят на сегментите на индекс (с реплики) трябва да бъде строго равен на броя на възлите с данни. Първо, за да осигурим коефициент на репликация от две (т.е. можем да загубим половината от клъстера). И, второ, за да се обработват заявки за четене и запис на поне половината от клъстера.

Първоначално определихме времето за съхранение на 30 дни.

Разпределението на шардовете може да бъде представено графично, както следва:

200TB+ Elasticsearch Cluster

Целият тъмносив правоъгълник е индексът. Левият червен квадрат в него е основният шард, първият в индекса. А синият квадрат е реплика на парче. Те се намират в различни центрове за данни.

Когато добавим друг шард, той отива в третия център за данни. И в крайна сметка получаваме такава структура, която осигурява възможност за загуба на DC без загуба на последователност на данните:

200TB+ Elasticsearch Cluster

Ротация на индекса, т.е. създавайки нов индекс и изтривайки най-стария, го направихме равен на 48 часа (според модела на използване на индекса: най-често се търсят последните 48 часа).

Този интервал на ротация на индекса се дължи на следните причини:

Когато заявка за търсене пристигне в конкретен възел с данни, тогава от гледна точка на производителността е по-изгодно, когато се анкетира един шард, ако размерът му е сравним с размера на бедрото на възела. Това ви позволява да запазите "горещата" част от индекса в купчината и да получите бърз достъп до нея. Когато има много „горещи части“, скоростта на търсене в индекса намалява.

Когато възел започне да изпълнява заявка за търсене на един шард, той разпределя брой нишки, равен на броя на ядрата с хипернишки на физическата машина. Ако заявката за търсене засяга голям брой фрагменти, тогава броят на нишките нараства пропорционално. Това се отразява зле на скоростта на търсене и се отразява негативно на индексирането на нови данни.

За да осигурим необходимото забавяне на търсенето, решихме да използваме SSD. За да обработват заявките бързо, машините, които хостваха тези контейнери, трябваше да имат поне 56 ядра. Числото 56 е избрано като условно достатъчна стойност, която определя броя на нишките, които Elasticsearch ще генерира по време на работа. В Elasitcsearch много параметри на пула от нишки пряко зависят от броя на наличните ядра, което от своя страна пряко влияе върху необходимия брой възли в клъстера според принципа "по-малко ядра - повече възли".

В резултат на това получихме, че средно един шард тежи около 20 гигабайта и има 1 шарда на 360 индекс. Съответно, ако ги редуваме на всеки 48 часа, тогава имаме 15 от тях. Всеки индекс съдържа данни за 2 дни.

Схеми за запис и четене на данни

Нека да видим как се записват данните в тази система.

Да кажем, че пристига някаква заявка от Graylog до координатора. Например искаме да индексираме 2-3 хиляди реда.

Координаторът, след като получи заявка от Graylog, пита господаря: „В заявката за индексиране ние конкретно посочихме индекса, но не беше посочено в кой фрагмент да напишем това.“

Капитанът отговаря: „Запишете тази информация в шард номер 71“, след което тя се изпраща директно до съответния възел с данни, където се намира първичен шард номер 71.

След това регистърът на транзакциите се копира в replica-shard, който вече се намира в друг център за данни.

200TB+ Elasticsearch Cluster

Заявка за търсене пристига от Graylog до координатора. Координаторът го пренасочва по индекс, докато Elasticsearch разпределя заявките между първичен шард и реплика шард според принципа на кръговия кръг.

200TB+ Elasticsearch Cluster

Възлите в размер на 180 реагират неравномерно и докато отговарят, координаторът натрупва информация, която по-бързите възли за данни вече са „изплюли“ в него. След това, когато или цялата информация е пристигнала, или е достигнат таймаут на заявката, той дава всичко директно на клиента.

Цялата тази система средно изпълнява заявки за търсене за последните 48 часа за 300-400ms, с изключение на тези заявки, които са с водещ заместващ знак.

Цветя с Elasticsearch: Настройка на Java

200TB+ Elasticsearch Cluster

За да направим всичко да работи както първоначално искахме, прекарахме много време в отстраняване на грешки в голямо разнообразие от неща в клъстера.

Първата част от откритите проблеми беше свързана с това как Java е предварително конфигурирана в Elasticsearch по подразбиране.

Проблем първи
Виждали сме много голям брой доклади, които имаме на ниво Lucene, когато се изпълняват фонови задания, сливанията на сегменти на Lucene са неуспешни. В същото време в регистрационните файлове беше ясно, че това е грешка OutOfMemoryError. От телеметрията видяхме, че бедрото е свободно и не стана ясно защо тази операция пада.

Оказа се, че сливането на индекса Lucene се случва извън бедрото. А контейнерите са доста силно ограничени по отношение на консумираните ресурси. Само купчината се побираше в тези ресурси (стойността на heap.size беше приблизително равна на RAM), а някои операции извън купчината падаха с грешка при разпределяне на паметта, ако по някаква причина не се вписват в тези ~ 500MB, които остават преди ограничението .

Поправката беше доста тривиална: обемът на наличната RAM за контейнера беше увеличен, след което те забравиха, че изобщо имаме такива проблеми.

Проблем два
4-5 дни след стартирането на клъстера забелязахме, че възлите за данни започват периодично да изпадат от клъстера и да влизат в него след 10-20 секунди.

Когато се изкачиха да го разберат, се оказа, че тази памет в Elasticsearch практически не се контролира по никакъв начин. Когато дадохме повече памет на контейнера, получихме възможността да запълним директни буферни пулове с различна информация и тя беше изчистена едва след като изричният GC беше стартиран от Elasticsearch.

В някои случаи тази операция отне доста дълго време и през това време клъстерът успя да маркира този възел като вече освободен. Този проблем е добре описан. тук.

Решението беше следното: ограничихме способността на Java да използва по-голямата част от паметта извън купчината за тези операции. Ограничихме го до 16 гигабайта (-XX:MaxDirectMemorySize=16g), като гарантирахме, че изричният GC се извиква много по-често и работи много по-бързо, като по този начин престава да дестабилизира клъстера.

Проблем три
Ако мислите, че проблемите с „възлите, напускащи клъстера в най-неочаквания момент“ са приключили, грешите.

Когато конфигурирахме работата с индекси, избрахме mmapfs, за да намаляване на времето за търсене върху пресни фрагменти с висока сегментация. Това беше доста груба грешка, защото при използване на mmapfs файлът се преобразува в RAM и след това работим с преобразувания файл. Поради това се оказва, че когато GC се опитва да спре нишките в приложението, ние отиваме в safepoint за много дълго време и по пътя към него приложението спира да отговаря на запитванията на съветника дали е живо . Съответно главният смята, че възелът вече не присъства в клъстера. След това, след 5-10 секунди, събирачът на боклук работи, възелът оживява, влиза отново в клъстера и започва да инициализира фрагментите. Всичко това силно приличаше на „постановката, която заслужавахме” и не ставаше за нищо сериозно.

За да се отървем от това поведение, първо преминахме към стандартните niofs, а след това, когато мигрирахме от петата версия на Elastic към шестата, опитахме hybridfs, където този проблем не беше възпроизведен. Можете да прочетете повече за видовете съхранение тук.

Проблем четири
След това имаше още един много забавен проблем, който лекувахме рекордно дълго време. Хванахме я 2-3 месеца, защото моделът й беше абсолютно неразбираем.

Понякога нашите координатори отиваха на Full GC, обикновено някъде следобед, и никога не се връщаха оттам. В същото време, когато регистрирахме забавянето на GC, изглеждаше така: всичко върви добре за нас, добре, добре, а след това веднъж - и всичко е рязко лошо.

Първоначално си помислихме, че имаме зъл потребител, който стартира някаква заявка, която изважда координатора от работен режим. Регистрирахме заявки за много дълго време, опитвайки се да разберем какво се случва.

В резултат на това се оказа, че в момента, когато някой потребител стартира огромна заявка и тя удари конкретен координатор на Elasticsearch, някои възли отговарят по-дълго от други.

И докато координаторът чака отговора на всички възли, той натрупва в себе си резултатите, изпратени от възлите, които вече са отговорили. За GC това означава, че нашият модел на използване на бедрата се променя много бързо. И GC, който използвахме, не можа да се справи с тази задача.

Единственото решение, което намерихме за промяна на поведението на клъстера в тази ситуация, е да мигрираме към JDK13 и да използваме колектора за боклук Shenandoah. Това реши проблема, нашите координатори спряха да падат.

Тук свършиха проблемите с Java и започнаха проблемите с честотната лента.

„Бери“ с Elasticsearch: пропускателна способност

200TB+ Elasticsearch Cluster

Проблемите с пропускателната способност означават, че нашият клъстер е стабилен, но в пика на броя на индексираните документи и по време на маневри производителността е недостатъчна.

Първият симптом, който срещнах: по време на някакъв вид „експлозии“ в производството, когато внезапно се генерира много голям брой регистрационни файлове, грешка при индексиране es_rejected_execution често мига в Graylog.

Това се дължи на факта, че thread_pool.write.queue на един възел с данни, докато Elasticsearch не успее да обработи заявката за индексиране и хвърли информацията в фрагмента на диска, по подразбиране може да кешира само 200 заявки. И в Elasticsearch документация много малко се казва за този параметър. Посочени са само ограниченият брой нишки и размерът по подразбиране.

Разбира се, отидохме да изкривим тази стойност и открихме следното: конкретно в нашата настройка до 300 заявки се кешират доста добре, а по-голяма стойност е изпълнена с факта, че отново отлитаме към Full GC.

Освен това, тъй като това са пакети от съобщения, които пристигат в рамките на една заявка, също беше необходимо да се настрои Graylog, така че да не пише често и на малки пакети, а на големи пакети или на всеки 3 секунди, ако пакетът все още не е пълен. В този случай се оказва, че информацията, която записваме в Elasticsearch, става достъпна не след две секунди, а след пет (което ни устройва доста добре), но броят на повторенията, които трябва да се направят, за да се прокара голям пакет от информация намалява.

Това е особено важно в онези моменти, когато нещо някъде се е сринало и яростно докладва за това, за да не получим напълно спам Elastic, а след известно време - Graylog възли, които не работят поради запушени буфери.

Освен това, когато имахме тези експлозии в производството, получихме оплаквания от програмисти и тестери: в момента, когато наистина се нуждаят от тези регистрационни файлове, те им се издават много бавно.

Започнаха да разбират. От една страна, беше ясно, че както заявките за търсене, така и заявките за индексиране се обработват всъщност на едни и същи физически машини и по един или друг начин ще има определени усвоявания.

Но това може да бъде частично заобиколено поради факта, че в шестата версия на Elasticsearch се появи алгоритъм, който ви позволява да разпределяте заявки между съответните възли с данни, а не според произволния кръгов принцип (контейнерът, който индексира и държи първичния фрагмент може да бъде много зает, няма да има начин да се отговори бързо), но да се насочи тази заявка към по-малко натоварен контейнер с replica-shard, който ще отговори значително по-бързо. С други думи, завършихме с use_adaptive_replica_selection: true.

Картината за четене започва да изглежда така:

200TB+ Elasticsearch Cluster

Преходът към този алгоритъм ни позволи значително да подобрим времето за заявка в онези моменти, когато имахме голям поток от регистрационни файлове за писане.

И накрая, основният проблем беше безболезненото премахване на центъра за данни.

Какво искахме от клъстера веднага след загубата на комуникация с един DC:

  • Ако имаме текущия главен в падналия център за данни, тогава той ще бъде преизбран и преместен като роля към друг възел в друг DC.
  • Главният бързо ще изхвърли всички недостъпни възли от клъстера.
  • Въз основа на останалото той ще разбере: в изгубения център за данни имахме такива и такива първични фрагменти, той бързо ще популяризира допълнителни реплики на фрагменти в останалите центрове за данни и ние ще продължим да индексираме данните.
  • В резултат на това честотната лента на клъстера за писане и четене плавно ще се влоши, но като цяло всичко ще работи, макар и бавно, но стабилно.

Както се оказа, ние искахме нещо подобно:

200TB+ Elasticsearch Cluster

И получи следното:

200TB+ Elasticsearch Cluster

Как се случи това?

По време на падането на центъра за данни главният се превърна в тясното място за нас.

Защо?

Факт е, че главният има TaskBatcher, отговорен за разпределянето на определени задачи и събития в клъстера. Всеки изход на възел, всяко повишаване на шард от реплика към основен, всяка задача за създаване на някакъв вид шард някъде - всичко това първо попада в TaskBatcher, където се обработва последователно и в една нишка.

По време на изтеглянето на един център за данни се оказа, че всички възли за данни в оцелелите центрове за данни считат за свое задължение да информират капитана „загубихме такива и такива фрагменти и такива и такива възли за данни“.

В същото време оцелелите възли за данни изпратиха цялата тази информация на текущия главен и се опитаха да изчакат потвърждение, че той я е приел. Те не очакваха това, тъй като господарят получи задачи по-бързо, отколкото имаше време да отговори. Възлите повтаряха заявки по време на изчакване и капитанът по това време дори не се опита да им отговори, а беше напълно погълнат от задачата да сортира заявките по приоритет.

В терминалната форма се оказа, че възлите за данни спамят главния до степен, че той преминава в пълен GC. След това ролята на главния се премести в някой следващ възел, с него се случи абсолютно същото и в резултат на това клъстерът се разпадна напълно.

Направихме измервания и преди версия 6.4.0, където беше коригирано, ни беше достатъчно да изтеглим едновременно само 10 от 360 възела за данни, за да поставим напълно клъстера.

Изглеждаше нещо подобно:

200TB+ Elasticsearch Cluster

След версия 6.4.0, където този странен бъг беше коригиран, възлите за данни спряха да убиват главния. Но това не го направи по-умен. А именно: когато изведем 2, 3 или 10 (всеки брой, различен от един) възли с данни, главният получава някакво първо съобщение, което казва, че възел A е напуснал и се опитва да каже на възел B, възел C, възел D.

И в момента това може да се справи само чрез задаване на таймаут за опити да се каже на някого за нещо, равно на някъде около 20-30 секунди, и по този начин да се контролира скоростта на изтегляне на центъра за данни от клъстера.

По принцип това се вписва в изискванията, които първоначално бяха поставени за крайния продукт в рамките на проекта, но от гледна точка на "чистата наука" това е грешка. Което, между другото, беше успешно коригирано от разработчиците във версия 7.2.

Освен това, когато се появи определен възел с данни, се оказа, че разпространението на информация за неговото излизане е по-важно от това да се каже на целия клъстер, че има такъв и такъв първичен шард (за да се популяризира реплика-шард в друг център за данни в основния и в те могат да пишат информация).

Следователно, когато всичко вече е замряло, освободените възли с данни не се маркират веднага като остарели. Съответно, ние сме принудени да изчакаме, докато всички ping изтекат, преди да бъдат освободени възлите за данни и едва след това нашият клъстер започва да говори за факта, че там, там и там е необходимо да продължим да записваме информация. Можете да прочетете повече за това тук.

В резултат на това операцията по изтегляне на центъра за данни днес ни отнема около 5 минути в час пик. За такъв голям и тромав колос това е доста добър резултат.

В резултат стигнахме до следното решение:

  • Имаме 360 възли за данни със 700 гигабайтови дискове.
  • 60 координатора за маршрутизиране на трафика на същите тези възли за данни.
  • 40 мастера, които ни останаха като наследство от времето на версиите преди 6.4.0 - за да преживеем изтеглянето на центъра за данни, бяхме психически подготвени да загубим няколко машини, за да гарантираме кворум от мастери дори през най-лошият сценарий
  • Всички опити за комбиниране на роли в един контейнер с нас се основаваха на факта, че рано или късно възелът се счупи при натоварване.
  • Целият клъстер използва heap.size, равен на 31 гигабайта: всички опити за намаляване на размера доведоха до факта, че тежките заявки за търсене с водещ заместващ знак или убиха някои възли, или убиха прекъсвач на веригата в самия Elasticsearch.
  • Освен това, за да гарантираме производителност при търсене, ние се опитахме да запазим броя на обектите в клъстера възможно най-малък, за да обработим възможно най-малко събития в тясното място, което получихме в съветника.

Едно последно нещо за мониторинга

За да работи всичко това по предназначение, ние наблюдаваме следното:

  • Всеки възел от данни докладва на нашия облак, че съществува и има такива и такива фрагменти върху него. Когато гасим нещо някъде, клъстерът съобщава след 2-3 секунди, че в център А сме гасили възел 2, 3 и 4 - това означава, че в други центрове за данни не можем да гасим тези възли само с един оставен шард.
  • Познавайки поведението на капитана, ние гледаме много внимателно броя на чакащите задачи. Тъй като дори една окачена задача, ако не е изтекла навреме, теоретично, в някаква извънредна ситуация, може да стане причината, поради която, например, насърчаването на реплика на шард в основния не работи за нас, което ще спре индексирането.
  • Също така разглеждаме много внимателно забавянията на събирача на отпадъци, защото вече имахме големи трудности с това при оптимизирането.
  • Отхвърляне на теми, за да разберете предварително къде е „тясното място“.
  • Е, стандартни показатели като куп, RAM и I/O.

Когато изграждате мониторинг, не забравяйте да вземете предвид характеристиките на Thread Pool в Elasticsearch. Elasticsearch Документация описва настройките и стойностите по подразбиране за търсене, индексиране, но е напълно мълчалив за thread_pool.management.Тези нишки обработват по-специално заявки като _cat/shards и други подобни, които са удобни за използване при писане на мониторинг. Колкото по-голям е клъстерът, толкова повече такива заявки се изпълняват за единица време, а гореспоменатото thread_pool.management не само не е представено в официалната документация, но и е ограничено по подразбиране до 5 нишки, което много бързо се оползотворява, след което мониторингът спира да работи правилно.

В заключение искам да кажа: успяхме! Успяхме да дадем на нашите програмисти и разработчици инструмент, който е в състояние бързо и надеждно да предостави информация за това какво се случва в производството в почти всяка ситуация.

Да, оказа се доста трудно, но въпреки това успяхме да вместим нашия списък с желания в съществуващи продукти, които в същото време не трябваше да бъдат закърпвани и пренаписвани за нас.

200TB+ Elasticsearch Cluster

Източник: www.habr.com

Добавяне на нов коментар