Jak firma Roblox skalowała swoją platformę Kafka do ponad 18 bilionów wiadomości dziennie
Kompleksowa optymalizacja na rzecz wydajnych systemów

W ciągu ostatnich dwóch lat skalowaliśmy naszą wewnętrzną platformę kolejki komunikatów ponad 2,5-krotnie każdego roku, osiągając ponad 18 bilionów komunikatów dziennie. Obecnie przetwarzamy ponad 300 milionów komunikatów na sekundę, utrzymując dostępność na poziomie 99,995%. Ta platforma kolejkowania komunikatów, oparta na Apache Kafka, stała się kluczowym elementem przepływu danych w całej firmie. Obsługuje ona obciążenia robocze, takie jak zdarzenia analityczne, procesy moderacji, systemy kojarzenia użytkowników i wiele innych. Liczby te są imponujące, ale ważniejsza jest historia tego, jak do tego doszliśmy.
Wpływ optymalizacji typu „end-to-end”
Powszechnym błędnym przekonaniem dotyczącym systemów działających na dużą skalę jest to, że pojemność to głównie problem sprzętowy. Z naszego doświadczenia wynika, że najbardziej znaczące ulepszenia osiąga się, traktując skalowalność jako kompleksowy problem inżynieryjny.
Zespoły w całej firmie Roblox zbudowały różne platformy oparte na kolejce komunikatów, w tym procesy szkolenia sztucznej inteligencji, abstrakcje przetwarzania strumieniowego oraz systemy telemetryczne. Współpraca z właścicielami tych platform pozwala nam zapewnić przestrzeganie najlepszych praktyk, takich jak wdrażanie przetwarzania wsadowego i kompresji. Efektem tego są wydajne systemy, które nie wymagają od użytkowników końcowych szczegółowej konfiguracji.

Kluczowe znaczenie ma uzgodnienie z zespołami korzystającymi z platformy priorytetów biznesowych, wskaźników pomiaru oraz kryteriów sukcesu, co sprzyja współpracy między zespołami. Kluczowe wskaźniki różnią się w zależności od warstw systemu: podczas gdy my możemy optymalizować serwery Kafka pod kątem wydajnego przetwarzania dużych ilości danych przy mniejszej liczbie maszyn, użytkownicy końcowi mogą chcieć zoptymalizować opóźnienie od początku do końca z dokładnością do milisekund lub całkowity czas potrzebny na zakończenie zapisu danych w ramach zadania wsadowego.
Nie każdy ruch jest taki sam
Jedną z kluczowych lekcji, jaką wyciągnęliśmy, jest to, że platformy działające na dużą skalę są bardziej niezawodne, gdy nie traktują całego ruchu jednakowo. Niektóre obciążenia wymagają bardzo niskiego opóźnienia i silnej ochrony podczas szczytów obciążenia, takie jak nasze systemy kojarzenia użytkowników, które działają z opóźnieniem od początku do końca poniżej 10 milisekund. Inne charakteryzują się niezwykle dużym natężeniem, ale są bardziej tolerancyjne na opóźnienia, jak nasze potoki pobierania zdarzeń analitycznych, które mogą działać płynnie nawet przy opóźnieniu od początku do końca wynoszącym kilka sekund. Jeśli oba rodzaje ruchu są obsługiwane w identyczny sposób, ruch o największym natężeniu może wyprzeć ruch bardziej krytyczny dla działalności, zwłaszcza podczas szczytowego obciążenia.
Aby rozwiązać ten problem, grupujemy ruch według wymagań poszczególnych przypadków użycia, aby zapewnić odpowiednią obsługę każdego z nich. Kompromis między opóźnieniem od początku do końca a przepustowością w Kafce jest jednym z głównych czynników branych pod uwagę podczas konfigurowania nowych przypadków użycia. Dotyczy to całej ścieżki danych od producenta przez serwer do konsumenta: każdy z tych elementów posiada konfiguracje, które można dostosować w celu optymalizacji przepustowości lub opóźnienia. Konfiguracje po stronie klienta można dostosować do poszczególnych przypadków użycia, ale konfiguracje po stronie serwera są mniej elastyczne, ponieważ utrzymanie dedykowanego klastra dla każdego odrębnego przypadku użycia jest nie do utrzymania.
To podejście polegające na optymalizacji każdego klastra pod kątem obsługiwanych przez niego scenariuszy zastosowań stosujemy na wszystkich poziomach, aż do warstwy sprzętowej.
Sprzęt nadal ma kluczowe znaczenie
Zaczęliśmy od uruchomienia Kafki na maszynach z dyskami HDD w macierzy RAID10. Wraz ze wzrostem natężenia ruchu przepustowość operacji wejścia/wyjścia na dyskach naszych maszyn stała się głównym wąskim gardłem ograniczającym przepustowość Kafki na poszczególnych serwerach. Aby temu zaradzić, wprowadziliśmy dwie optymalizacje. Przekonwertowaliśmy brokery tak, aby w miarę możliwości korzystały z dysków SSD, przenosząc wąskie gardło z przepustowości operacji wejścia/wyjścia na dyskach na przepustowość sieciową lub przestrzeń dyskową (ponieważ zakupione przez nas dyski SSD miały mniejszą przestrzeń dyskową). Zwiększyło to przepustowość operacji wejścia/wyjścia na dysku dziesięciokrotnie. Rozpoczęliśmy również konwersję pozostałych dysków HDD z macierzy RAID10 na RAID0. Pozwoliło to w przybliżeniu podwoić przepustowość operacji wejścia/wyjścia na dysku, ponieważ każdy zapis w macierzy RAID0 wymaga tylko jednego zapisu na dysku zamiast dwóch.
Kompromisem jest to, że ponieważ każda replika wiadomości jest zapisywana na jednym dysku na brokera zamiast na dwóch, przejście z macierzy RAID10 na RAID0 może wiązać się z ryzykiem utraty niezawodności. Aby zachować niezawodność, skupiliśmy się na kluczowych przypadkach użycia, zwiększając liczbę potwierdzeń (acks) oraz replikację danych, w tym replikację do wielu regionów.
Pełne wykorzystanie możliwości każdej maszyny w zakresie pamięci masowej i przepustowości wymagało dodatkowej pracy. Zoptymalizowaliśmy alokację pamięci oraz konfigurację zarządzania pamięcią podręczną stron, tak aby Kafka mogła buforować komunikaty i obsługiwać odczyty lub dostarczać najnowsze dane z pamięci, sięgając do dysku i zużywając cenne operacje IOPS tylko wtedy, gdy jest to konieczne, na przykład podczas odczytu danych historycznych.
Przeprowadzamy również testy porównawcze wydajności, aby poznać ograniczenia sprzętowe i określić, jakie ograniczenia na poziomie oprogramowania musimy ustawić. W praktyce oznacza to ustawienie ograniczeń odczytu i zapisu dla brokerów Kafki, które różnią się w zależności od sprzętu, na którym działają klastry. Nasze maszyny z dyskami SSD mogą obsłużyć wyższą przepustowość na brokera, podczas gdy maszyny z dyskami HDD lepiej nadają się do zastosowań wymagających długoterminowego przechowywania danych historycznych.
Budowanie z myślą o niezawodności
Platforma kolejek jest kluczowym elementem infrastruktury Roblox, a utrzymanie jej niezawodności ma zasadnicze znaczenie. Testy przed uruchomieniem, a także bieżące testy scenariuszy awarii (czasami nazywane ćwiczeniami „game day”) odegrały kluczową rolę w potwierdzeniu, że nasza infrastruktura jest odporna na różne rodzaje awarii, takie jak awaria pojedynczego hosta, awaria szafy serwerowej, problemy z siecią i wiele innych. Wewnętrzna platforma testów chaotycznych naszego zespołu ds. niezawodności umożliwiła i usprawniła proces symulacji różnych scenariuszy awarii. Testy te pomagają zidentyfikować możliwości ulepszenia systemu lub regresje oraz budować doskonałość operacyjną poprzez regularne weryfikowanie alertów i zapewnienie, że operatorzy znają kroki niezbędne do radzenia sobie z różnymi problemami – nawet tymi rzadkimi.
Zaprojektowaliśmy również i wdrożyliśmy procesy przełączania awaryjnego, które pozwalają szybko przywrócić funkcjonalność kolejki w zależnych przypadkach użycia. Nawet w przypadku całkowitej awarii pojedynczego klastra jesteśmy w stanie przełączyć klientów na sprawny klaster o wystarczającej pojemności. Aby zapewnić dostępność wystarczającej ilości niewykorzystanej pojemności, utrzymujemy rezerwowy klaster Kafka. Pomaga to wyeliminować ryzyko wystąpienia problemów związanych z „hałaśliwym sąsiadem” podczas przenoszenia ruchu między aktywnymi klastrami.
Poniższy schemat przedstawia ogólne etapy przeprowadzania przełączenia awaryjnego. Najpierw operator określa zakres przełączenia awaryjnego. Następnie za pomocą zautomatyzowanej operacji zbiorczej w klastrze rezerwowym tworzone są tematy i listy kontroli dostępu (ACL). Na koniec operator aktualizuje konfiguracje naszych usług płaszczyzny sterowania, które klienci sprawdzają w celu ustalenia, czy konieczne jest ponowne nawiązanie połączenia. Poniższa ilustracja dotyczy ogólnych przypadków użycia w czasie rzeczywistym. Dysponujemy dodatkowymi strategiami i narzędziami dla innych przypadków użycia i scenariuszy, w tym tworzeniem kopii lustrzanych tematów za pomocą Mirror Maker 2, narzędziami do uzupełniania danych i innymi rozwiązaniami.

Roblox stawia surowe wymagania dotyczące bezpieczeństwa, które jest również kluczowym elementem niezawodności. Wdrożyliśmy standardowe funkcje bezpieczeństwa, takie jak szyfrowanie podczas przesyłania (za pomocą mTLS), szyfrowanie danych w spoczynku (za pomocą szyfrowanego dysku), wymagania dotyczące okresu przechowywania danych oraz uwierzytelnianie i autoryzację za pomocą podmiotów Kafki i list kontroli dostępu (ACL). Ponadto automatycznie rotujemy dane uwierzytelniające naszych użytkowników, aby jeszcze bardziej zwiększyć bezpieczeństwo.
Oprócz tego stworzyliśmy niestandardową obsługę w naszej płaszczyźnie kontrolnej, aby ułatwić szybkie aktualizowanie systemu operacyjnego i opróżnianie szaf serwerowych. Podczas aktualizacji systemu operacyjnego zatrzymujemy dane na dysku, dzięki czemu zaktualizowane maszyny mogą szybko odzyskać sprawność po ponownym dołączeniu do klastra Kafka. W ten sposób możemy zaktualizować całą naszą flotę w ciągu 30 dni, nawet jeśli wciąż się powiększa. Korzystając z narzędzi takich jak Cruise Control, replikujemy dane z uwzględnieniem poszczególnych szaf serwerowych, dzięki czemu całe szafy mogą zostać wyłączone w celu konserwacji i instalowania poprawek sieciowych bez wpływu na dostępność danych.
Perspektywy na przyszłość
Kolejny etap rozwoju naszej platformy kolejek polega na uczynieniu jej jeszcze prostszą dla programistów, przy jednoczesnym dalszym zwiększaniu elastyczności i izolacji. Inwestujemy w nowe abstrakcje, w tym proxy producentów i konsumentów, a także w pełni zarządzane łączniki. Systemy te jeszcze bardziej oddzielą zespoły aplikacyjne od złożoności infrastruktury i sprawią, że nasze platformy będą łatwiejsze do wdrożenia i zarządzania na dużą skalę. Na przykład proxy konsumenta umożliwi stosowanie wzorca konkurujących konsumentów, dzięki czemu platforma będzie mogła obsługiwać szerszy zakres przypadków użycia oraz oddzielić skalowanie procesorów od skalowania partycji Kafki. Jest to cenne w przypadku takich zastosowań, jak przetwarzanie masowych fal powiadomień, które nie wymagają ustalania kolejności i mogą skorzystać na tymczasowym zwiększeniu liczby procesorów bez konieczności osiągania określonych celów dostarczania danych od początku do końca.
Skalowanie Kafki to nie tylko zwiększanie liczby i rozmiaru klastrów. Chodzi o budowanie dyscypliny inżynierskiej, abstrakcji i modelu operacyjnego, które pozwolą Roblox kontynuować naszą podróż, aby codziennie łączyć miliard ludzi w atmosferze optymizmu i wzajemnego szacunku.
Podziękowania: Dziękujemy Danny'emu Yuanowi i George'owi Li za ich znaczący wkład w rozwój platformy kolejkowej Roblox.


