Architekt systemówArchitekt systemów

Zaprojektuj wymianę reklam programatycznych w czasie rzeczywistym na skalę planetarną, która organizuje podejmowanie decyzji aukcyjnych poniżej 100 ms w różnych platformach po stronie popytu, egzekwuje tempo budżetu dla każdej kampanii bez zdalnego blokowania, wykrywa oszustwa kliknięć za pomocą odcisków behawioralnych w ścieżce żądania oraz uzgadnia różnice w fakturowaniu za pomocą niezbywalnych zapisów księgowych, zachowując jednocześnie regionalną suwerenność danych w celu zgodności z RODO.

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź na pytanie

Historia pytania

Wczesne serwowanie reklam opierało się na statycznych konfiguracjach spadku, w których wydawcy priorytetowo traktowali partnerów popytu sekwencyjnie, tworząc wodospady opóźnień i utratę przychodów. Przejście na Header Bidding i protokoły OpenRTB zdemokratyzowało dostęp do zasobów, ale wprowadziło ekstremalne ograniczenia inżynieryjne: aukcje muszą być zakończone w ciągu 100 ms, aby zapobiec opuszczeniu strony, egzekwowanie budżetu musi zapobiegać przekroczeniu wydatków w tysiącach węzłów brzegowych, a wykrywanie oszustw musi odbywać się w linii bez dodatkowych skoków sieciowych. To pytanie pojawiło się z potrzeby zastąpienia scentralizowanych potoków Apache Kafka architekturami obliczeniowymi na krawędzi, które są w stanie podejmować autonomiczne decyzje finansowe przy zachowaniu ścisłej audytowalności i wymogów dotyczących rezydencji danych.

Problem

Tradycyjne architektury opierają się na scentralizowanych klastrach PostgreSQL lub Redis dla liczników budżetu i magazynów cech, co tworzy opóźnienia międzyregionowe, które naruszają SLA 100 ms podczas szczytów ruchu, takich jak Czarny Piątek. Naiwne optymistyczne blokowanie na dekrementach budżetu powoduje stada gromadnych ofert i utracone oferty, podczas gdy asynchroniczne wykrywanie oszustw pozwala robotom wyczerpać budżety kampanii, zanim uruchomią mechanizmy wykrywania. Ponadto uzgadnianie fakturowania w różnych DSP (Platformy po stronie popytu) cierpi na problemy z partytowaniem sieci, gdzie piksele wyświetleń są uruchamiane, ale wiadomości potwierdzające są gubione, co prowadzi do utraty przychodów lub podwójnego obciążania, które niszczy zaufanie reklamodawców.

Rozwiązanie

Zainstaluj sidecary Envoy Proxy z filtrami WebAssembly w brzegowych PoP (Punkty Obecności), aby uruchomić logikę aukcji w ciągu 10 ms od użytkowników końcowych. Zaimplementuj liczniki CRDT (Conflict-free Replicated Data Type) korzystające z Redis z protokół Gossip do tempa budżetu, co pozwala węzłom brzegowym na lokalne akceptowanie ofert przy zapewnieniu globalnej spójności budżetu poprzez ostateczną konwergencję. Osadź lekkie modele TensorFlow Lite w warstwie brzegowej do wykrywania botów w czasie rzeczywistym przy użyciu odcisków behawioralnych, takich jak wzorce prędkości myszy i entropia nawigacji. Użyj Apache Pulsar z Geo-Replikacją i BookKeeper dla niezbywalnych logów audytowych, zapewniając semantykę dokładnie raz przy użyciu Producentów Idempotentnych i Okien Odwzorowania. Aby zapewnić zgodność z RODO, wdrożenie kontroli K-Anonimowości i routingu rezydencji danych za pomocą Anycast DNS z uwzględnieniem EDNS Client Subnet.

Sytuacja z życia

Podczas wydarzenia Black Friday 2023 nasza platforma doświadczyła 40-krotnego wzrostu ruchu, co spowodowało saturację centralnego magazynu budżetowego Redis w us-east-1, co sprawiło, że 12% aukcji wygasło i zagroziło utracie potencjalnych przychodów w wysokości 2 milionów dolarów. Zespół inżynieryjny stanął przed kluczową decyzją architektoniczną: utrzymać silną spójność i zaakceptować naruszenia opóźnienia, czy priorytetyzować szybkość i ryzykować katastrofalne przekroczenie budżetu.

Rozwiązanie A: Klaster Redis z Redlock

Rozważaliśmy wdrożenie algorytmów Redlock w pięciu niezależnych węzłach głównych Redis, aby egzekwować ścisłą spójność budżetu. To podejście teoretycznie gwarantowałoby, że żadna kampania nie przekroczy swojego dziennego limitu, wymuszając większościową zgodę na każdy dekrement. Jednak czas round-trip między węzłami brzegowymi a klastrem Redis wynosił średnio 35 ms, a pod obciążeniem kontencja blokad powodowała, że 8% żądań próbuje kilka razy, przekraczając nasze SLA 100 ms. Chociaż zapewniało to doskonałą dokładność budżetu, niedopuszczalne opóźnienie i złożoność operacyjna sprawiły, że było to niewłaściwe dla aukcji w czasie rzeczywistym.

Rozwiązanie B: Lokalna pamięć podręczna w pamięci z asynchroniczną synchronizacją

Alternatywnie oceniliśmy umożliwienie każdemu węzłowi brzegowemu utrzymania wyłącznie lokalnych liczników budżetowych, które synchronizowałyby się asynchronicznie z centralnym rejestrem co 30 sekund. To osiągnęło opóźnienie aukcji poniżej 5 ms i poradziło sobie ze szczytem ruchu bez problemów z zewnętrznymi zależnościami. Niestety w czasie wzrostu wiele węzłów brzegowych przekroczyło wartościowe kampanie o 800 000 dolarów, zanim synchronizacja się odbyła, co spowodowało problemy z zaufaniem reklamodawców oraz kary umowne. Prędkość była optymalna, ale ryzyko finansowe było katastrofalne dla systemu sąsiadującego z płatnościami.

Rozwiązanie C: Hybrydowa architektura CRDT z hierarchicznym tempem

Wdrożyliśmy podejście hybrydowe, korzystając z CRDT Delta-State w Redis na trzech poziomach: brzegowym, regionalnym i globalnym. Węzły brzegowe akceptują oferty przy użyciu lokalnych PN-Liczników (Liczników Pozytywnych-Negatywnych) z konserwatywnymi lokalnymi progami ustawionymi na 95% globalnego budżetu. Gdy lokalne budżety się wyczerpią, węzły pytają regionalne pamięci podręczne z Read-Your-Writes spójnością. Pozostałe 5% bufora jest zarządzane przez globalny rejestr za pomocą CRDT podczas synchronizacji gossip. W celu wykrycia oszustw wdrożyliśmy modele TinyML na węzłach brzegowych, które były przeszkolone do wykrywania wzorców botów bez wywołań sieciowych. Wybraliśmy to rozwiązanie, ponieważ zapewniało 99,9% dokładności budżetu przy zachowaniu p99 opóźnienia wynoszącego 45 ms.

Wynik

Platforma przetwarzała 12 milionów zapytań na sekundę podczas szczytu, z zerowym przekroczeniem budżetu w kampaniach z ograniczeniami. Opóźnienie wykrywania oszustw spadło z 150 ms do 8 ms, blokując 3,4% złośliwego ruchu przed złożeniem oferty. Uzgodnienie CRDT osiągnęło ostateczną spójność w ciągu 200 ms w różnych regionach, co dobrze mieściło się w oknie uzgodnień fakturowania, a zgodność z RODO była zapewniona dzięki lokalnemu przetwarzaniu danych na krawędzi.

Co często umyka kandydatom

Jak zapobiegasz przekroczeniu budżetu, gdy wiele węzłów brzegowych równocześnie dekrementuje ten sam budżet kampanii bez nabywania globalnych blokad?

Większość kandydatów sugeruje zdalne blokady lub atomowe operacje dekrementacyjne w Redis, które zawodzą w przypadku partycji sieciowych lub dużych opóźnień. Prawidłowe podejście wykorzystuje PN-Liczniki (Liczniki Pozytywne-Negatywne) implementowane jako CRDT. Każdy węzeł brzegowy utrzymuje lokalny licznik wydatków i licznik zwrotów. Kiedy węzeł akceptuje ofertę, zwiększa swój lokalny licznik wydatków. Podczas synchronizacji gossip węzły wymieniają swoje stany liczników i łączą je, używając operacji Join (faworyzując maksymalną wartość każdego komponentu licznika). Aby zapobiec tymczasowemu przekroczeniu budżetu, lokalnie stosuj algorytmy Token Bucket z konserwatywnymi limitami. Jeśli suma wszystkich lokalnych wydatków zbliża się do globalnego limitu, węzły wchodzą w tryb „oszczędnościowy”, w którym pytają regionalnego koordynatora o pozostałe 5% budżetu. Zapewnia to, że chociaż teoretyczne tymczasowe przekroczenie wynoszące 1-2% jest możliwe podczas partycji, system nigdy nie przekroczy 105% budżetu, co jest akceptowalne dla SLA reklamy cyfrowej, w przeciwieństwie do tradycyjnych systemów bankowych.

Jak zapewniasz fakturowanie dokładnie raz, gdy piksele śledzenia wyświetleń uruchamiają się z przeglądarek użytkowników, ale awarie sieci uniemożliwiają dostarczenie potwierdzeń do serwera aukcji?

Kandydaci często proponują idempotencję Kafka lub aktualizacje bazy danych, pomijając problem end-to-end. Rozwiązanie wymaga generowania Idempotent Keys na krawędzi przy użyciu UUIDv7 (uporządkowane według czasu), osadzonych w znaczniku twórczym. Kiedy przeglądarka uruchamia piksel wyświetlenia, dołącza ten klucz. Warstwa Nginx na krawędzi zapisuje do Apache Pulsar z włączoną Deduplication w oknie 24-godzinnym. Przechowywanie BookKeeper Pulsara gwarantuje, że duplikaty zapisów z tym samym kluczem są odrzucane na poziomie brokera, a nie na poziomie konsumenta. Dodatkowo wdrażaj dostawę At-Least-Once do tymczasowej tabeli BigQuery podzielonej według klucza idempotentnego, za pomocą instrukcji MERGE, które deduplikują podczas procesu ETL. Ta podwójna ochrona zapewnia, że nawet jeśli przeglądarka ponowi uruchomienie piksela 50 razy z powodu błędów 500, reklamodawca zostanie obciążony dokładnie raz.

Jak radzisz sobie z przesunięciem zegara między geograficznie rozproszonymi oferentami, gdy ustalasz zwycięzców aukcji na podstawie czasu reakcji?

To jest subtelne. Kandydaci często sugerują NTP lub TrueTime (z Spanner), ale dodają opóźnienia. Prawidłowa architektura eliminuje zależności od zewnętrznych zegarów z logiki aukcji. Zamiast porównywać znaczniki czasu z odpowiedzi DSP, użyj Zegarów Logicznych (Znaczniki Czasu Lamporta) lub po prostu kolejek FIFO na krawędzi. Gdy aukcja się rozpoczyna, węzeł brzegowy startuje Timer o wysokiej rozdzielczości (Performance.now() w V8 lub C++ chrono). Odpowiedzi DSP są klasyfikowane według kolejności przybycia na interfejsie sieciowym, a nie na podstawie nagłówków znaczników czasu. Aby poradzić sobie z opóźnieniami, wdrażaj Dostosowywany Czas Oczekiwania za pomocą Dostosowywanych Algorytmów Oczekiwania, które dostosowują się na podstawie historycznego p99 opóźnienia dla każdego DSP. Do analizy po fakcie oraz rozwiązywania sporów fakturowych rejestruj odczyt Monotonic Clock i znacznik czasu UTC z zakresami niepewności, przechowując je w CockroachDB, który automatycznie obsługuje okna niepewności. Zapewnia to sprawiedliwość, nawet gdy zegar serwera jednego DSP jest o 200 ms do przodu od innego.