Cześć, Habr! Ostatnie kilka miesięcy spędziliśmy w bardzo interesującej sytuacji i chciałbym podzielić się naszą historią skalowania infrastruktury. W tym czasie SberMarket zwiększył liczbę zamówień czterokrotnie i uruchomił serwis w 17 nowych miastach. Eksplozja popytu na dostawę produktów wymagała od nas skalowania infrastruktury. O najciekawszych i najbardziej użytecznych wnioskach przeczytasz poniżej.

Nazywam się Dima Bobylev, jestem dyrektorem technicznym SberMarketu. Ponieważ to pierwszy post w naszym blogu, powiem kilka słów o sobie i o naszej firmie. Zeszłej jesieni brałem udział w konkursie młodych liderów Runetu. Na potrzeby konkursu o tym, jak w SberMarkecie postrzegamy wewnętrzną kulturę i podejście do rozwoju serwisu. I choć nie udało mi się wygrać w konkursie, to sformułowałem dla siebie podstawowe zasady rozwoju ekosystemu IT.
Zarządzając zespołem, ważne jest zrozumienie i znalezienie równowagi pomiędzy potrzebami biznesowymi a wymaganiami każdego pojedynczego programisty. Obecnie SberMarket rośnie 13 razy rok do roku, co wpływa na produkt, wymagając ciągłego zwiększania objętości i tempa pracy nad rozwojem. Mimo to, zapewniamy programistom wystarczająco dużo czasu na wstępną analizę i jakościowe napisanie kodu. Uformowane podejście pomaga nie tylko w tworzeniu działającego produktu, ale także w jego dalszym skalowaniu i rozwoju. W rezultacie takiego wzrostu SberMarket stał się już liderem wśród serwisów dostaw produktów: codziennie dostarczamy około 18 tysięcy zamówień dziennie, chociaż jeszcze na początku lutego było ich około 3500.

Pewnego dnia klient poprosił kuriera SberMarketu o dostawę produktów w sposób bezkontaktowy — prosto na balkon.
Przejdźmy do konkretów. Ostatnie kilka miesięcy intensywnie zajmowaliśmy się skalowaniem infrastruktury naszej firmy. Ta potrzeba była spowodowana czynnikami zewnętrznymi i wewnętrznymi. Równocześnie z rozszerzeniem bazy klientów liczba podłączonych sklepów wzrosła z 90 na początku roku do ponad 200 w połowie maja. Oczywiście przygotowaliśmy się, zarezerwowaliśmy główną infrastrukturę, a także liczyliśmy na możliwość poziomego i pionowego skalowania wszystkich maszyn wirtualnych umieszczonych w chmurze Yandex. Jednak praktyka pokazała: „Wszystko, co może pójść źle, pójdzie źle”. Dziś chciałbym podzielić się najciekawszymi sytuacjami, które miały miejsce w ciągu tych tygodni. Mam nadzieję, że nasze doświadczenie okaże się użyteczne dla Was.
Slave w pełnej gotowości bojowej
Już przed rozpoczęciem pandemii napotkaliśmy wzrost liczby zapytań do naszych serwerów backendowych. Trend zamawiania produktów z dostawą do domu zyskiwał na sile, a po wprowadzeniu pierwszych środków samoizolacji w związku z COVID-19, obciążenie dramatycznie rosło w ciągu całego dnia. Powstała potrzeba szybkiego odciążenia serwerów głównych bazy danych (master) i przeniesienia części zapytań dotyczących odczytu na serwery replikacyjne (slave).
Z wyprzedzeniem przygotowaliśmy się do tego kroku, a do takiego manewru uruchomiono już 2 serwery slave. Na nich głównie pracowały zadania wsadowe związane z generowaniem informacji do wymiany danych z partnerami. Procesy te generowały dodatkowe obciążenie i słusznie zostały wyłączone „na boku” parę miesięcy wcześniej.
Ponieważ na Slave zachodziła replikacja, trzymaliśmy się koncepcji, że aplikacje mogą z nimi pracować tylko w trybie tylko do odczytu. Plan odzyskiwania po awarii zakładał, że w przypadku katastrofy będziemy mogli po prostu zamontować Slave w miejscu Master i przekierować wszystkie zapytania do zapisu i odczytu na Slave. Jednak chcieliśmy również wykorzystać replikacje do potrzeb działu analityki, dlatego serwery nie zostały całkowicie przekształcone w tryb tylko do odczytu, a na każdym hoście znajdował się swój zestaw użytkowników, z których niektórzy mieli uprawnienia do zapisu dla zachowania pośrednich wyników obliczeń.
Do pewnego poziomu obciążenia wystarczał nam master zarówno do zapisu, jak i odczytu przy przetwarzaniu zapytań http. W połowie marca, kiedy Sbermarket zdecydował się całkowicie przejść na pracę zdalną, zaczęliśmy doświadczać znaczącego wzrostu RPS. Coraz więcej naszych klientów przechodziło na samoizolację lub pracę z domu, co wpłynęło na wskaźniki obciążenia.
Wydajności „mastera” przestało wystarczać, dlatego zaczęliśmy przenosić część najcięższych zapytań do odczytu na replikę. Aby przejrzysto kierować zapytania do zapisu do mastera, a odczytu — do slave’a, użyliśmy rubinowego gema „”. Stworzyliśmy specjalnego użytkownika z postfiksem _readonly bez praw do zapisu. Jednak z powodu błędu w konfiguracji jednego z hostów część zapytań do zapisu trafiła na serwer slave w imieniu użytkownika, który miał odpowiednie uprawnienia.
Problem nie ujawnił się od razu, ponieważ zwiększone obciążenie spowodowało, że opóźnienie replik zwiększyło się. Niekonsystencja danych ujawniła się rano, kiedy po nocnych importach replikacje nie „dogoniły” mastera. Zrzuciliśmy to na wysokie obciążenie samego serwisu i import związany z uruchomieniem nowych sklepów. Jednak przekazywanie danych z kilkugodzinnym opóźnieniem było niedopuszczalne, więc przełączyliśmy procesy na drugiego analitycznego slave’a, ponieważ miał on bwiększąardziej zasobne i nie był obciążony zapytaniami do odczytu (co tłumaczyliśmy sobie jako powód braku opóźnienia replikacji).
Gdy uporaliśmy się z przyczynami „rozpadania się” głównego slave’a, analityczny już przestał działać z tego samego powodu. Mimo istnienia dwóch dodatkowych serwerów, na które planowaliśmy przenieść obciążenie w przypadku awarii mastera, z powodu niefortunnego błędu okazało się, że w krytycznym momencie nie ma żadnego.
Jednak ponieważ robiliśmy nie tylko dump bazy danych (odtworzenie trwało wtedy około 5 godzin), ale także snapshot mastera, udało się uruchomić replikę w ciągu 2 godzin. Niestety po tym czekało nas nakładanie logu replikacji przez długi czas (ponieważ proces odbywa się w trybie jednoprocesowym, ale to już całkiem inna historia).
Wynik: Po takim incydencie stało się jasne, że należy zrezygnować z praktyki ograniczania dostępu do nagrywania dla użytkowników i ogłosić cały serwer jako readonly. Przy takim podejściu można być pewnym, że repliki będą dostępne w krytycznym momencie.
Optymalizacja nawet jednego ciężkiego zapytania może "przywrócić do życia" bazę danych.
Chociaż stale aktualizujemy katalog na stronie, zapytania, które kierowaliśmy na serwery Slave, miały małe opóźnienie w stosunku do Mastera. Czas, w którym odkryliśmy i usunęliśmy problem z "niespodziewanie odstającymi" slave'ami, przekroczył "psychologiczny próg" (w tym czasie mogła nastąpić aktualizacja cen, a klienci widzieliby nieaktualne dane), i musieliśmy przełączyć wszystkie zapytania na główny serwer bazy danych. W rezultacie strona działała wolno… ale przynajmniej działała. I podczas gdy Slave się odbudowywał, nie pozostało nam nic innego, jak tylko optymalizacja.
Podczas gdy serwery Slave się odbudowywały, minuty powoli mijały, Master pozostawał przeciążony, a my poświęciliśmy wszystkie siły na optymalizację aktywnych zadań według "Zasady Pareto": wybraliśmy TOP zapytań, które generowały największe obciążenie i zaczęliśmy tuning. Było to robione na bieżąco.
Ciekawy efekt polegał na tym, że MySQL przeciążony do granic możliwości reagował nawet na niewielkie poprawy procesów. Optymalizacja kilku zapytań, które generowały tylko 5% łącznego obciążenia, już pokazała wyraźne odciążenie CPU. W rezultacie udało nam się zapewnić akceptowalny zapas zasobów dla pracy Mastera z bazą danych i uzyskać niezbędny czas na odbudowę replik.
Wynik: Nawet niewielka optymalizacja pozwala "przetrwać" przy przeciążeniu przez kilka godzin. Tyle potrzebowaliśmy na czas odbudowy serwerów z replikami. A tak przy okazji, techniczne aspekty optymalizacji zapytań omówimy w jednym z następnych postów. Tak więc zasubskrybuj nasz blog, jeśli to może okazać się przydatne.
Zorganizuj monitorowanie funkcjonalności usług partnerów.
Zajmujemy się przetwarzaniem zamówień od klientów, dlatego nasze usługi nieustannie współpracują z zewnętrznymi interfejsami API — to bramy do wysyłania SMS-ów, platformy płatnicze, systemy routingu, geokodery, służba skarbowa i wiele innych systemów. Kiedy obciążenie zaczęło szybko rosnąć, zaczęliśmy napotykać ograniczenia API naszych usług partnerskich, o których wcześniej nawet nie myśleliśmy.
Niespodziewane przekroczenie limitów usług partnerskich może prowadzić do przestoju własnych usług. Wiele API blokuje klientów, którzy przekraczają limity, a w niektórych przypadkach nadmiar zapytań może obciążyć produkcję u partnera.
Na przykład, w momencie wzrostu liczby dostaw, towarzyszące usługi nie radziły sobie z zadaniami ich rozdzielania i wyznaczania tras. W rezultacie okazywało się, że zamówienia zostały złożone, a usługa generująca trasę nie działa. Należy zaznaczyć, że nasi logistycy zrobili praktycznie to, co niemożliwe w tych warunkach, a ścisła współpraca zespołu pomogła zrekompensować tymczasowe awarie usług. Jednak tak dużą liczbę zgłoszeń niemożliwe jest nieustanne przetwarzanie ręczne, a po pewnym czasie zmierzyliśmy się z niedopuszczalnym opóźnieniem między zamówieniami a ich realizacją.
Podjęto szereg działań organizacyjnych, a zgrana praca zespołu pomogła zaoszczędzić czas, podczas gdy negocjowaliśmy nowe warunki i czekaliśmy na modernizację usług ze strony niektórych partnerów. Istnieją również inne API, które cieszą się wysoką wydajnością i niebotycznymi stawkami w przypadku dużego ruchu. Na przykład na początku korzystaliśmy z jednego znanego interfejsu API do geokodowania, aby ustalić adres punktu dostawy. Jednak na koniec miesiąca otrzymaliśmy rachunek na prawie 2 miliony rubli. Po tym postanowiliśmy szybko go wymienić. Nie zamierzam promować, ale powiem, że nasze wydatki znacznie się zmniejszyły.

Wynik: Należy koniecznie monitorować warunki pracy wszystkich usług partnerskich i mieć je na uwadze. Nawet jeśli dziś wydaje się, że mają Państwo "duży zapas", nie oznacza to, że jutro nie staną się przeszkodą dla rozwoju. I oczywiście najlepiej jest wcześniej uzgodnić warunki finansowe wzrastających zapytań do usługi.
Czasami okazuje się, że "" (c) nie pomaga
Jesteśmy przyzwyczajeni do "zatorów" w głównych bazach danych lub na serwerach aplikacyjnych, ale podczas skalowania problemy mogą się ujawniać tam, gdzie się ich nie spodziewano. Do pełnotekstowego wyszukiwania na stronie używamy silnika Apache Solr. W miarę wzrostu obciążenia zauważyliśmy spadek czasu odpowiedzi, a obciążenie procesora serwera osiągało już 100%. Co może być prostsze — przydzielimy kontenerowi z Solr więcej zasobów.
Zamiast oczekiwanego wzrostu wydajności, serwer po prostu "umarł". Od razu obciążał się w 100% i odpowiadał jeszcze wolniej. Początkowo mieliśmy 2 rdzenie i 2 GB RAM. Zdecydowaliśmy się zrobić to, co zazwyczaj pomaga — daliśmy serwerowi 8 rdzeni i 32 GB. Wszystko stało się znacznie gorsze (jak dokładnie i dlaczego — opowiemy w osobnym poście).
W ciągu kilku dni zrozumieliśmy szczegóły tej kwestii i osiągnęliśmy optymalną wydajność przy 8 rdzeniach i 32 GB. Ta konfiguracja pozwala nadal zwiększać obciążenie, co jest bardzo ważne, ponieważ wzrost następuje nie tylko wśród klientów, ale także liczby podłączonych sklepów — w ciągu 2 miesięcy ich liczba podwoiła się.
Wynik: Standardowe metody typu "dodać więcej sprzętu" nie działają zawsze. Dlatego przy skalowaniu jakiejkolwiek usługi należy dobrze zrozumieć, w jaki sposób wykorzystuje zasoby i z wyprzedzeniem testować jej działanie w nowych warunkach.
Stateless — klucz do prostego poziomego skalowania.
Ogólnie nasz zespół trzyma się znanego podejścia: usługi nie powinny mieć wewnętrznego stanu (stateless) i powinny być niezależne od środowiska uruchomieniowego. To pozwalało nam wytrzymywać wzrost obciążenia poprzez proste poziome skalowanie. Mieliśmy jednak jedną usługę- wyjątek – obsługiwacz długich zadań w tle. Zajmował się wysyłką emaili i smsów, przetwarzaniem zdarzeń, generowaniem feedów, importowaniem cen i stanów magazynowych oraz przetwarzaniem obrazów. Tak się złożyło, że był on uzależniony od lokalnego magazynu plików i istniał w jedynym egzemplarzu.
Kiedy liczba zadań w kolejce przetwornika wzrosła (co naturalnie miało miejsce wraz ze wzrostem liczby zamówień), wydajność hosta, na którym znajdowały się przetwornik i pamięć plikowa, stała się czynnikiem ograniczającym. W efekcie zastało zatrzymane aktualizowanie asortymentu i cen, wysyłanie powiadomień do użytkowników i wiele innych krytycznych funkcji, które utknęły w kolejce. Zespół Ops szybko przeniósł pamięć plikową do przechowywania w sieci podobnego do S3, co pozwoliło nam uruchomić kilka mocnych maszyn, aby skalować przetwornik zadań w tle.
Wynik: Zasada Stateless musi być przestrzegana dla wszystkich komponentów bez wyjątku, nawet jeśli wydaje się, że „tutaj na pewno się nie utkwimy”. Lepiej poświęcić trochę czasu na odpowiednią organizację pracy wszystkich systemów, niż później w pośpiechu przepisywać kod i naprawiać serwis poddany przeciążeniu.
7 zasad intensywnego wzrostu
Mimo dostępności dodatkowych zasobów, podczas wzrostu natrafiliśmy na kilka przeszkód. W tym czasie liczba zamówień wzrosła ponad 4-krotnie. Obecnie dostarczamy już ponad 17 000 zamówień dziennie w 62 miastach i planujemy jeszcze bardziej rozszerzyć geografię — w pierwszej połowie 2020 roku planowane jest uruchomienie serwisu w całej Rosji. Aby poradzić sobie z rosnącym obciążeniem, uwzględniając już zebrane doświadczenia, sformułowaliśmy 7 podstawowych zasad pracy w warunkach stałego wzrostu:
- Zarządzanie incydentami. Stworzyliśmy planszę w Jira, gdzie każdy incydent jest odzwierciedlany w postaci biletu. To pomoże rzeczywiście priorytetować i realizować zadania związane z incydentem. W końcu w istocie nie ma nic złego w popełnianiu błędów — źle jest popełniać je dwa razy w tej samej sprawie. Na wypadek, gdy incydenty powtarzają się szybciej, niż można naprawić przyczynę, powinna być gotowa instrukcja działania, ponieważ w czasach dużego obciążenia ważne jest szybkie reagowanie.
- Monitoring jest wymagany dla wszystkich elementów infrastruktury bez wyjątku. To dzięki niemu mogliśmy prognozować wzrost obciążenia i prawidłowo wybierać „wąskie gardła” do priorytetyzacji eliminacji. Prawdopodobnie przy dużym obciążeniu zawiedzie lub zacznie spowalniać wszystko, o czym nawet nie myślałeś. Dlatego nowe alerty najlepiej tworzyć tuż po wystąpieniu pierwszych incydentów, aby monitorować i przewidywać je.
- Prawidłowe alerty są po prostu niezbędne przy nagłym wzroście obciążenia. Po pierwsze, powinny informować o tym, co dokładnie zawiodło. Po drugie, nie powinno być ich zbyt wiele, ponieważ nadmiar niekrytycznych alertów prowadzi do ignorowania wszystkich powiadomień w ogóle.
- Aplikacje powinny być bezstanowe. Upewniliśmy się, że dla tej zasady nie powinno być wyjątków. Potrzebna jest pełna niezależność od środowiska uruchomieniowego. Można przechowywać współdzielone dane w bazie danych lub, na przykład, bezpośrednio w S3. A jeszcze lepiej trzymać się zasad. W trakcie gwałtownego wzrostu czasu na optymalizację kodu po prostu nie ma, więc należy radzić sobie z obciążeniem metodą bezpośredniego zwiększenia zasobów obliczeniowych oraz skalowania poziomego.
- Limity i wydajność zewnętrznych usług. Przy szybkim wzroście problem może wystąpić nie tylko w twojej infrastrukturze, ale i w zewnętrznej usłudze. Najbardziej frustrujące jest to, gdy dzieje się to nie z powodu awarii, a z powodu osiągnięcia limitów lub kwot. Tak więc zewnętrzne usługi muszą skalować się tak dobrze, jak ty sam.
- Dziel procesy i kolejki. Bardzo to pomaga, gdy na jednym z bram zjawia się zator. Nie napotkalibyśmy opóźnień w przesyłaniu danych, gdyby wypełnione kolejki wysyłania SMS-ów nie przeszkadzały w wymianie powiadomień między systemami informatycznymi. A liczba pracowników byłaby łatwiejsza do zwiększenia, gdyby pracowali osobno.
- Finansowe realia. Gdy następuje eksplozja wzrostu przepływów danych, nie ma czasu na myślenie o taryfach i subskrypcjach. Ale trzeba o nich pamiętać, zwłaszcza jeśli jesteś małą firmą. Duże rachunki mogą wystawić właściciele dowolnego API, a także twój dostawca usług hostingowych. Dlatego umowy należy czytać uważnie.
Podsumowanie
Nie bez strat, ale przetrwaliśmy ten etap i dziś staramy się trzymać wszystkich znalezionych zasad, a każda maszyna ma możliwość łatwego zwiększenia wydajności x4, aby poradzić sobie z nieoczekiwanymi sytuacjami.
W kolejnych postach podzielimy się naszym doświadczeniem w badaniu spadku wydajności w Apache Solr, a także opowiemy o optymalizacji zapytań i o tym, jak współpraca z FNS pomaga firmie oszczędzać pieniądze. Subskrybuj naszego bloga, aby niczego nie przegapić, i opowiedz w komentarzach, czy mieliście podobne problemy podczas wzrostu ruchu.

Tylko zarejestrowani użytkownicy mogą brać udział w ankiecie. , proszę.
Czy doświadczyliście spowolnienia/spadku usługi podczas gwałtownego wzrostu obciążenia z powodu:
55,6%Niemożności szybkiego dodania zasobów obliczeniowych10
16,7%Limitów infrastruktury dostawcy hostingu3
33,3%Limitów zewnętrznych API6
27,8%Naruszenia zasad stateless własnych aplikacji5
88,9%Nieoptymalności kodu własnych usług16
Oddano głos 18 użytkowników. 6 użytkowników wstrzymało się.
Źródło: habr.com
