W tym przewodniku znajdziesz sprawdzone metody, które warto wziąć pod uwagę podczas tworzenia aplikacji zgodnie z protokołem RTB.
Zarządzanie połączeniami
Utrzymywanie połączeń
Nawiązywanie nowego połączenia zwiększa opóźnienia i wymaga znacznie więcej zasobów po obu stronach niż ponowne użycie istniejącego połączenia. Zamknięcie mniejszej liczby połączeń pozwala zmniejszyć liczbę połączeń, które trzeba otworzyć ponownie.
Po pierwsze, każde nowe połączenie wymaga dodatkowej wymiany danych z siecią. Ponieważ połączenia nawiązujemy na żądanie, pierwsze żądanie dotyczące połączenia ma krótszy termin realizacji i jest bardziej narażone na przekroczenie limitu czasu niż kolejne żądania. Każde dodatkowe przekroczenie limitu czasu zwiększa współczynnik błędów, co może spowodować ograniczenie działania licytatora.
Po drugie, wiele serwerów WWW tworzy osobny wątek roboczy dla każdego nawiązanego połączenia. Oznacza to, że aby zamknąć i ponownie utworzyć połączenie, serwer musi zamknąć i odrzucić wątek, przydzielić nowy, umożliwić jego uruchomienie i utworzyć stan połączenia, zanim przetworzy prośbę. To dużo niepotrzebnych kosztów.
Unikaj zamykania połączeń
Zacznij od dostosowania zachowania połączenia. Większość domyślnych ustawień serwera jest dostosowana do środowisk z dużą liczbą klientów, z których każdy wysyła niewielką liczbę żądań. W przypadku RTB niewielka pula maszyn wysyła żądania w imieniu stosunkowo dużej liczby przeglądarek. W takich warunkach sensowne jest ponowne wykorzystywanie połączeń tak często, jak to możliwe. Zalecamy ustawienie:
- Czas bezczynności: 2,5 minuty.
- Maksymalna liczba żądań na połączenie do najwyższej możliwej wartości.
- Maksymalna liczba połączeń do najwyższej wartości, jaką może pomieścić pamięć RAM, przy jednoczesnym sprawdzaniu, czy liczba połączeń nie zbliża się zbytnio do tej wartości.
W Apache oznacza to np. ustawienie wartości KeepAliveTimeout
na 150, MaxKeepAliveRequests
na 0, a MaxClients
na wartość zależną od typu serwera.
Gdy już dostroisz zachowanie połączenia, sprawdź też, czy kod oferenta nie zamyka niepotrzebnie połączeń. Jeśli na przykład masz kod interfejsu, który w przypadku błędów lub przekroczenia limitu czasu na zapleczu zwraca domyślną odpowiedź „brak stawki”, upewnij się, że kod zwraca odpowiedź bez zamykania połączenia. Dzięki temu unikniesz sytuacji, w której w przypadku przeciążenia licytatora połączenia zaczynają się zamykać, a liczba limitów czasu rośnie, co powoduje ograniczenie działania licytatora.
Utrzymywanie równowagi w przypadku połączeń
Jeśli Autoryzowani Kupujący łączą się z serwerami Twojego licytującego przez serwer proxy, z czasem połączenia mogą stać się nierówne, ponieważ Autoryzowani Kupujący znają tylko adres IP serwera proxy i nie mogą określić, który serwer licytujący otrzymuje poszczególne informacje. Z upływem czasu, gdy Autoryzowani Kupujący nawiązują i zamykają połączenia, a serwer licytującego jest ponownie uruchamiany, liczba połączeń przypisanych do każdego z nich może się znacznie zmieniać.
Gdy niektóre połączenia są intensywnie wykorzystywane, inne otwarte połączenia mogą pozostawać w większości nieaktywne, ponieważ nie są w danym momencie potrzebne. W miarę zmian w ruchu autoryzowanych kupujących połączenia nieaktywne mogą stać się aktywne, a aktywne – nieaktywne. Jeśli połączenia są źle pogrupowane, mogą powodować nierówne obciążenie serwerów licytujących. Google stara się temu zapobiec,zamykając wszystkie połączenia po 10 tys. żądań, aby automatycznie wyrównywać na bieżąco połączenia z najczęściej używanymi adresami. Jeśli nadal widzisz nierówny rozkład ruchu w swoim środowisku, możesz podjąć dodatkowe działania:
- Jeśli używasz serwerów proxy na poziomie frontendu, wybierz backend na potrzeby każdego żądania, a nie raz na połączenie.
- Jeśli łączysz się przez serwer proxy za pomocą sprzętowego równoważnika obciążenia lub zapory sieciowej, określ maksymalną liczbę żądań na połączenie. Po nawiązaniu połączeń mapowanie jest stałe. Pamiętaj, że Google określa już górny limit 10 tys. żądań na połączenie, więc musisz podać bardziej rygorystną wartość tylko wtedy, gdy w Twoim środowisku nadal występują liczne połączenia z tymi samymi adresami. Na przykład w Apache ustaw wartość
MaxKeepAliveRequests
na 5000. - Serwery licytujących należy skonfigurować tak, aby monitorowały częstotliwość żądań i zamykały niektóre połączenia, jeśli licytujący stale wysyła zbyt wiele żądań w porównaniu z innymi licytującymi.
Eleganckie radzenie sobie z przeciążeniem
W idealnej sytuacji limity powinny być ustawione na tyle wysokie, aby licytujący mógł otrzymać wszystkie żądania, które może obsłużyć, ale nie więcej. W praktyce utrzymanie limitów na optymalnym poziomie jest trudnym zadaniem, a przeciążenia zdarzają się z różnych powodów: w czasie szczytowego obciążenia może wystąpić awaria backendu, zmiana mieszanki ruchu może spowodować konieczność przetwarzania większej ilości danych w przypadku każdego żądania lub wartość limitu może zostać ustawiona zbyt wysoko. Dlatego warto zastanowić się, jak będzie zachowywać się licytator, gdy będzie otrzymywać zbyt dużo ruchu.
Aby uwzględnić tymczasowe zmiany ruchu (do tygodnia) między regionami (zwłaszcza między Azją a USA Zachód i USA Wschód oraz USA Zachód), zalecamy 15% zapas między 7-dniowym szczytem a wartością QPS na lokalizację handlową.
Jeśli chodzi o zachowanie w przypadku dużych obciążeń, oferenci dzielą się na 3 kategorie:
- Licytujący „odpowiedz na wszystko”
Chociaż jest on łatwy do wdrożenia, to w przypadku przeciążenia działa najgorzej. Po prostu próbuje odpowiedzieć na każde otrzymane żądanie stawki, niezależnie od okoliczności, umieszczając w kolejce te, których nie można wyświetlić od razu. W efekcie często dochodzi do takiej sytuacji:
- Wraz ze wzrostem częstotliwości żądań rośnie czas oczekiwania na ich realizację, aż do momentu, gdy wszystkie żądania zaczynają się przerywać.
- Czasy oczekiwania rosną gwałtownie, gdy współczynniki objaśnień zbliżają się do szczytu
- Włącza się ograniczanie, które znacznie zmniejsza liczbę dozwolonych wezwań
- Opóźnienia zaczynają się zmniejszać, co powoduje zmniejszenie ograniczeń przepustowości
- Cykl zaczyna się od nowa.
Wykres opóźnienia w przypadku tego licytującego przypomina bardzo stromy wzór zębatkowy. Może się też zdarzyć, że żądania w kolejce spowodują, że serwer zacznie używać strony pamięci lub zrobi coś innego, co spowoduje długotrwałe spowolnienie, a opóźnienia nie znikną, dopóki nie minie okres szczytowy, co spowoduje obniżenie wskaźnika korzystania z funkcji przez cały okres szczytowy. W obu przypadkach liczba wywołań i liczba odpowiedzi jest mniejsza niż w przypadku ustawienia niższej wartości limitu.
- Licytujący „error on overload”
Ten licytujący akceptuje podświetlenia do określonej stawki, a potem zaczyna zwracać błędy w przypadku niektórych podświetleń. Można to zrobić za pomocą wewnętrznych limitów czasowych, wyłączając kolejkowanie połączeń (sterowane przez parametr
ListenBackLog
w Apache), wdrażając tryb odrzucania z prawdopodobieństwem, gdy wykorzystanie lub opóźnienia stają się zbyt wysokie, lub stosując inny mechanizm. Jeśli Google zauważy wskaźnik błędów przekraczający 15%, zaczniemy ograniczać przepustowość. W przeciwieństwie do licytującego, który „odpowiada na wszystko”, ten licytujący „ogranicza straty”, co pozwala mu natychmiast odzyskać straty, gdy współczynniki zapytań spadają.Wykres opóźnienia tego licytującego przypomina płytki ząbkowany wzór podczas przeciążeń, zlokalizowany wokół maksymalnej dopuszczalnej szybkości.
- Licytujący „brak stawki w przypadku przeciążenia”
Ten licytujący akceptuje wezwania do określonej wartości, a następnie zaczyna zwracać odpowiedzi „brak stawki” w przypadku przeciążenia. Podobnie jak w przypadku oferentowania z opcją „error on overload” można to zaimplementować na kilka sposobów. Różnica polega na tym, że do Google nie jest zwracany żaden sygnał, więc nigdy nie ograniczamy liczby wywołań. Przeciążenie jest absorbowane przez maszyny front-endowe, które przepuszczają tylko ruch, który mogą obsłużyć, do backendów.
Na wykresie opóźnienia tego licytującego widać plateau, które (sztucznie) przestaje być równoległe ze współczynnikiem żądań w godzinach szczytowych i odpowiednio spada odsetek odpowiedzi zawierających stawkę.
Zalecamy połączenie metody „Błąd w przypadku przeciążenia” z metodą „Brak stawki w przypadku przeciążenia” w ten sposób:
- Przeskaluj interfejsy i ustaw je tak, aby w przypadku przeciążenia generowały błąd. Pomoże to zmaksymalizować liczbę połączeń, na które mogą one odpowiadać.
- W przypadku błędu spowodowanego przeciążeniem maszyny front-end mogą użyć gotowej odpowiedzi „brak stawki” i wcale nie muszą analizować żądania.
- Wdrożyć kontrolę stanu backendów, tak aby w przypadku braku wystarczającej dostępnej pojemności zwracały odpowiedź „brak stawki”.
Dzięki temu można częściowo zniwelować przeciążenie i dać backendom możliwość odpowiedzi na dokładnie tyle żądań, ile mogą obsłużyć. Możesz to traktować jako „brak stawki przy przeciążeniu”, gdy maszyny front-end przechodzą w tryb „błąd przy przeciążeniu”, gdy liczba żądań jest znacznie większa niż oczekiwana.
Jeśli masz system licytujący „odpowiadaj na wszystko”, rozważ przekształcenie go w system licytujący „błąd przy przeciążeniu”, dostosowując zachowanie połączenia, aby odmawiał obsługi przeciążenia. Chociaż powoduje to więcej błędów, zmniejsza czas oczekiwania i zapobiega temu, aby serwer nie znalazł się w stanie, w którym nie może odpowiadać na żadne żądania.
Rozważ połączenie równorzędne
Innym sposobem na zmniejszenie opóźnień w sieci lub ich zmienności jest połączenie równorzędne z Google. Połączenie równorzędne pomaga zoptymalizować ścieżkę, którą ruch dociera do licytującego. Punkty końcowe połączenia pozostają takie same, ale zmieniają się łącza pośrednie. Szczegółowe informacje znajdziesz w przewodniku dotyczącym peeringu. Powód, dla którego peering jest sprawdzoną metodą, można podsumować w ten sposób:
W Internecie linki tranzytowe są wybierane głównie za pomocą „przekierowywania gorących ziemniaków”, które znajduje najbliższy link poza naszą siecią, który może przekazać pakiet do miejsca docelowego, i przekierowuje pakiet przez ten link. Gdy ruch przechodzi przez część sieci szkieletowej należącej do dostawcy, z którym mamy wiele połączeń peeringowych, wybrane połączenie będzie prawdopodobnie znajdować się w pobliżu miejsca, w którym zaczyna się pakiet. Nie mamy kontroli nad trasą, którą pakiet podąża do licytującego, więc może on zostać przekierowany do innych autonomicznych systemów (sieci) po drodze.
Gdy jednak istnieje umowa o bezpośrednim połączeniu, pakiety są zawsze wysyłane przez połączenie peeringowe. Niezależnie od tego, skąd pochodzi pakiet, przechodzi przez łącza należące do Google lub dzierżane przez tę firmę, aż do wspólnego punktu peeringu, który powinien znajdować się w pobliżu lokalizacji licytującego. Odwrotna trasa rozpoczyna się krótkim przeskokiem do sieci Google i przez resztę trasy odbywa się w tej sieci. Przeprowadzenie większości podróży w ramach infrastruktury zarządzanej przez Google zapewnia, że pakiet będzie korzystać z trasy o niskiej latencji i nie będzie narażony na duże wahania.
Przesyłanie statycznych serwerów DNS
Zalecamy, aby kupujący zawsze przesyłali do Google jeden wynik statycznego DNS i pozwalali Google na zarządzanie przesyłaniem ruchu.
Oto 2 popularne metody stosowane przez oferentów na serwerach DNS, gdy próbują one zrównoważyć obciążenie lub zarządzać dostępnością:
- Serwer DNS zwraca jeden adres lub podzbiór adresów w odpowiedzi na zapytanie, a następnie w jakiś sposób przechodzi przez te odpowiedzi.
- Serwer DNS zawsze odpowiada tym samym zestawem adresów, ale cyklicznie zmienia kolejność tych adresów w odpowiedzi.
Pierwsza technika nie sprawdza się w przypadku równoważenia obciążenia, ponieważ na wielu poziomach stosu występuje dużo buforowania, a próby obejścia buforowania prawdopodobnie nie przyniosą pożądanych wyników, ponieważ Google obciąża licytującego czasem rozwiązania DNS.
Druga metoda w ogóle nie zapewnia równoważenia obciążenia, ponieważ Google losowo wybiera adres IP z listy odpowiedzi DNS, więc kolejność w odpowiedzi nie ma znaczenia.
Jeśli licytujący wprowadzi zmianę w DNS, Google będzie stosować wartość TTL(czas życia danych) ustawioną w rekordach DNS, ale interwał odświeżania pozostaje niepewny.