
Wielu napotyka Elasticsearch. Co się jednak dzieje, gdy chcesz go używać do przechowywania logów "w szczególnie dużych ilościach"? Jak można to zrobić, aby bezboleśnie przetrwać awarię jednego z kilku centrów danych? Jak powinna wyglądać architektura i na jakie pułapki można natknąć się?
W Odnoklassnikach postanowiliśmy z pomocą Elasticsearch rozwiązać problem zarządzania logami, a teraz dzielimy się naszym doświadczeniem na Habrze: zarówno na temat architektury, jak i pułapek.
Nazywam się Piotr Zajcew, pracuję jako administrator systemów w Odnoklassnikach. Wcześniej byłem także administratorem, pracowałem z Manticore Search, Sphinx Search, Elasticsearch. Możliwe, że jeśli pojawi się jakieś inne …search, to prawdopodobnie również będę z nim pracować. Uczestniczę również w kilku projektach open source na zasadzie wolontariatu.
Kiedy przyszedłem do Odnoklassnikach, nieroztropnie powiedziałem na rozmowie kwalifikacyjnej, że umiem pracować z Elasticsearch. Po tym jak się oswoiłem i wykonałem kilka prostszych zadań, dostałem dużą odpowiedzialność za reformę istniejącego systemu zarządzania logami.
Wymagania
Wymagania systemowe zostały sformułowane w następujący sposób:
- Jako front-end miał być używany Graylog. Ponieważ firma miała już doświadczenie z tym produktem, programiści i testerzy go znali, był dla nich znajomy i wygodny.
- Objętość danych: w średnio 50-80 tysięcy wiadomości na sekundę, ale jeśli coś się psuje, to ruch nie jest w żaden sposób ograniczony, mogą to być 2-3 miliony wierszy na sekundę.
- Po omówieniu z klientami wymagań dotyczących prędkości przetwarzania zapytań, zrozumieliśmy, że typowy wzorzec użycia takiego systemu jest taki: ludzie szukają logów swojej aplikacji z ostatnich dwóch dni i nie chcą czekać na wynik swojego sformułowanego zapytania dłużej niż sekundę.
- Administratorzy nalegali, aby system łatwo skalował się w razie potrzeby, nie wymagając od nich głębokiego zrozumienia, jak jest zbudowany.
- Aby jedynym zadaniem konserwacyjnym, które te systemy wymagały okresowo, było zmienianie jakiegoś sprzętu.
- Ponadto, w Odnoklassnikach istnieje wspaniała tradycja techniczna: każda usługa, którą uruchamiamy, musi przetrwać awarię centrum danych (niespodziewaną, nieplanowaną i całkowicie w dowolnym czasie).
Ostatnie wymaganie dotyczące realizacji tego projektu kosztowało nas najwięcej trudu, o czym opowiem jeszcze bardziej szczegółowo.
Środowisko
Pracujemy w czterech centrach danych, natomiast węzły danych Elasticsearch mogą znajdować się tylko w trzech (z szeregu przyczyn technicznych).
W tych czterech centrach danych znajduje się około 18 tysięcy różnych źródeł logów — urządzenia, kontenery, maszyny wirtualne.
Ważna cecha: uruchomienie klastra odbywa się w kontenerach nie na fizycznych maszynach, ale na . Kontenery mają przydzielone 2 rdzenie, odpowiadające 2.0Ghz v4 z możliwością wykorzystania pozostałych rdzeni w przypadku ich bezczynności.
Innymi słowy:

Topologia
Ogólny wygląd rozwiązania na początku wydawał mi się następujący:
- 3-4 VIP znajdują się za rekordem A domeny Graylog, to jest adres, na który wysyłane są logi.
- każdy VIP stanowi równoważnik obciążenia LVS.
- Po nim logi trafiają do baterii Graylog, część danych jest w formacie GELF, część w formacie syslog.
- Następnie wszystko to w dużych partiach zapisuje się w baterii koordynatorów Elasticsearch.
- Oni z kolei wysyłają zapytania o zapisywanie i odczytywanie do odpowiednich węzłów danych.

Terminologia
Możliwe, że nie wszyscy dokładnie rozumieją terminologię, dlatego chciałbym się na niej chwilę zatrzymać.
W Elasticsearch istnieje kilka typów węzłów — master, coordinator, data node. Są jeszcze dwa inne typy do różnych przekształceń logów i komunikacji między różnymi klastrami, ale używaliśmy tylko wymienionych.
Master
Pingują wszystkie węzły obecne w klastrze, utrzymują aktualną mapę klastra i rozprzestrzeniają ją między węzłami, obsługują logikę zdarzeń, zajmują się różnymi rzeczami związanymi z administracją klastra.
Koordynator
Wykonuje jedną jedyną zadanie: przyjmuje zapytania od klientów dotyczące odczytu lub zapisu i kieruje ten ruch. W przypadku, gdy zapytanie dotyczy zapisu, najprawdopodobniej zapyta master, do którego shardu odpowiedniego indeksu to zapisać, i przekieruje zapytanie dalej.
Węzeł danych
Przechowuje dane, wykonuje przychodzące zapytania o wyszukiwanie i operacje na umieszczonych na niej shardach.
Graylog
To jest coś w rodzaju połączenia Kibana z Logstash w stosie ELK. Graylog łączy w sobie interfejs użytkownika oraz potok do przetwarzania logów. W Graylog działają Kafka i Zookeeper, które zapewniają spójność Graylog jako klastra. Graylog potrafi buforować logi (Kafka) w przypadku braku dostępu do Elasticsearch i powtarzać nieudane zapytania do odczytu i zapisu, grupować i oznaczać logi według zadanych reguł. Podobnie jak Logstash, Graylog ma funkcjonalność do modyfikacji ciągów przed zapisem do Elasticsearch.
Ponadto w Graylog znajduje się wbudowane odkrywanie usług, które umożliwia na podstawie jednej dostępnej węzła Elasticsearch uzyskanie całej mapy klastra i filtrowanie jej według określonego tagu, co pozwala na kierowanie zapytań do konkretnych kontenerów.
Wizualnie wygląda to mniej więcej tak:

To jest zrzut ekranu z konkretnego instancji. Tutaj na podstawie zapytania wyszukiwania budujemy histogram, wyświetlamy odpowiednie ciągi.
Indeksy
Wracając do architektury systemu, chciałbym bliżej przyjrzeć się temu, jak budowaliśmy model indeksów, aby wszystko działało poprawnie.
Na wcześniej przedstawionym schemacie to najniższy poziom: węzły danych Elasticsearch.
Indeks to duża wirtualna jednostka składająca się z shardów Elasticsearch. Sam każdy z shardów jest niczym innym jak indeksem Lucene. A każdy indeks Lucene składa się z jednego lub więcej segmentów.

Podczas projektowania przewidywaliśmy, że aby spełnić wymagania dotyczące szybkości odczytu przy dużych ilościach danych, musimy równo "rozłożyć" te dane po węzłach danych.
To doprowadziło do tego, że liczba shardów na indeks (z replikami) musi być ściśle równa liczbie węzłów danych. Po pierwsze, aby zapewnić współczynnik replikacji równy dwóm (to znaczy, że możemy stracić połowę klastra). A po drugie, aby przetwarzać zapytania do odczytu i zapisu na co najmniej połowie klastra.
Czas przechowywania określiliśmy początkowo na 30 dni.
Rozkład shardów można graficznie przedstawić w następujący sposób:

Cały ciemnoszary prostokąt to indeks. Lewy czerwony kwadrat w nim to primary shard, pierwszy w indeksie. A błękitny kwadrat to replica shard. Znajdują się w różnych centrach danych.
Kiedy dodajemy kolejny shard, trafia on do trzeciego centrum danych. Ostatecznie uzyskujemy taką strukturę, która zapewnia możliwość utraty DC bez utraty spójności danych:

Rotacja indeksów, czyli tworzenie nowego indeksu i usuwanie najstarszego, została ustawiona na 48 godzin (według wzorca korzystania z indeksu: najczęściej szuka się według ostatnich 48 godzin).
Taki interwał rotacji indeksów związany jest z następującymi przyczynami:
Gdy konkretna data-noda otrzymuje zapytanie wyszukiwawcze, z perspektywy wydajności korzystniej jest, gdy odpytywana jest jeden shard, jeśli jego rozmiar jest zbliżony do rozmiaru hipa nody. Pozwala to utrzymać „gorącą” część indeksu w hipie i szybko do niej sięgać. Gdy liczba „gorących części” wzrasta, to spada prędkość wyszukiwania w indeksie.
Gdy noda zaczyna realizować zapytanie wyszukiwawcze na jednym shardzie, alokuje liczbę wątków równą liczbie rdzeni z hyper-threading fizycznej maszyny. Jeśli zapytanie wyszukiwawcze dotyczy dużej liczby shardów, to liczba wątków rośnie proporcjonalnie. Negatywnie odbija się to na prędkości wyszukiwania i negatywnie wpływa na indeksowanie nowych danych.
Aby zapewnić wymagane opóźnienie wyszukiwania, zdecydowaliśmy się na użycie SSD. W celu szybkiego przetwarzania zapytań maszyny, na których znajdowały się te kontenery, musiały dysponować co najmniej 56 rdzeniami. Liczba 56 została wybrana jako warunkowo wystarczająca wielkość, określająca liczbę wątków, które generuje Elasticsearch podczas pracy. W Elasticsearch wiele parametrów puli wątków bezpośrednio zależy od liczby dostępnych rdzeni, co z kolei wpływa na wymaganą liczbę nod w klastrze według zasady „mniej rdzeni — więcej nodów”.
W rezultacie otrzymaliśmy, że średnio shard waży około 20 gigabajtów, a na 1 indeks przypada 360 shardów. W związku z tym, jeśli rotujemy je co 48 godzin, to mamy ich 15. Każdy indeks przechowuje dane za 2 dni.
Schematy zapisu i odczytu danych
Zastanówmy się, jak w tym systemie zapisywane są dane.
Załóżmy, że z Grayloga przychodzi do koordynatora jakieś zapytanie. Na przykład chcemy zindeksować 2-3 tysiące wierszy.
Koordynator, otrzymawszy zapytanie z Graylog, pyta mastera: „W zapytaniu o indeksowanie mieliśmy konkretnie wskazany indeks, ale nie było podane, do którego shardu to zapisać.”
Master odpowiada: „Zapisz te informacje w shardzie numer 71”, po czym trafia ona bezpośrednio do odpowiedniego data-node, w którym znajduje się primary-shard numer 71.
Następnie dziennik transakcji jest replikowany na replica-shard, który znajduje się już w innym centrum danych.

Z Graylog do koordynatora przychodzi zapytanie wyszukiwania. Koordynator przekierowuje je według indeksu, przy czym Elasticsearch na zasadzie round-robin rozdziela zapytania między primary-shard a replica-shard.

Węzły w liczbie 180 odpowiadają nierównomiernie, a podczas gdy odpowiadają, koordynator gromadzi informacje, które już „wyrzuciły” do niego szybsze data-nody. Następnie, gdy albo wszystkie informacje przyjdą, albo osiągnięty zostanie limit czasowy dla zapytania, przekazuje wszystko bezpośrednio klientowi.
Cały ten system średnio przetwarza zapytania wyszukiwania po ostatnich 48 godzinach w czasie 300-400ms, z wyjątkiem tych zapytań, które mają leading wildcard.
„Kwiatki” z Elasticsearch: konfiguracja Java

Aby wszystko działało tak, jak początkowo chcieliśmy, długo testowaliśmy różnorodne rzeczy w klastrze.
Pierwsza część odkrytych problemów dotyczyła tego, jak w Elasticsearch domyślnie skonfigurowana jest Java.
Problem pierwszy
Obserwowaliśmy bardzo dużą ilość komunikatów o tym, że na poziomie Lucene, kiedy uruchomione są background joby, scalanie segmentów Lucene kończy się błędem. W logach widać było, że jest to błąd OutOfMemoryError. Z telemetrii widzieliśmy, że pamięć heap jest wolna, a nie było jasne, dlaczego ta operacja się nie powiodła.
Okazało się, że scalanie indeksów Lucene odbywa się poza heapem. A kontenery są dość sztywno ograniczone pod względem zużywanych zasobów. W te zasoby upadał tylko heap (wartość heap.size była mniej więcej równa RAM), a niektóre operacje off-heap kończyły się błędem alokacji pamięci, jeśli z jakiegoś powodu nie mieściły się w tych ~500MB, które pozostawały do limitu.
Rozwiązanie było dość trywialne: zwiększyliśmy dostępną dla kontenera ilość RAM, po czym zapomnieliśmy, że w ogóle mieliśmy takie problemy.
Problem drugi
Po około 4-5 dniach od uruchomienia klastra zauważyliśmy, że data-nody zaczynają okresowo wypadać z klastra i wracać do niego po 10-20 sekundach.
Gdy zaczęliśmy się tym zajmować, okazało się, że pamięć off-heap w Elasticsearch nie jest praktycznie w ogóle kontrolowana. Gdy daliśmy kontenerowi więcej pamięci, zyskaliśmy możliwość zapełniania pul buforów bezpośrednich różnymi informacjami, które były usuwane dopiero po uruchomieniu explicit GC ze strony Elasticsearch.
W niektórych przypadkach operacja ta trwała dość długo, a w tym czasie klaster zdążył oznaczyć tę nodę jako już niedostępną. Problem ten został dobrze opisany. .
Rozwiązanie było następujące: ograniczyliśmy Java możliwość używania głównej części pamięci poza heapem na te operacje. Ograniczyliśmy ją do 16 gigabajtów (-XX:MaxDirectMemorySize=16g), co sprawiło, że explicit GC był wywoływany znacznie częściej i działał znacznie szybciej, przestając tym samym destabilizować klaster.
Problem trzeci
Jeśli myślisz, że problemy z „nodami, które opuszczają klaster w najmniej oczekiwanym momencie” się na tym kończą, mylisz się.
Gdy konfigurowaliśmy pracę z indeksami, zdecydowaliśmy się na mmapfs, aby w świeżych shardach z dużą segmentacją. Był to dość poważny błąd, ponieważ przy użyciu mmapfs plik jest mapowany do pamięci operacyjnej, a następnie pracujemy już z tym mapowanym plikiem. W związku z tym, gdy garbage collector starał się zatrzymać wątki w aplikacji, bardzo długo dochodziliśmy do safepoint, a po drodze aplikacja przestawała odpowiadać na zapytania mastera, czy żyje. W związku z tym master uznaje, że nody już nie ma w klastrze. Po około 5-10 sekundach garbage collector w końcu działa, node ożywa, ponownie wchodzi do klastra i rozpoczyna inicjalizację shardów. Całość mocno przypominała „produkcję, na którą zasłużyliśmy” i nie nadawała się do niczego poważnego.
Aby pozbyć się takiego zachowania, najpierw przeszliśmy na standardowy niofs, a później, kiedy z piątych wersji Elastic przeszliśmy na szóstą, wypróbowaliśmy hybridfs, w którym problem ten się nie pojawiał. Więcej o typach storage można poczytać. .
Problem czwarty
Później była jeszcze bardzo zajmująca sprawa, którą leczenie zajęło nam rekordowo dużo czasu. Łapaliśmy ją przez 2-3 miesiące, ponieważ wzorzec był zupełnie niejasny.
Czasami nasi koordynatorzy wchodzili w Full GC, zazwyczaj gdzieś po południu, i już się nie wracali. Przy logowaniu opóźnień GC wyglądało to tak: wszystko szło dobrze, dobrze, dobrze, a potem nagle — wszystko stawało się nagle złe.
Początkowo myśleliśmy, że mamy złego użytkownika, który uruchamia jakąś kwerendę, która wybija koordynator z trybu pracy. Bardzo długo logowaliśmy zapytania, próbując ustalić, co się dzieje.
Ostatecznie okazało się, że w momencie, gdy jakiś użytkownik uruchamia ogromne zapytanie, a ono trafia do konkretnego koordynatora Elasticsearch, niektóre węzły odpowiadają dłużej niż inne.
A czas, podczas gdy koordynator czeka na odpowiedź wszystkich węzłów, gromadzi w sobie wyniki przysłane przez już odpowiednią węzły. Dla GC oznacza to, że bardzo szybko zmienia się wzorzec użycia pamięci heap. I ten GC, którego używaliśmy, nie radził sobie z tym zadaniem.
Jedynym rozwiązaniem, które znaleźliśmy, aby zmienić zachowanie klastra w takiej sytuacji, była migracja na JDK13 i użycie zbieracza śmieci Shenandoah. To rozwiązało problem, koordynatory przestały padać.
Na tym problemy z Java się skończyły, a zaczęły problemy z przepustowością.
«Owoce» z Elasticsearch: przepustowość

Problemy z przepustowością oznaczają, że nasz klaster działa stabilnie, ale w szczytowych momentach liczby indeksowanych dokumentów i podczas manewrów wydajność jest niewystarczająca.
Pierwszym zauważonym objawem: przy jakichś «wybuchach» na produkcji, gdy nagle generuje się bardzo duża ilość logów, w Graylog zaczyna często pojawiać się błąd indeksacji es_rejected_execution.
Działo się tak, ponieważ thread_pool.write.queue na jednym węźle danych, zanim Elasticsearch zdąży przetworzyć zapytanie indeksacyjne i wrzucić informacje do shardu na dysku, domyślnie może buforować tylko 200 zapytań. I w o tym parametrze mówi się bardzo mało. Podano tylko maksymalną liczbę wątków i domyślny rozmiar.
Oczywiście przystąpiliśmy do modyfikacji tej wartości i odkryliśmy następujące: konkretnie w naszym ustawieniu można całkiem dobrze buforować do 300 zapytań, a większa wartość wiąże się z tym, że znowu wkrótce wpadamy w Full GC.
Ponadto, ponieważ są to partie wiadomości, które przychodzą w ramach jednego zapytania, należało również dostosować Graylog, aby zapisywał nieczęsto i małymi partiami, ale ogromnymi partiami lub co 3 sekundy, jeśli partia wciąż nie jest pełna. W takim przypadku informacja, którą zapisujemy w Elasticsearch, staje się dostępna nie po dwóch sekundach, a po pięciu (co nam w zupełności odpowiada), ale zmniejsza się liczba retrayów, które musimy wykonać, aby przesłać dużą paczkę informacji.
Jest to szczególnie ważne w momentach, gdy coś gdzieś się zepsuło i intensywnie o tym informuje, aby nie otrzymać całkowicie spamowanego Elastic, a po pewnym czasie — nie działających nodów Graylog z powodu zatkanych buforów.
Ponadto, gdy miały miejsce te eksplozje na produkcji, otrzymywaliśmy skargi od programistów i testerów: w momencie, gdy bardzo potrzebowali tych logów, były one im udostępniane bardzo wolno.
Zaczęliśmy badać. Z jednej strony było jasne, że zarówno zapytania wyszukiwania, jak i zapytania o indeksowanie działają w zasadzie na tych samych fizycznych maszynach, i w ten czy inny sposób pewne spadki będą miały miejsce.
Jednak można to było częściowo obejść dzięki temu, że w szóstych wersjach Elasticsearch pojawił się algorytm, który pozwalał na rozdzielanie zapytań między odpowiednie węzły danych nie w przypadkowy, losowy sposób (kontener, który zajmuje się indeksowaniem i utrzymuje primary-shard, może być bardzo zajęty, nie będzie możliwości odpowiedzi szybko), ale skierować to zapytanie do mniej obciążonego kontenera z replica-shard, który odpowie znacznie szybciej. Innymi słowy, doszliśmy do use_adaptive_replica_selection: true.
Obraz odczytu zaczyna wyglądać następująco:

Przejście na ten algorytm pozwoliło znacznie poprawić czas zapytań w momentach, gdy mieliśmy duży ruch logów do zapisania.
W końcu głównym problemem było bezbolesne wycofanie centrum danych.
Czego chcieliśmy od klastra zaraz po utracie łączności z jednym DC:
- Jeśli w odłączonym centrum danych znajduje się aktualny master, zostanie on wybrany ponownie i przeniesie się jako rola na inny węzeł w innym DC.
- Master szybko usunie z klastra wszystkie niedostępne węzły.
- Na podstawie pozostałych danych zrozumie, że w zaginionym centrum danych mieliśmy takie primary shard'y, szybko promując komplementarne replica shard'y w pozostałych centrach danych, co umożliwi nam kontynuację indeksacji danych.
- W wyniku tego nasza przepustowość klastra do zapisu i odczytu będzie stopniowo się degradować, jednak w ogólnym rozrachunku wszystko będzie działać, choć wolno, to stabilnie.
Jak się okazało, chcieliśmy czegoś takiego:

A otrzymaliśmy to:

Jak do tego doszło?
W momencie awarii centrum danych naszym wąskim gardłem stał się master.
Dlaczego?
Chodzi o to, że w masterze znajduje się TaskBatcher, odpowiedzialny za rozprzestrzenianie określonych zadań i zdarzeń w klastrze. Jakiekolwiek wyjście węzła, jakiekolwiek promowanie shard'a z replica do primary, jakiekolwiek zadanie dotyczące stworzenia shard'a — wszystko to trafia najpierw do TaskBatcher'a, gdzie jest przetwarzane sekwencyjnie w jednym wątku.
W momencie wyłączenia jednego centrum danych wszystkie węzły danych w pozostałych centrach uznawały za swój obowiązek informowanie master'a "straciliśmy takie shard'y i takie węzły danych."
Przy tym pozostałe węzły danych przesyłały tę informację aktualnemu master'owi i próbowały czekać na potwierdzenie, że ją przyjął. Nie doczekały się, ponieważ master otrzymywał zadania szybciej, niż zdążył odpowiadać. Węzły powtarzały zapytania po upływie czasu, a master w tym czasie przestał próbować na nie odpowiadać, będąc całkowicie pochłonięty sortingiem zapytań według priorytetów.
W skrajnych przypadkach wyglądało to tak, że węzły danych spamowały master'a do tego stopnia, że przechodził on w full GC. Po tym rola master'a przechodziła na jakiś następny węzeł, z którym działo się dokładnie to samo, i ostatecznie klaster całkowicie się rozpadał.
Przeprowadziliśmy pomiary, i do wersji 6.4.0, gdzie to naprawiono, wystarczyło, aby wyłączyć jednocześnie tylko 10 węzłów danych z 360, aby całkowicie zawalić klaster.
Wyglądało to mniej więcej tak:

Po wersji 6.4.0, w której naprawiono ten problem, węzły danych przestały zabijać master'a. Ale przez to nie stał się 'mądrzejszy'. A dokładniej: kiedy wyłączamy 2, 3 lub 10 (dowolną liczbę poza jedną) węzłów danych, master otrzymuje jakieś pierwsze powiadomienie, które mówi, że węzeł A się wyłączył, i próbuje o tym opowiedzieć węzłowi B, węzłowi C, węzłowi D.
Na chwilę obecną można temu zaradzić tylko poprzez ustawienie limitu czasowego na próby komunikacji z kimś, wynoszącego około 20-30 sekund, co pozwala zarządzać prędkością wyjścia centrum danych z klastra.
Zasadniczo mieści się to w wymaganiach, które pierwotnie zostały postawione wobec końcowego produktu w projekcie, ale z punktu widzenia 'czystej nauki' to błąd. Tak czy inaczej, został on skutecznie naprawiony przez programistów w wersji 7.2.
Co więcej, kiedy pewien węzeł danych wychodził, okazywało się, że rozprzestrzenienie informacji o jego wyjściu jest ważniejsze niż powiadomienie całego klastra, że znajdowały się na nim takie a takie primary-shardy (aby promować replica-shard w innym centrum danych do primary, co umożliwiało zapisywanie informacji).
Dlatego gdy wszystko już 'przygasło', wyszedłe węzły danych nie są natychmiast oznaczane jako stale. W związku z tym musimy czekać, aż wszystkie pingi do wyszłych węzłów danych dojdą do timeoutu, a dopiero potem nasz klaster zaczyna informować o tym, gdzie należy kontynuować zapis informacji. Więcej szczegółów można przeczytać .
W rezultacie operacja wyjścia centrum danych zajmuje nam dziś około 5 minut w godzinach szczytu. Dla tak dużej i nieporęcznej machiny, jest to całkiem dobry wynik.
Ostatecznie doszliśmy do następującego rozwiązania:
- Mamy 360 węzłów danych z dyskami o pojemności 700 gigabajtów.
- 60 koordynatorów do routingu ruchu pomiędzy tymi właśnie węzłami danych.
- 40 masterów, które wciąż posiadamy jako pewnego rodzaju spuściznę z czasów przed wersją 6.4.0 — aby przetrwać wyjście centrum danych, byliśmy mentalnie przygotowani na utratę kilku maszyn, aby zagwarantować, że nawet w najgorszym scenariuszu będziemy mieli quorum masterów.
- Jakiekolwiek próby łączenia ról na jednym kontenerze kończyły się tym, że prędzej czy później węzeł psuł się pod obciążeniem.
- W całym klastrze używa się heap.size równemu 31 gigabajtów: wszelkie próby zmniejszenia rozmiaru prowadziły do tego, że przy ciężkich zapytaniach wyszukiwania z prowadzącym wildcard albo psuły się jakieś węzły, albo wyłączał się circuit breaker w samym Elasticsearch.
- Ponadto, aby zapewnić wydajność wyszukiwania, staraliśmy się utrzymywać liczbę obiektów w klastrze na minimalnym możliwym poziomie, aby przetwarzać jak najmniej zdarzeń w najszlachetniejszym miejscu, które udało nam się uzyskać w masterze.
Na koniec o monitorowaniu.
Aby to wszystko działało tak, jak zamierzono, monitorujemy następujące rzeczy:
- Każdy węzeł w datacenter zgłasza się do naszej chmury, informując, że jest obecny i ma takie a takie shard'y. Kiedy gdzieś coś wyłączamy, klaster po 2-3 sekundach informuje nas, że w centrum A wyłączyliśmy węzły 2, 3 i 4 — oznacza to, że w innych datacentrach nie możemy wyłączać tych węzłów, na których pozostały shard'y w pojedynczym egzemplarzu.
- Znając charakter zachowania mastera, bardzo uważnie obserwujemy liczbę zadań oczekujących. Ponieważ nawet jedno wiszące zadanie, jeśli nie timeoutuje się na czas, teoretycznie w jakiejś krytycznej sytuacji może stać się powodem, dla którego na przykład nie powiedzie się promocja shard'a replica na primary, przez co przestanie działać indeksacja.
- Również bardzo dokładnie zwracamy uwagę na opóźnienia garbage collector'a, ponieważ mieliśmy już z tym poważne problemy przy optymalizacji.
- Odrzucenia w wątkach, aby wcześniej wiedzieć, gdzie znajduje się "wąskie gardło".
- No i standardowe metryki, takie jak heap, RAM i I/O.
Przy budowaniu monitoringu koniecznie trzeba uwzględnić cechy Thread Pool w Elasticsearch. opisem możliwości konfiguracji i wartości domyślnych dla wyszukiwania, indeksacji, ale całkowicie milczy na temat thread_pool.management. Te wątki obsługują między innymi zapytania typu _cat/shards oraz inne podobne, które są wygodne do wykorzystania przy pisaniu monitoringu. Im większy klaster, tym więcej takich zapytań wykonywanych jest w jednostce czasu, a wspomniany thread_pool.management jest nie tylko nieobecny w oficjalnej dokumentacji, ale również domyślnie limitowany do 5 wątków, co bardzo szybko się wyczerpuje, po czym monitoring przestaje działać poprawnie.
Co chciałbym powiedzieć na koniec: udało nam się! Udało nam się dać naszym programistom i deweloperom narzędzie, które praktycznie w każdej sytuacji potrafi szybko i wiarygodnie dostarczyć informacje o tym, co dzieje się na produkcji.
Tak, to było dość skomplikowane, ale mimo to udało się nasze oczekiwania wkomponować w istniejące już produkty, które przy tym nie musieliśmy łatać ani przepisywać pod siebie.

Źródło: habr.com
