Jak zwiększamy wydajność i odporność infrastruktury Roblox

Wraz z rozwojem Roblox w ciągu ostatnich ponad 16 lat wzrosła skala i złożoność infrastruktury technicznej, która obsługuje miliony wciągających wspólnych doświadczeń 3D. Liczba obsługiwanych przez nas maszyn wzrosła ponad trzykrotnie w ciągu ostatnich dwóch lat, z około 36 000 na dzień 30 czerwca 2021 r. do prawie 145 000 obecnie. Obsługa tych zawsze dostępnych doświadczeń dla ludzi na całym świecie wymaga ponad 1000 wewnętrznych usług. Aby pomóc nam kontrolować koszty i opóźnienia sieciowe, wdrażamy i zarządzamy tymi maszynami w ramach niestandardowej, hybrydowej infrastruktury chmury prywatnej, która działa głównie na miejscu.
Nasza infrastruktura obsługuje obecnie ponad 70 milionów aktywnych użytkowników dziennie na całym świecie, w tym twórców, którzy w swojej działalności opierają się na ekonomii Roblox. Wszystkie te miliony ludzi oczekują bardzo wysokiego poziomu niezawodności. Biorąc pod uwagę immersyjny charakter naszych doświadczeń, tolerancja na opóźnienia lub lag jest niezwykle niska, nie mówiąc już o awariach. Roblox to platforma służąca komunikacji i nawiązywaniu kontaktów, gdzie ludzie spotykają się w wciągających doświadczeniach 3D. Kiedy ludzie komunikują się jako swoje awatary w immersyjnej przestrzeni, nawet niewielkie opóźnienia lub usterki są bardziej zauważalne niż w przypadku czatu tekstowego lub telekonferencji.
W październiku 2021 roku doświadczyliśmy awarii obejmującej cały system. Zaczęło się od drobnego problemu z jednym komponentem w jednym centrum danych. Jednak podczas badania sytuacji problem szybko się rozprzestrzenił i ostatecznie doprowadził do 73-godzinnej przerwy w działaniu. W tamtym czasie podzieliliśmy się zarówno szczegółami dotyczącymi tego, co się wydarzyło, jak i niektórymi z naszych wczesnych wniosków płynących z tego zdarzenia. Od tego czasu analizujemy te wnioski i pracujemy nad zwiększeniem odporności naszej infrastruktury na rodzaje awarii, które występują we wszystkich systemach na dużą skalę z powodu takich czynników, jak ekstremalne skoki ruchu, pogoda, awarie sprzętu, błędy oprogramowania lub po prostu ludzkie pomyłki. Kiedy dochodzi do takich awarii, jak zapewnić, aby problem w pojedynczym komponencie lub grupie komponentów nie rozprzestrzenił się na cały system? To pytanie było w centrum naszej uwagi przez ostatnie dwa lata i chociaż prace wciąż trwają, to to, co zrobiliśmy do tej pory, już się opłaca. Na przykład w pierwszej połowie 2023 roku zaoszczędziliśmy 125 milionów godzin pracy miesięcznie w porównaniu z pierwszą połową 2022 roku. Dzisiaj dzielimy się tym, co już zrobiliśmy, a także naszą długoterminową wizją budowy bardziej odpornego systemu infrastruktury.

Tworzenie zabezpieczenia
W systemach infrastruktury na dużą skalę wiele razy dziennie zdarzają się drobne awarie. Jeśli jedna maszyna ma problem i trzeba ją wyłączyć, da się to ogarnąć, bo większość firm ma wiele instancji swoich usług zaplecza. Więc jak jedna instancja zawiedzie, inne przejmują obciążenie. Żeby sobie poradzić z tymi częstymi awariami, żądania są zazwyczaj ustawione tak, żeby automatycznie próbowały ponownie, jeśli pojawi się błąd.
Staje się to wyzwaniem, gdy system lub osoba ponawia próby zbyt agresywnie, co może spowodować rozprzestrzenienie się tych niewielkich awarii w całej infrastrukturze na inne usługi i systemy. Jeśli sieć lub użytkownik będzie ponawiać próby wystarczająco uparcie, ostatecznie doprowadzi to do przeciążenia każdej instancji tej usługi, a potencjalnie także innych systemów, w skali globalnej. Nasza awaria w 2021 roku była wynikiem zjawiska dość powszechnego w systemach na dużą skalę: awaria zaczyna się na małą skalę, a następnie rozprzestrzenia się w systemie, narastając tak szybko, że trudno ją rozwiązać, zanim wszystko przestanie działać.
W momencie awarii mieliśmy jedno aktywne centrum danych (z komponentami pełniącymi rolę kopii zapasowej). Potrzebowaliśmy możliwości ręcznego przełączenia się na nowe centrum danych, gdy problem spowodował awarię istniejącego. Naszym priorytetem było zapewnienie zapasowej instalacji Roblox, więc zbudowaliśmy tę kopię zapasową w nowym centrum danych, zlokalizowanym w innym regionie geograficznym. Zapewniło to dodatkową ochronę na wypadek najgorszego scenariusza: awarii rozprzestrzeniającej się na tyle komponentów w centrum danych, że staje się ono całkowicie nieczynne. Obecnie mamy jedno centrum danych obsługujące obciążenia (aktywne) i jedno w trybie gotowości, służące jako kopia zapasowa (pasywne). Naszym długoterminowym celem jest przejście z tej konfiguracji aktywnej-pasywnej do konfiguracji aktywnej-aktywnej, w której oba centra danych obsługują obciążenia, a moduł równoważenia obciążenia rozdziela żądania między nimi w oparciu o opóźnienie, wydajność i stan. Gdy to zostanie wdrożone, spodziewamy się jeszcze większej niezawodności całego Roblox i możliwości niemal natychmiastowego przełączenia awaryjnego zamiast trwającego kilka godzin.

Przejście na infrastrukturę klastrową
Naszym kolejnym priorytetem było stworzenie solidnych ścian ochronnych wewnątrz każdego centrum danych, aby zmniejszyć ryzyko awarii całego centrum danych. Komórki (niektóre firmy nazywają je klastrami) to zasadniczo zestaw maszyn i właśnie w ten sposób tworzymy te ściany. Replikujemy usługi zarówno w obrębie komórek, jak i pomiędzy nimi, aby zapewnić dodatkową nadmiarowość. Ostatecznie chcemy, aby wszystkie usługi w Roblox działały w komórkach, dzięki czemu będą mogły korzystać zarówno z solidnych ścian ochronnych, jak i nadmiarowości. Jeśli komórka przestaje działać, można ją bezpiecznie wyłączyć. Replikacja między komórkami pozwala usłudze działać dalej, podczas gdy komórka jest naprawiana. W niektórych przypadkach naprawa komórki może oznaczać jej całkowite ponowne skonfigurowanie. W branży czyszczenie i ponowne konfigurowanie pojedynczego komputera lub małego zestawu komputerów jest dość powszechne, ale robienie tego dla całej komórki, która zawiera około 1400 komputerów, już nie.
Aby to działało, komórki te muszą być w dużej mierze jednolite, dzięki czemu możemy szybko i efektywnie przenosić obciążenia z jednej komórki do drugiej. Ustaliliśmy pewne wymagania, które usługi muszą spełniać, zanim będą mogły działać w komórce. Na przykład usługi muszą być konteneryzowane, co znacznie zwiększa ich przenośność i uniemożliwia komukolwiek wprowadzanie zmian konfiguracyjnych na poziomie systemu operacyjnego. W przypadku komórek przyjęliśmy filozofię „infrastruktury jako kodu”: w naszym repozytorium kodu źródłowego umieszczamy definicję wszystkiego, co znajduje się w komórce, dzięki czemu możemy szybko odbudować ją od podstaw przy użyciu zautomatyzowanych narzędzi.
Nie wszystkie usługi spełniają obecnie te wymagania, więc pracowaliśmy nad tym, aby pomóc właścicielom usług w ich spełnieniu tam, gdzie to możliwe, i stworzyliśmy nowe narzędzia ułatwiające migrację usług do komórek, gdy będą gotowe. Na przykład nasze nowe narzędzie wdrożeniowe automatycznie „rozdziela” wdrożenie usługi między komórki, dzięki czemu właściciele usług nie muszą martwić się o strategię replikacji. Taki poziom rygoru sprawia, że proces migracji jest znacznie trudniejszy i bardziej czasochłonny, ale długoterminową korzyścią będzie system, w którym:
- znacznie łatwiej będzie ograniczyć awarię i zapobiec jej rozprzestrzenianiu się na inne komórki;
- Nasi inżynierowie infrastruktury mogą pracować wydajniej i szybciej; oraz
- Inżynierowie, którzy tworzą usługi na poziomie produktu, które są ostatecznie wdrażane w komórkach, nie muszą wiedzieć ani martwić się o to, w których komórkach działają ich usługi.
Rozwiązywanie większych wyzwań
Podobnie jak drzwi przeciwpożarowe służą do powstrzymywania płomieni, komórki działają jak mocne ściany przeciwwybuchowe w naszej infrastrukturze, pomagając powstrzymać każdy problem, który powoduje awarię w pojedynczej komórce. Ostatecznie wszystkie usługi składające się na Roblox będą wdrażane redundantnie wewnątrz komórek i pomiędzy nimi. Po zakończeniu tych prac problemy nadal mogą się rozprzestrzeniać na tyle szeroko, że cała komórka stanie się nieczynna, ale niezwykle trudno byłoby, aby problem rozprzestrzenił się poza tę komórkę. A jeśli uda nam się sprawić, że komórki będą wymienne, przywrócenie sprawności będzie znacznie szybsze, ponieważ będziemy mogli przełączyć się na inną komórkę i zapobiec wpływowi problemu na użytkowników końcowych.
Trudność polega na tym, aby oddzielić te komórki na tyle, by ograniczyć możliwość rozprzestrzeniania się błędów, jednocześnie zachowując wydajność i funkcjonalność. W złożonym systemie infrastruktury usługi muszą komunikować się ze sobą, aby dzielić się zapytaniami, informacjami, obciążeniami itp. Replikując te usługi do komórek, musimy starannie przemyśleć sposób zarządzania komunikacją między nimi. W idealnym świecie przekierowujemy ruch z jednej komórki w złym stanie do innych komórek w dobrym stanie. Ale jak zarządzać „zapytaniem śmierci” — takim, które powoduje, że komórka staje się niesprawna? Jeśli przekierujemy to zapytanie do innej komórki, może to spowodować, że ta komórka stanie się niesprawna, dokładnie tak, jak staramy się tego uniknąć. Musimy znaleźć mechanizmy, które pozwolą nam przekierować „dobry” ruch z niesprawnych komórek, jednocześnie wykrywając i blokując ruch, który powoduje, że komórki stają się niesprawne.
W perspektywie krótkoterminowej wdrożyliśmy kopie usług obliczeniowych w każdej komórce obliczeniowej, tak aby większość żądań kierowanych do centrum danych mogła być obsługiwana przez jedną komórkę. Zajmujemy się również równoważeniem obciążenia ruchem między komórkami. Patrząc w dalszą przyszłość, rozpoczęliśmy tworzenie procesu wykrywania usług nowej generacji, który będzie wykorzystywany przez siatkę usług (service mesh) i który mamy nadzieję ukończyć w 2024 roku. Pozwoli nam to wdrożyć zaawansowane zasady, które umożliwią komunikację między komórkami tylko wtedy, gdy nie wpłynie to negatywnie na komórki przejmujące obciążenie. Również w 2024 r. pojawi się metoda kierowania zależnych żądań do wersji usługi w tej samej komórce, co zminimalizuje ruch międzykomórkowy, a tym samym zmniejszy ryzyko rozprzestrzeniania się awarii między komórkami.
W szczytowych momentach ponad 70 procent ruchu naszych usług zaplecza jest obsługiwanych poza komórkami i wiele się nauczyliśmy o tym, jak tworzyć komórki, ale przewidujemy dalsze badania i testy w miarę kontynuowania migracji naszych usług w 2024 roku i później. W miarę postępów te bariery ochronne będą stawały się coraz silniejsze.

Migracja infrastruktury działającej w trybie ciągłym
Roblox to globalna platforma obsługująca użytkowników na całym świecie, więc nie możemy przenosić usług w godzinach poza szczytem lub w czasie „przestoju”, co dodatkowo komplikuje proces migracji wszystkich naszych maszyn do komórek i uruchamiania naszych usług w tych komórkach. Mamy miliony zawsze aktywnych doświadczeń, które muszą być nadal obsługiwane, nawet gdy przenosimy maszyny, na których działają, oraz usługi, które je obsługują. Kiedy rozpoczęliśmy ten proces, nie mieliśmy dziesiątek tysięcy maszyn, które po prostu stałyby bezczynnie i byłyby dostępne do migracji tych obciążeń.
Mieliśmy jednak niewielką liczbę dodatkowych maszyn, które zostały zakupione w oczekiwaniu na przyszły wzrost. Na początek zbudowaliśmy nowe komórki przy użyciu tych maszyn, a następnie przenieśliśmy na nie obciążenia. Cenimy sobie zarówno wydajność, jak i niezawodność, więc zamiast kupować więcej maszyn, gdy skończyły się nam „zapasowe” maszyny, zbudowaliśmy więcej komórek, czyszcząc i ponownie konfigurując maszyny, z których przeprowadziliśmy migrację. Następnie przenieśliśmy obciążenia na te ponownie skonfigurowane maszyny i rozpoczęliśmy cały proces od nowa. Proces ten jest złożony — w miarę jak maszyny są wymieniane i zwalniają się, aby można je było włączyć do komórek, nie zwalniają się one w idealny, uporządkowany sposób. Są one fizycznie rozproszone po różnych salach serwerowych, co zmusza nas do ich przydzielania w sposób fragmentaryczny, co z kolei wymaga procesu defragmentacji na poziomie sprzętowym, aby zapewnić zgodność lokalizacji sprzętu z obszarami awarii fizycznej na dużą skalę.
Część naszego zespołu inżynierów infrastruktury koncentruje się na migracji istniejących obciążeń z naszego starszego środowiska, czyli środowiska „przedkomórkowego”, do komórek. Prace te będą trwały do momentu, aż przeniesiemy tysiące różnych usług infrastrukturalnych i tysiące usług zaplecza do nowo zbudowanych komórek. Spodziewamy się, że ze względu na pewne czynniki komplikujące sytuację zajmie to cały przyszły rok, a być może nawet sięgnie 2025 roku. Po pierwsze, prace te wymagają stworzenia solidnych narzędzi. Na przykład potrzebujemy narzędzi do automatycznego równoważenia dużej liczby usług podczas wdrażania nowej komórki — bez wpływu na naszych użytkowników. Zauważyliśmy również usługi, które zostały zbudowane w oparciu o założenia dotyczące naszej infrastruktury. Musimy zrewidować te usługi, aby nie były zależne od elementów, które mogą ulec zmianie w przyszłości w miarę przechodzenia na komórki. Wdrożyliśmy również zarówno sposób wyszukiwania znanych wzorców projektowych, które nie będą dobrze współpracować z architekturą komórkową, jak i metodyczny proces testowania każdej migrowanej usługi. Procesy te pomagają nam zapobiegać wszelkim problemom użytkowników spowodowanym niekompatybilnością usługi z komórkami.
Obecnie komórki zarządzają blisko 30 000 maszyn. To tylko ułamek naszej całej floty, ale jak dotąd przejście przebiega bardzo płynnie, bez negatywnego wpływu na graczy. Naszym ostatecznym celem jest osiągnięcie przez nasze systemy 99,99% dostępności dla użytkowników każdego miesiąca, co oznacza, że zakłócenia nie będą przekraczać 0,01% godzin aktywności. W całej branży nie da się całkowicie wyeliminować przestojów, ale naszym celem jest ograniczenie przestojów w Roblox do poziomu, który będzie praktycznie niezauważalny.
Przygotowanie na przyszłość w miarę rozwoju
Chociaż nasze wczesne działania okazują się skuteczne, praca nad komórkami jest daleka od zakończenia. W miarę rozwoju Roblox będziemy nadal pracować nad poprawą wydajności i odporności naszych systemów dzięki tej i innym technologiom. W miarę postępów platforma będzie coraz bardziej odporna na problemy, a wszelkie występujące usterki powinny stawać się coraz mniej widoczne i mniej uciążliwe dla użytkowników naszej platformy.
Podsumowując, do tej pory:
- Zbudowaliśmy drugie centrum danych i pomyślnie osiągnęliśmy status aktywny/pasywny.
- Stworzyliśmy komórki w naszych aktywnych i pasywnych centrach danych i pomyślnie przenieśliśmy do nich ponad 70 procent ruchu naszych usług zaplecza.
- Ustaliliśmy wymagania i najlepsze praktyki, których będziemy musieli przestrzegać, aby zachować jednolitość wszystkich komórek w miarę dalszej migracji pozostałej części naszej infrastruktury.
- Rozpoczęliśmy ciągły proces budowania silniejszych „ścian ochronnych” między komórkami.
W miarę jak komórki te staną się bardziej wymienne, będzie mniej zakłóceń między nimi. Otwiera to przed nami bardzo interesujące możliwości w zakresie zwiększenia automatyzacji monitorowania, rozwiązywania problemów, a nawet automatycznego przenoszenia obciążeń.
We wrześniu rozpoczęliśmy również eksperymenty typu active/active w naszych centrach danych. Jest to kolejny mechanizm, który testujemy w celu poprawy niezawodności i zminimalizowania czasu przełączania awaryjnego. Eksperymenty te pomogły zidentyfikować szereg wzorców projektowych systemu, głównie w zakresie dostępu do danych, które musimy przerobić, dążąc do osiągnięcia pełnej aktywności typu active-active. Ogólnie rzecz biorąc, eksperyment okazał się na tyle udany, że pozostawiliśmy go uruchomionym dla ruchu z ograniczonej liczby naszych użytkowników.



