Jak poprawiliśmy wydajność Moove o 59%
Większość użytkowników Moove jest daleko od naszych serwerów. Jak skróciliśmy czas do pierwszej odpowiedzi o 59% bez przenoszenia serwera i co było trudniejsze.
Odległość była naszą najwolniejszą zależnością
Większość osób, które otwierają Moove app, korzysta z sieci komórkowych w Nigerii i Indonezji, tysiące kilometrów od serwerów, które im odpowiadają. W tym miesiącu zmieniliśmy sposób, w jaki ich żądania do nas docierają, a nasz czas do pierwszego bajtu (TTFB), czyli czas między wysłaniem żądania przez telefon a nadejściem pierwszego fragmentu odpowiedzi, spadł o 59%, z 0,74 sekundy do 0,31 sekundy. Nie przenieśliśmy do tego ani jednego serwera.
W tym wpisie wyjaśniamy, dlaczego odległość kosztowała nas więcej, niż się spodziewaliśmy, co zmieniliśmy, co okazało się trudniejsze niż sama zmiana i co powiedzielibyśmy innemu zespołowi, który zamierza zrobić to samo.
Dlaczego jedna długa podróż zamienia się w trzy
Inżynierowie nazywają opóźnienie między wysłaniem czegoś a jego dotarciem opóźnieniem (latency), a czas, w którym wiadomość dociera do serwera i wraca odpowiedź, czasem podróży w obie strony (RTT). Z Lagos do naszych serwerów RTT wynosi od 200 do 300 milisekund. Samo w sobie jest to do zaakceptowania. Problem w tym, że żądanie rzadko podróżuje samo.
Zanim telefon zapyta o cokolwiek przez bezpieczne połączenie, musi przejść dwa uściski dłoni (handshake), czyli krótkie wymiany komunikatów, które ustanawiają połączenie. Handshake TCP otwiera połączenie, a handshake TLS uzgadnia klucze szyfrowania, które je chronią. Każdy z nich kosztuje pełną podróż w obie strony. Dopiero potem wychodzi samo żądanie, czyli trzecia podróż. Przy 250 milisekundach na podróż użytkownik czeka około trzech czwartych sekundy, zanim nasze serwery wykonają jakąkolwiek pracę.
W sieciach komórkowych ten koszt płaci się znacznie częściej, niż byśmy chcieli. Telefony przełączają się między stacjami bazowymi, moduły radiowe usypiają, by oszczędzać baterię, a aplikacje trafiają w tło. Każde z tych zdarzeń może zerwać połączenie, a następne żądanie musi zacząć od zimnego połączenia (cold connection), w którym nie wykonano jeszcze żadnego handshake'u, i ponownie zapłacić pełny koszt nawiązania. Ciepłe połączenie (warm connection), już otwarte i zabezpieczone, od razu przechodzi do żądania.
Nie mogliśmy przybliżyć Lagos do naszych serwerów. Mogliśmy za to przybliżyć nawiązywanie połączenia do Lagos.
Przenieśliśmy handshake, a nie serwery
Sieć dostarczania treści (CDN) zwykle opisuje się jako pamięć podręczną: zestaw lokalizacji na całym świecie, które przechowują kopie plików, by serwować je z pobliża. My użyliśmy jej w innym celu. Dla naszego API, interfejsu, z którym aplikacja rozmawia przy wszystkim, co robi, buforowanie jest całkowicie wyłączone. Każde żądanie nadal trafia do naszego źródła (origin), jak CDN nazywa serwery za nim.
Zmienia się miejsce, w którym zaczyna się połączenie. Telefon otwiera teraz połączenie z najbliższą lokalizacją brzegową (edge location), zwaną też punktem obecności (PoP): jedną z lokalizacji CDN na całym świecie, często oddaloną o dziesiątki milisekund, a nie setki. Brzeg wykonuje terminację TLS (TLS termination), co oznacza, że bezpieczne połączenie z telefonu kończy się właśnie tam. Następnie ponownie wykorzystuje pulę ciepłych połączeń z naszym źródłem, utrzymywanych dzięki keep-alive, ustawieniu, które pozostawia połączenie otwarte między żądaniami zamiast je zamykać. Dwie kosztowne podróże związane z nawiązaniem połączenia odbywają się na krótkim odcinku, a przez ocean przechodzi tylko samo żądanie, na połączeniu, które jest już ciepłe.
Trzy długie podróże zamieniają się w jedną długą i dwie krótkie. Na tym polega cały pomysł.
Resztę celowo zostawiliśmy nudną:
- Klienci niczego nie zauważyli. Aplikacja i strona zachowały te same adresy, więc nie była potrzebna nowa wersja aplikacji i nikt nie musiał niczego aktualizować.
- Zachowanie w razie awarii pozostało takie samo. Limity czasu odpowiadają dawnym, więc to, co wcześniej zawodziło, zawodzi tak samo. Nie chcieliśmy żadnych nowych rodzajów błędów.
- Brzeg zamyka bezczynne połączenia jako pierwszy. Każde połączenie ma limit bezczynności (idle timeout), czyli czas, przez jaki może leżeć nieużywane, zanim zostanie zamknięte. Gdyby nasze serwery zamknęły ciche połączenie, a brzeg nadal uważał je za otwarte, następne żądanie zakończyłoby się błędem. Ustawienie krótszego limitu na brzegu sprawia, że to on zawsze rezygnuje pierwszy i otwiera nowe połączenie.
- Nowsze protokoły dla telefonów. Telefony mogą łączyć się przez HTTP/3, najnowszą wersję protokołu żądań w sieci. Działa on na QUIC, który łączy oba handshake'i w jedną podróż w obie strony i lepiej radzi sobie z utratą pakietów, typową dla sieci komórkowych.
- Żaden region nie został pominięty. Tańsze plany sieci brzegowych często pomijają lokalizacje dokładnie w tych częściach świata, w których mieszkają nasi użytkownicy, dlatego wybraliśmy pełne pokrycie.
Co zmierzyliśmy
Mierzyliśmy z Sydney, które jest mniej więcej tak samo daleko od naszych serwerów jak Lagos, porównując starą ścieżkę z nową. Dla każdej otworzyliśmy pięć nowych zimnych połączeń, czyli przypadek, który najbardziej liczy się w sieciach komórkowych, i wzięliśmy medianę, zapisywaną także jako p50: wynik środkowy, który połowa żądań pobija, a połowa nie. Ten sam projekt włączył też kompresję skryptów naszej strony, które wcześniej były wysyłane w pełnym rozmiarze. Kompresja zmniejsza plik przed wysłaniem, a przeglądarka rozpakowuje go po otrzymaniu; użyliśmy Brotli, formatu kompresji stworzonego z myślą o sieci.
| Co mierzyliśmy | Przed | Po | Poprawa |
|---|---|---|---|
| Zakończone handshake'i TCP i TLS (p50) | 490 ms | 59 ms | 88% szybciej |
| Czas do pierwszego bajtu (p50) | 743 ms | 307 ms | 59% szybciej |
| Rozmiar skryptów strony głównej | 1,76 MB | 560 KB | 68% mniej |
Z czasu do pierwszego bajtu bierze się te 59%. Połączenie, którego nawiązanie zajmowało pół sekundy, jest teraz gotowe w mniej niż jedną dziesiątą sekundy, a pierwsza wizyta na naszej stronie pobiera teraz o dwie trzecie mniej danych, w sieciach, w których wiele osób płaci za każdy megabajt.
Te liczby pochodzą z jednego miasta i nie są globalnym testem porównawczym. Rachunek przenosi się jednak na Lagos i Dżakartę, a tam są nasi użytkownicy.
Najtrudniejsze było ustalenie, kto dzwoni
Postawienie czegoś przed serwerami jest łatwe. Zachowanie działających zabezpieczeń za tą warstwą pochłonęło większość wysiłku.
Jak większość platform płatniczych, Moove stosuje ograniczanie liczby żądań (rate limiting), czyli limit tego, ile żądań może wysłać jeden wywołujący w danym czasie, a te limity są powiązane z adresem IP wywołującego, numerem identyfikującym urządzenie w internecie. Za siecią brzegową każde żądanie przychodzi z adresu lokalizacji brzegowej, a nie użytkownika. Gdyby nic innego się nie zmieniło, wszyscy w Lagos dzieliliby jeden budżet, a jedno ruchliwe popołudnie zaczęłoby odrzucać ludzi, którzy nic złego nie zrobili.
Rozwiązanie opiera się na dwóch ideach.
Ufaj dokładnie tym warstwom, które masz. CDN jest rodzajem zwrotnego serwera proxy (reverse proxy), serwera, który stoi przed twoim i przekazuje żądania dalej. Każdy proxy dodaje adres, z którego otrzymał żądanie, do standardowego nagłówka o nazwie X-Forwarded-For, więc prawdziwy wywołujący znajduje się stałą liczbę kroków, czyli przeskoków (hops), od końca tej listy. Pomyłka w tej liczbie jest błędem w obie strony. Jeśli policzysz za mało warstw, każdy użytkownik wygląda jak jego lokalizacja brzegowa. Jeśli za dużo, zaczynasz ufać wpisowi, który wywołujący napisał sam. Dlatego uczyniliśmy tę liczbę właściwością infrastruktury, a nie kodu. Zmienia się w tej samej chwili co sieć, a nasza usługa odmawia uruchomienia, jeśli wartość nie ma sensu, zamiast zgadywać.
Pozwól brzegowi poręczyć za wywołującego i wierz tylko brzegowi. Brzeg zapisuje teraz prawdziwy adres wywołującego w każdym żądaniu i nadpisuje nim wszystko, co wysłał wywołujący. Nasze zabezpieczenia wierzą tej wartości tylko wtedy, gdy mogą zweryfikować, że żądanie naprawdę przeszło przez nasz brzeg. Każdy limit, który wcześniej opierał się na adresie sieciowym, dla ruchu z brzegu opiera się teraz na prawdziwym wywołującym. Dodaliśmy te reguły obok starych zamiast je zmieniać, więc każdy krok wdrożenia był czystym dodatkiem, który każdy mógł przejrzeć jednym czytaniem.
Błąd, który wyłapaliśmy przed wdrożeniem
Moove Profiles mogą działać na własnej domenie sprzedawcy. Żądania dla tych domen były przekazywane przez naszą stronę razem z domeną sprzedawcy, by strona wiedziała, który profil pokazać.
Nowy brzeg przed stroną usuwa tego rodzaju informacje z przychodzących żądań, i słusznie, bo odwiedzający nigdy nie powinien móc ich ustawiać. Oznaczało to jednak, że po przeniesieniu strony za brzeg każda domena sprzedawcy po cichu pokazywałaby stronę główną Moove zamiast profilu sprzedawcy.
Wyłapaliśmy to w trakcie przeglądu, najpierw przenieśliśmy ścieżkę domen sprzedawców na osobną trasę, a naszą infrastrukturę zmusiliśmy do odmowy zastosowania zmiany dla strony wszędzie tam, gdzie to przeniesienie nie zostało wykonane. Błąd, który kiedyś istniał tylko w głowie recenzenta, jest teraz czymś, co zatrzymuje build.
Ogólna lekcja: dodając warstwę, która czyści dane wejściowe, poszukaj wszystkiego, co zależało od danych, które zaraz wyczyścisz.
Jak to wdrożyliśmy
W infrastrukturze płatniczej nie ma podejścia „wdrożyć i zobaczyć”, więc zmieniliśmy najpierw staging, a potem produkcję, a kilka decyzji sprawiło, że przełączenie przebiegło spokojnie. Obniżyliśmy TTL (czas życia) naszych rekordów DNS, czyli internetowego katalogu, który zamienia nazwę taką jak moove.xyz na adres serwera. TTL to czas, przez jaki reszta internetu może przechowywać tę odpowiedź w pamięci podręcznej; skróciliśmy go z godziny do pięciu minut, pełną godzinę przed jakąkolwiek zmianą, tak aby stara odpowiedź wygasła wszędzie, a wycofanie zmian zadziałało w ciągu minut. Rekordy adresowe zachowaliśmy w postaci, którą można przełączyć w miejscu, ponieważ ich usunięcie i ponowne utworzenie zostawiłoby lukę, a luka to awaria. Każdy ryzykowny krok był osobną małą zmianą, z kolejno zapisanymi krokami jej cofnięcia.
Co powiedzielibyśmy innemu zespołowi
Jeśli twoi użytkownicy są daleko od twoich serwerów, sieć brzegowa jest warta posiadania, nawet jeśli niczego nie buforuje. Zanim przełączysz ruch:
- Wypisz wszystko, co odczytuje adres wywołującego, w tym limity żądań, listy dozwolonych, sygnały nadużyć, dzienniki audytu i reguły bezpieczeństwa. Każde z nich widzi adres brzegu, dopóki tego nie zmienisz.
- Uczyń liczbę zaufanych warstw częścią infrastruktury i odmawiaj uruchomienia, gdy jest błędna.
- Pozwól brzegowi zapisywać prawdziwego wywołującego i ufaj temu zapisowi tylko w ruchu, o którym możesz zweryfikować, że pochodzi z twojego brzegu.
- Upewnij się, że twoje serwery przyjmują ruch wyłącznie przez brzeg, i obserwuj, zanim zaczniesz wymuszać.
- Jeśli buforujesz strony, zbuduj klucz pamięci podręcznej (cache key), czyli zestaw szczegółów żądania, których pamięć podręczna używa do ustalenia, czy dwa żądania mogą dzielić jedną zapisaną odpowiedź, ze wszystkiego, od czego odpowiedź faktycznie zależy. Serwery wymieniają te szczegóły w nagłówku Vary, więc przeczytaj go, zamiast polegać na pamięci.
- Mierz przed i po, a czas handshake'u i czas do pierwszego bajtu analizuj osobno, aby odróżnić nawiązywanie połączenia od czasu pracy twoich serwerów.
Co dalej
Większość naszych stron jest spersonalizowana, więc pamięć podręczna brzegu strony na razie w większości stoi bezczynnie. Poprawa połączenia dotyczy każdej strony niezależnie od tego, a poszczególne strony mogą teraz włączać buforowanie, gdy bezpiecznie jest współdzielić ich zawartość. Buforowanie odpowiedzi API wymagałoby własnego projektu i przeglądu i to jest kolejna rzecz, której chcemy się przyjrzeć.
Pieniądze powinny poruszać się z prędkością sieci, a nie z prędkością światła przez ocean.
Jeśli budujesz na Moove, przeczytaj nasze wprowadzenie do API i SDK płatności Web3 oraz Moove Developer Program.
Napisane przez zespół inżynierów moove.xyz.
O moove.xyz
moove.xyz to globalna platforma fintech Web3 stworzona z myślą o swobodnym i bezwysiłkowym przepływie wartości. Umożliwiamy firmom i konsumentom na całym świecie wysyłanie, odbieranie, stakowanie i wymianę dowolnych kryptowalut w dowolnych blockchainach — wszystko na jednej platformie.
Jesteśmy jedną z pierwszych firm fintech Web3 na świecie, które tworzą kompletną infrastrukturę płatności krypto i zdecentralizowanych finansów, zapewniającą spójną i szeroką obsługę: dostęp do portfeli multi-chain, spersonalizowane nazwy portfeli, wymianę tokenów cross-chain, wbudowane transakcje cross-chain i zdecentralizowaną sieć społecznościowo-finansową. Do naszych kluczowych produktów należą Moove Profile, Moove Send, Moove Receive, Moove Stake, Moove Swap, Moove Rewards, Moove Discover i inne.
Nasza misja jest prosta — tworzyć i udostępniać nieograniczone zezwoleniami i bezwysiłkowe technologie finansowe dla kolejnego miliarda użytkowników Web3. Głęboko wierzymy, że przyszły przepływ pieniędzy i wartości będzie bezkosztowy, bez granic, bez zezwoleń, bezwysiłkowy i stworzony dla wszystkich — i budujemy najlepszą platformę fintech Web3, by tę przyszłość urzeczywistnić.
Twoje pieniądze. Twój ruch.






