Szybkie skalowanie magazynu danych online dla ponad miliarda użytkowników ChatGPT (część II)
Jak zoptymalizowaliśmy Azure Cosmos DB w Habitat, naszej platformie przechowywania danych aplikacji, aby sprostać bezprecedensowemu wzrostowi.
Wszystkie produkty OpenAI wymagają szybkiego i niezawodnego dostępu do danych, który zapewnia nasza platforma przechowywania danych — Habitat. Musi ona odpowiadać w ciągu milisekund na żądania ponad miliarda użytkowników w 40 regionach geograficznych. Nasza infrastruktura stanęła przed wyjątkowym wyzwaniem: obsługiwać gwałtowny wzrost i nowe zastosowania, a jednocześnie zwiększać niezawodność, wydajność i tempo rozwoju produktów. Sprostanie temu wyzwaniu wymagało ponownego przemyślenia architektury i sposobu utrzymania naszej platformy przechowywania danych online. Stale rozważaliśmy kompromisy, trzymając się kolejności: najpierw niezawodność wystarczająca do obsługi krytycznego ruchu produktowego, potem szybkość odpowiednia dla użytkowników na całym świecie, a na końcu sprawne działanie na ogromną skalę.
Ta dwuczęściowa seria opisuje, jak rozwinęliśmy Habitat z prostej biblioteki klienckiej i bazy danych w globalnie rozproszoną platformę obsługującą ponad 500 petabajtów danych. Część I dotyczyła ewolucji Habitat z klienta w Pythonie w usługę. Opisaliśmy w niej także techniki, dzięki którym usługa oparta na Pythonie znacznie przekroczyła granice uznawane przez wielu za praktyczne.
W tym wpisie, czyli części II, opisujemy, jak skalowaliśmy bazowy komponent przechowywania danych: Azure Cosmos DB. Dzięki ścisłej współpracy z zespołem Azure wypracowaliśmy skuteczne podejście do wielodostępności oparte na wspólnie zarządzanych kontenerach, zapewniające doskonałą wydajność odczytu na ogromną, globalną skalę.
Rysunek 01 · Czym jest Habitat?
Platforma magazynowa online
Habitat to zbudowana przez nas platforma magazynowa online, która zapewnia produktom OpenAI szybki i niezawodny dostęp do potrzebnych informacji.
- Żądanie
- Odpowiedź
- Zmiany (CDC)
Wraz z rozwojem firm zespoły infrastruktury często przechodzą od systemów z jednym dzierżawcą do systemów wielodostępnych. W modelu z jednym dzierżawcą każdy zespół odpowiedzialny za obszar produktu utrzymuje własny system. Ogranicza to zasięg awarii, ułatwia wyznaczanie odpowiedzialności i zapewnia izolację obciążeń. Gdy przybywa zespołów i obszarów produktów, utrzymanie takiego modelu staje się kosztowne. Model z jednym dzierżawcą obarcza każdy zespół produktowy odpowiedzialnością za planowanie zasobów i często prowadzi do przydzielania ich ze znacznym nadmiarem.
W modelu wielodostępnym kontrolę sprawuje natomiast centralny zespół baz danych, dysponujący większą wiedzą i doświadczeniem w przydzielaniu zasobów. Wielodostępność rodzi jednak własne problemy, takie jak efekt hałaśliwego sąsiada: jeden dzierżawca zużywa wyjątkowo dużą część zasobów kosztem pozostałych.
Projektując Habitat jako niezawodny i efektywny system przechowywania danych działający na dużą skalę, musieliśmy rozważyć wszystkie te kompromisy. Dziś Habitat jest wielodostępną warstwą bazodanową dla wielu produktów OpenAI, ale droga do tego zatoczyła koło: od jednego wielodostępnego kontenera Azure Cosmos DB, przez kontenery zaplecza dla pojedynczych dzierżawców, po powrót do domyślnego kontenera wielodostępnego. Wciąż jesteśmy na początku drogi z wielodostępnością w Habitat. Obsługujemy szeroki zakres zastosowań, ale mogliśmy odłożyć inwestycje w tym obszarze, korzystając wszędzie tam, gdzie to możliwe, z wbudowanych mechanizmów Azure Cosmos DB.
Zanim opiszemy naszą drogę z Azure Cosmos DB, kilka podstawowych informacji:
Konto Azure Cosmos DB definiuje zestaw kontenerów i zasady replikacji
Każdemu kontenerowi przydziela się określoną przepustowość w jednostkach żądań na sekundę (RU/s)
Dane w każdym kontenerze są dzielone na fragmenty według wskazanego klucza partycji logicznej, z udokumentowanymi limitami pojemności i przepustowości dla każdego takiego klucza
Azure Cosmos DB rozmieszcza klucze partycji logicznych na mniejszej liczbie bazowych partycji fizycznych. Dzieli je na kolejne partycje fizyczne, gdy ilość danych przekroczy próg lub gdy kontener potrzebuje więcej partycji fizycznych do zapewnienia przydzielonej przepustowości
Początkowo przepuszczaliśmy cały ruch klientów, licząc na to, że deklarowany limit 10 tys. RU/s na klucz partycji logicznej zapewni mechanizm presji zwrotnej. Na początku używaliśmy też tylko jednego domyślnego kontenera Azure Cosmos DB na każdym koncie. Działało to wystarczająco dobrze, ale z kilku powodów okazało się problematyczne:
Wczesne decyzje dotyczące API sprawiły, że udostępniliśmy klientom bezpośredni wybór klucza partycji logicznej. W rezultacie często dochodzi do kolizji między różnymi zastosowaniami produktów na popularnych kluczach partycji, takich jak user_id czy workspace_id. Ponieważ te zastosowania współdzieliły limity Azure Cosmos DB dla tych samych kluczy partycji logicznych, projekt ograniczał łączną przepustowość dostępną danemu użytkownikowi.
Zanim przeszliśmy na Habitat i Azure Cosmos DB, częstym problemem przy skalowaniu Postgresa było to, że duża liczba żądań związanych z bardzo aktywną organizacją lub kluczem — czyli jeden dzierżawca generujący nieproporcjonalnie duże obciążenie — mogła unieruchomić całą bazę danych. Włożyliśmy wiele wspólnej pracy w buforowanie tych kluczy i dodanie mechanizmu dzierżaw przy chybieniach pamięci podręcznej, aby wynikający z nich ruch nie przeciążał bazowej bazy danych.
Figure 02a · Logical partition key limits
Different clients, the same partition key
Choć udokumentowane limity rozmiaru danych i przepustowości dotyczą kluczy partycji logicznych, w rzeczywistości są egzekwowane na poziomie partycji fizycznej. W praktyce oznacza to, że partycje logiczne nie mogą osiągnąć 10 000 RU/s. Zamiast tego współdzielą limity partycji fizycznej ze wszystkimi pozostałymi kluczami partycji logicznych, które się na niej znajdują.
Pozwalało to hałaśliwym sąsiadom wpływać na wiele niepowiązanych kluczy partycji logicznych: zadania przetwarzające dane niszowych funkcji produktu zakłócały podstawowe procesy logowania i rejestracji.
Figure 02b · Noisy neighbors
Different users, the same physical partition
Limit przepustowości danej partycji fizycznej również jest zmienny i zależy od łącznej przepustowości przydzielonej kontenerowi Azure Cosmos DB. Przepustowość przydzielona kontenerowi jest rozdzielana mniej więcej równomiernie między jego partycje fizyczne.
Wraz ze wzrostem ilości danych partycje fizyczne dzielą się. Ich liczba rośnie, ale łączna przydzielona przepustowość pozostaje bez zmian. W rezultacie efektywny limit przepustowości na partycję maleje wraz ze wzrostem rozmiaru danych.
W naszych zastosowaniach wymagających największej przestrzeni dyskowej, przypominających przechowywanie obiektów blob, prowadzi to do problemu: sam przyrost danych zmniejsza dostępną przepustowość aktywnych obciążeń, nawet gdy częstotliwość żądań i wykorzystanie pozostają stabilne.
Figure 02C · Weak partitions
The limit falls below demand
A partition splits when it reaches its storage limit. Each child receives half the throughput capacity.
Osobne kontenery pomagają częściowo rozwiązać problemy limitów kluczy partycji i hałaśliwych sąsiadów. Każdemu z osobnych kontenerów można przydzielić niezależne partycje fizyczne. Wówczas tylko obciążenia w obrębie danego kontenera mogą być dla siebie hałaśliwymi sąsiadami lub nakładać się w przestrzeni kluczy partycji. Minusem jest narzut związany z tworzeniem nowych kontenerów — każdy wymaga indywidualnego planowania zasobów. Dlatego chcemy wydzielać kontenery tylko dla usług lub zastosowań, dla których taki podział ma sens.
Zaczęliśmy używać osobnych kontenerów Azure Cosmos DB dla najważniejszych nowych zastosowań wymagających silnej izolacji. Dodaliśmy też skrypt rozwiązujący problem słabych partycji przez dostosowywanie łącznej przepustowości przydzielonej każdemu kontenerowi do liczby jego partycji fizycznych. Pozwoliło nam to w dużej mierze uniknąć wszystkich dotychczasowych problemów.
Ponieważ jednak zaczęliśmy od kontenera domyślnego, wiele starszych, krytycznych zastosowań niestety pozostawało z nim związanych, dopóki nie przeprowadziliśmy migracji danych. Aby chronić krytyczne usługi, które nadal korzystały z danych we współdzielonym kontenerze domyślnym, często zalecaliśmy zespołom tworzenie osobnych kontenerów dla nowych usług lub dużych funkcji produktów. Wraz ze wzrostem skali rosło obciążenie kontenera domyślnego i przybywało incydentów produkcyjnych. Zaczęliśmy tworzyć kontenery dla każdej nowej usługi lub dużej funkcji produktu, choćby po to, by chronić kontener domyślny przed dodatkowym obciążeniem.
Uchroniło nas to przed wieloma awariami całego serwisu, ograniczając skutki do zakłóceń działania poszczególnych funkcji lub usług. Z czasem jednak odczuliśmy narzut związany z tworzeniem nowych kontenerów:
W starym współdzielonym kontenerze domyślnym wzrost ruchu jednego nowego zastosowania z 0 do 1 miliona QPS raczej nie zmieniał znacząco łącznego wykorzystania zasobów. W izolowanym kontenerze taki sam wzrost z 0 do 1 miliona QPS mógł jednak oznaczać obciążenie 1000 razy większe niż poziom bazowy, wynikający wyłącznie z wewnętrznych testów produktu.
Przy kontenerach dedykowanych zespoły musiały pamiętać o przydzieleniu im z wyprzedzeniem przepustowości oczekiwanej w dniu udostępnienia produktu. Zespoły produktowe często wdrażały funkcje na produkcję, zapominając o wcześniejszym skalowaniu. W dniu premiery trafialiśmy więc na spotkania kryzysowe, próbując w ostatniej chwili zwiększyć zasoby kontenerów.
Łączenie różnych obciążeń to jedna z podstawowych zalet systemów wielodostępnych. W tej strategii kontenerów tracimy ją na rzecz lepszej izolacji.
Figure 03 · Lack of stable aggregate growth
The same workload, different headroom
1K RU/s during load testing → 1M RU/s at launch
Z czasem monitorowanie wykorzystania kontenerów dedykowanych również zaczęło wymagać dogłębnej znajomości produktu i wiedzy o tym, kiedy ruch się zmieni lub wzrośnie.
Możemy udostępnić prosty skrypt warstwy sterowania, który zwiększa przepustowość każdego kontenera na podstawie trendów wzrostu. Bez stabilnego wzrostu łącznego obciążenia prawidłowe skalowanie wymagało jednak udziału w planowaniu zasobów praktycznie każdego zespołu produktowego w OpenAI.
Organizacyjnie to podejście się nie skalowało: zespoły odpowiedzialne za poszczególne usługi bardzo różnie podchodziły do staranności przydzielania zasobów Azure Cosmos DB.
Wraz z rozwojem organizacji coraz trudniej było radzić sobie z tymi problemami operacyjnymi. Brak jednego centralnego zespołu odpowiedzialnego za planowanie sprawiał, że wiele zespołów planowało źle. Ostatecznie wróciliśmy więc do rozwiązania wielodostępnego i w ścisłej współpracy z zespołem Azure opracowaliśmy poprawki, które rozwiązały nasze pierwotne problemy ze współdzieleniem zasobów:
Ograniczanie ruchu najpierw dla najbardziej obciążonych kluczy zapobiega zużywaniu przez hałaśliwych sąsiadów całej przepustowości danej partycji fizycznej. Zmniejsza to efekt hałaśliwego sąsiada, przez który jeden intensywnie używany klucz może zakłócić działanie całego serwisu.
Podobnie klasyfikacja żądań w ramach QoS zapobiega wykorzystywaniu całej przepustowości danej partycji fizycznej przez zadania offline, takie jak masowe wstawianie lub pobieranie danych. Nadanie żądaniom tych zadań niższego priorytetu sprawia, że są ograniczane jako pierwsze. Chroni to przed ograniczaniem interaktywnych żądań o wyższym priorytecie, które obsługują użytkowników końcowych.
Azure Cosmos DB miał już tryb bezserwerowy, który rozwiązałby nasz problem słabych partycji występujący w trybie z przydzieloną przepustowością. Miał jednak poważną wadę: skalował się wyłącznie na podstawie zajętej przestrzeni i ograniczał zużycie RU do 5 tys. RU/s na partycję fizyczną. Rozwiązałoby to problem słabych partycji, ale uniemożliwiłoby skalowanie zastosowań zużywających dużo RU.
Zespół Azure Cosmos DB opracował dla nas ulepszony tryb bezserwerowy, w którym każda partycja fizyczna może skalować się do 10 tys. RU/s. Oprócz podziałów na podstawie zajętej przestrzeni wprowadził też podziały zależne od zużycia RU, dzięki czemu nie musimy już ręcznie monitorować i skalować kontenerów.
Dzięki tym zmianom mogliśmy wrócić do jednego współdzielonego kontenera. Znacznie uprościło to wstępne i bieżące planowanie zasobów przez zespoły produktowe, przy zachowaniu izolacji obciążeń. W kolejnych miesiącach nie odnotowaliśmy żadnych incydentów związanych z przydzielaniem zasobów ani hałaśliwymi sąsiadami, mimo coraz szybszego rozwoju produktów.
Gdy firmy osiągają skalę OpenAI, usługi zarządzane stają się mniej „zarządzane”. Wiele obciążeń zaczyna odbiegać od typowych wzorców w infrastrukturze dostawcy chmury. Gdy OpenAI stało się jednym z największych użytkowników Azure Cosmos DB, jego sprawne utrzymanie wymagało innego modelu: wspólnych testów i pulpitów, bezpośrednich kanałów komunikacji inżynieryjnej oraz wspólnej odpowiedzialności za awarie występujące wyłącznie przy naszej skali. Dziś nasze największe konto Azure Cosmos DB przechowuje dziesiątki petabajtów danych i obsługuje ponad 20 mln QPS. O ile nam wiadomo, jesteśmy jedyną firmą, która wdrożyła Azure Cosmos DB na taką skalę.
Pod koniec 2024 r. awaria regionalna pokazała jasno, dlaczego ta współpraca była konieczna. Przerwa w zasilaniu dotknęła wtedy podstawowy region Azure dla jednego z naszych największych kont Azure Cosmos DB. Podczas wynikającego z tego przełączania awaryjnego odkryliśmy, że przełączenie naszych największych kont do regionu pomocniczego zajmie kilka godzin. Ani nasz zespół, ani zespół Azure Cosmos DB nie testował tego wcześniej w naszej skali.
Około miesiąca później, przełączając te konta z powrotem do pierwotnego regionu, wywołaliśmy kolejną poważną awarię. Lawina połączeń do pierwotnego regionu przeciążyła znajdującą się w nim bramę Azure Cosmos DB. Specyfika obsługi przełączeń awaryjnych przez klienta Azure Cosmos DB w trybie bramy sprawiła, że wszyscy klienci natychmiast nawiązali połączenia i wysłali żądania do pierwotnego regionu, choć wróciła do niego tylko część partycji fizycznych.
Przy każdej awarii OpenAI i Azure uruchamiały swoje procedury zarządzania incydentami, identyfikując przyczyny źródłowe i wdrażając poprawki. Awarie te były jednak objawem szerszego problemu systemowego wynikającego z tego, jak nasz zespół i zespół Azure Cosmos DB pracowały i prowadziły testy. Początkowo usługi zarządzane pozwalały nam działać szybko i skupiać się na celach biznesowych. Na dużą skalę usługi zarządzane stają się mniej zarządzane i wymagają ścisłej integracji z dostawcą na każdym poziomie — od projektowania po utrzymanie.
Dziś zespoły inżynieryjne Habitat i Azure wspólnie monitorują każdą warstwę Habitat: od naszych usług po fizyczne warstwy przechowywania danych Azure Cosmos DB. Utrzymujemy nieprodukcyjne konto Azure Cosmos DB o rozmiarach odpowiadających produkcji i testujemy na nim zmiany. Wspólnie testujemy i ulepszamy SDK oraz ustalamy najlepsze praktyki skalowania i doboru rozmiarów naszych kont Azure Cosmos DB. Mamy bezpośredni kontakt między zespołami inżynieryjnymi przez Teams, a nawet możliwość wzajemnego alarmowania dyżurnych bez przechodzenia przez kolejne poziomy wsparcia.
W ciągu ostatniego roku wspólnie wykonaliśmy wiele prac, by poprawić skalowalność warstwy sterowania i bramy Azure Cosmos DB. Dodaliśmy w bramie ochronę przed przeciążeniem i buforowanie różnych metadanych Azure Cosmos DB, aby chronić partycję metadanych. Zmniejszyliśmy obciążenie generowane przez nasze procesy habitat-service w Pythonie dzięki pulom połączeń i częstszemu wykorzystywaniu już istniejących połączeń. Tam, gdzie było to możliwe, umieszczaliśmy obciążenia w tej samej lokalizacji, a obciążenia krytyczne izolowaliśmy od zadań offline za pomocą osobnych sieci VPC.
Tak ścisła współpraca inżynieryjna miała kluczowe znaczenie dla stabilności naszego Azure Cosmos DB w ostatnim roku. Każdy wystarczająco duży lub krytyczny system wymaga takiej współpracy: przełamywania barier między obszarami odpowiedzialności i działania ponad granicami organizacyjnymi.
Jak wspomnieliśmy w części I, kierujemy się zasadą: najpierw niech działa, potem niech działa szybko, a na końcu — także tanio. Najpierw niezawodność, potem wydajność, a następnie efektywność. Habitat stał się krytyczną infrastrukturą OpenAI, więc priorytetem było zapewnienie mu niezawodności. Gdy w 2025 r. system się ustabilizował, a OpenAI rozszerzało działalność na świecie, kolejną barierą dla użytkowników i zespołów produktowych stały się opóźnienia.
Zasoby obliczeniowe i magazyny danych często znajdowały się w różnych regionach, przez co nie dało się uniknąć żądań międzyregionalnych.
Figure 04 · Improving performance at a global scale
Global traffic
Compute and storage capacity were often located in different regions, making cross-region requests unavoidable.
Lokalizacje geograficzne zasobów obliczeniowych i magazynów danych mają wyłącznie charakter poglądowy.
Opóźnienia znacznie się różniły w zależności od miejsca przechowywania danych i obsługi żądania. Aby przyspieszyć żądania do Habitat, programiści produktów stosowali dodatkową pamięć podręczną po stronie klienta, grupowali żądania, minimalizowali odczyty i zapisy w bazie oraz przenosili zadania poza ścieżkę krytyczną wszędzie tam, gdzie było to możliwe. Zaowocowało to wprawdzie ciekawymi rozwiązaniami produktowymi, bardziej odpornymi na problemy z bazą danych, ale zwiększyło też złożoność produktów i spowolniło ich rozwój. Nie udało się więc spełnić założenia Habitat: umożliwiać szybki rozwój produktów bez konieczności poznawania bazy danych.
To jeden z naszych głównych priorytetów w 2026 r. Opracowaliśmy wielowarstwową strategię optymalizacji odczytów, które stanowią ponad 90% żądań w Habitat:
Mamy wiele kont Azure Cosmos DB w różnych regionach Azure na świecie, przeznaczonych dla użytkowników z wymaganiami dotyczącymi rezydencji danych. Nawet gdy użytkowników nie obowiązują ścisłe wymagania dotyczące rezydencji danych, przechowywanie ich w pobliżu poprawia komfort korzystania z produktów dzięki mniejszym opóźnieniom.
W praktyce nie możemy stosować tego powszechnie poza przypadkami związanymi z rezydencją danych, ponieważ rozmieszczenie danych jest ograniczone dostępnością fizycznych zasobów chmury. Przy rozmiarach naszych zbiorów danych wiele regionów nie jest w stanie zapewnić ogromnej przestrzeni dyskowej potrzebnej do obsługi przypadającego na nie udziału w ruchu użytkowników.
W regionach o ograniczonych zasobach centrów danych, zasilania lub chłodzenia czasem stajemy przed prostym wyborem: zarezerwować zasoby na lokalne magazyny danych i zmniejszyć opóźnienia w Habitat albo przeznaczyć je na dodatkowe zasoby obliczeniowe do obsługi ruchu, a żądania do magazynów danych kierować dalej.
Decyzje o rozmieszczeniu danych są więc często kompromisem produktowym między: A) optymalizacją opóźnień dla różnych produktów i grup użytkowników a B) dostępną pojemnością magazynów danych i mocą obliczeniową.
W przypadku obciążeń najbardziej wrażliwych na opóźnienia, takich jak metadane uwierzytelniania potrzebne przy każdym żądaniu, skracamy czas RTT między zasobami obliczeniowymi a Habitat, używając kont Azure Cosmos DB replikowanych do wielu regionów pomocniczych. Niektóre konta replikujemy do wszystkich 40 regionów Azure, w których obsługujemy ruch, a inne — do co najmniej jednego regionu na każdym kontynencie.
Musieliśmy wcześniej, niż planowaliśmy, zbudować w Habitat niewidoczną dla klientów warstwę przenoszenia danych i routingu, aby móc przenosić zbiory danych między kontami o różnych topologiach replikacji.
Jednak i to podejście jest ostatecznie ograniczone dostępnością zasobów, dlatego stosujemy je tylko do kilku starannie wybranych zbiorów danych.
W obciążeniach Habitat zdecydowanie dominują odczyty: aplikacje stale pobierają ustawienia, stan i inne właściwości. Nieproporcjonalnie duża część ruchu dotyczy stosunkowo małego zbioru roboczego często używanych typów danych. Dzięki temu system szczególnie dobrze nadaje się do wykorzystania rozproszonej pamięci podręcznej. Pamięć podręczna poprawia wydajność infrastruktury w jednym regionie, ale przy pracy między regionami korzyści są jeszcze większe, ponieważ może „ukryć” opóźnienia komunikacji międzyregionalnej.
Zintegrowaliśmy z Habitat rozproszoną warstwę pamięci podręcznej typu lookaside. Przechowuje ona kopie ostatnio odczytywanych obiektów i krawędzi na serwerach Valkey umieszczonych w naszej infrastrukturze blisko miejsc przetwarzania żądań użytkowników. Takie pamięci podręczne zwykle używają czasu ważności (TTL), który stanowi kompromis między współczynnikiem trafień a nieaktualnością danych. Krótki TTL ogranicza nieaktualność danych, ale osłabia korzyści z buforowania: wpisy szybko wygasają i trzeba je częściej pobierać. Dłuższy TTL zwiększa współczynnik trafień, lecz oznacza dłuższe udostępnianie nieaktualnych danych, co pogarsza komfort użytkowników i powoduje nieprzewidywalne zachowanie produktu.
Inne podejście polega na jawnym unieważnianiu wpisów, gdy aplikacja wykonuje operacje zapisu na tych samych danych źródłowych. Znacznie poprawia to aktualność danych w pamięci podręcznej, ale poprawne wdrożenie bywa trudne. Nadal pozostaje też wiele przypadków brzegowych, takich jak częściowe awarie, przez które pamięć podręczna zawiera nieaktualne dane. Wdrożenia wieloregionalne potęgują te problemy: sieci międzyregionalne są mniej niezawodne, a operacje pamięci podręcznej ścigają się z replikacją bazowego magazynu danych. Uzupełnienie tego mechanizmu o TTL ogranicza maksymalny czas nieaktualności, ale nie eliminuje opisanego wyżej kompromisu.
Wybraliśmy bardziej zaawansowane podejście do unieważniania, idąc śladem systemów takich jak TAO firmy Meta. Zintegrowana pamięć podręczna Habitat wykorzystuje przechwytywanie zmian danych (CDC) za pośrednictwem kanału zmian Azure Cosmos DB. Rozsyła unieważnienia na całym świecie w ciągu kilku sekund, zapewniając wysoki współczynnik trafień i krótki czas nieaktualności danych. Usługa w naszej infrastrukturze odczytuje ten kanał zmian i unieważnia wpisy w lokalnych pamięciach podręcznych po aktualizacji danych źródłowych. Przy kolejnym odczycie następuje więc chybienie, a pamięć podręczna zostaje uzupełniona najnowszą wersją danych.
Znacznie zmniejszyliśmy też opóźnienia, eliminując narzut w samej usłudze Habitat: przepisaliśmy kod z Pythona na wydajniejszy język Rust.
Łącznie zmiany te zmniejszyły globalną medianę (p50) czasu odczytu z około 120 ms do około 10 ms — to 12-krotna poprawa od momentu, gdy na początku tego roku skupiliśmy się na wydajności.
Figure 05 · Improving performance at a global scale
p50 latency for reads
Three major design changes improved read latency
Choć pamięć podręczna znacząco obniżyła medianę opóźnień, najwolniejsze żądania nadal wymagają pobierania niebuforowanych danych z bazowego magazynu, często w odległych regionach. W miarę udostępniania kolejnych zasobów i zwiększania efektywności przechowywania planujemy zająć się bardziej podstawowym wyzwaniem: umieszczaniem danych od samego początku bliżej miejsc, z których będą odczytywane.
Niezawodne i skalowalne CDC — przekazywanie zmian w bazie danych do systemów podrzędnych — jest kluczowe nie tylko dla strategii pamięci podręcznej Habitat. Miliony zmian na sekundę, przechwytywanych w tysiącach kontenerów i milionach ich partycji, przepływają przez ponad sto tematów Kafka. Zasilają też indeksy wtórne o spójności ostatecznej, potoki przenoszenia danych, analitykę offline i inne funkcje produktów działające w czasie rzeczywistym. Wraz z globalnym rozwojem naszej infrastruktury nasłuchiwanie zmian w wierszach staje się coraz bardziej złożone. Aby zarejestrować funkcję zwrotną uruchamianą przy każdej zmianie wierszy danego typu, klient musi między innymi ustalić, które konta i kontenery Cosmos zawierają jego dane oraz które wdrożenie usługi CDC w poszczególnych regionach nasłuchuje tych kontenerów. Musi też odpowiednio skalować procesy robocze CDC, platformę Kafka oraz własny komponent odbierający dane. Nasza platforma CDC ma ukryć całą tę złożoność przed programistami produktów. Udostępnia prosty interfejs, w którym klienci rejestrują funkcję wywoływaną po zmianie ich wierszy, a my zajmujemy się całą infrastrukturą i skalowaniem.
Wewnętrzna warstwa sterowania stale pilnuje przechwytywania zmian ze wszystkich wymaganych kontenerów, wybiera regiony Cosmos do odczytu i efektywnie rozdziela pracę między tysiące procesów roboczych w wielu regionach geograficznych. Korzystanie z regionów replik i odczytów kanału zmian o niższym priorytecie chroni wrażliwy na opóźnienia ruch Habitat przed obciążeniem związanym z przechwytywaniem zmian w tle. W tak dużej infrastrukturze stale zdarzają się awarie procesów roboczych, problemy z kontenerami, błędy magazynu dzierżaw, nieprawidłowe konfiguracje i awarie systemów docelowych. Zwiększyliśmy niezawodność zarządzania cyklem życia procesów roboczych CDC i obsługi dzierżaw, odizolowaliśmy awarie poszczególnych strumieni oraz dodaliśmy zabezpieczenia zmian konfiguracji i wdrożeń. Używamy kolejek wiadomości niedostarczonych, aby trwałe błędy dostarczania nie blokowały całej kolejki. Radzimy sobie z awariami regionalnymi, przełączając procesy odczytu do innych regionów Cosmos. Przy naszej skali musieliśmy nawet zmodyfikować SDK Azure do CDC, aby usprawnić obsługę punktów kontrolnych: grupujemy je w pamięci i zapisujemy trwale tylko okresowo.
Choć sposób wykorzystania Azure Cosmos DB wewnątrz Habitat jest złożony, jego główną zaletą zawsze było ukrywanie szczegółów infrastruktury przed programistami, aby przyspieszyć rozwój produktów. Nasza platforma CDC realizuje ten sam cel.
Niewielki zespół zdołał utrzymać szybko rosnącą platformę przechowywania danych dzięki celowo wąskiemu zakresowi API, dalszemu skalowaniu usługi w Pythonie i skupieniu na najpilniejszych wyzwaniach inżynieryjnych. Najpierw skupiliśmy się na niezawodności obsługi krytycznego ruchu produktowego, potem na wydajności odczytu dla użytkowników na całym świecie, a na końcu na efektywności operacyjnej na dużą skalę. To podejście umożliwiło szybki wzrost i nowe zastosowania, a zarazem poprawę niezawodności, wydajności i tempa rozwoju produktów.
Aby skalować Habitat i przechowywanie rozmów dla kolejnego miliarda użytkowników, musimy uporać się z trudnościami replikacji danych w całej naszej infrastrukturze na jeszcze większą skalę. Wyprzedzając ten problem, budujemy własny system przechowywania danych online w skali eksabajtów, zoptymalizowany pod kątem typów danych Habitat zajmujących dużo miejsca. Rozwijany system łączy rdzeń RocksDB Cloud firmy Rockset z replikacją opartą na Raft. Implementuje ten sam model dokumentów co Azure Cosmos DB, ale wykorzystuje oddzieloną warstwę przechowywania danych i replikację fizyczną, aby zmniejszyć wymaganą pojemność pamięci flash kosztem niższej szczytowej liczby zapytań na sekundę.
Jeśli interesuje Cię praca nad takimi projektami infrastruktury danych na niespotykaną dotąd skalę, sprawdź nasze oferty pracy.


