Jak jsme vybudovali pipeline tracio.ai pod 30 ms
Od sběru signálů k visitor ID za méně než 30 ms: naše architektura postavená na Go, ClickHouse, Redisu a distribuovaném zpracování.
Když jsme se pustili do stavby enginu pro identifikaci zařízení tracio.ai, měli jsme jeden nekompromisní požadavek: celá pipeline — od přijetí zašifrovaných signálů po vrácení visitor ID — musí ve svém 95. percentilu proběhnout do méně než 30 milisekund. Tento článek je podrobným průvodcem architekturou, kterou jsme kvůli splnění tohoto cíle vybudovali.
Přehled pipeline
Identifikační pipeline má pět fází: dešifrování signálů, normalizace signálů, výpočet hashe, rozlišení identity a serializace odpovědi. Každá fáze je optimalizována samostatně a fáze, které mohou běžet paralelně, tak běží. Celkový rozpočet je 30 ms, přibližně rozdělený takto: dešifrování 2 ms, normalizace 3 ms, hashování 2 ms, rozlišení identity 20 ms, serializace 1 ms. Zbývající 2 ms tvoří rezervu.
Dešifrování signálů obrací šifrovaný přenos na straně klienta. Používáme kryptografické balíčky jazyka Go s hardwarovou akcelerací, které dešifrování typického 4KB payloadu zvládnou do méně než 1 ms. Normalizace parsuje JSON signálu, ověří typy a aplikuje transformace specifické pro danou platformu — například normalizuje řetězce user agent tak, aby odstranila šum závislý na konkrétní verzi.
Distribuované rozlišení identity
Rozlišení identity — určení, zda jsme toto zařízení už někdy viděli — je fází nejcitlivější na latenci. Profily zařízení ukládáme v Redisu, shardované napříč clusterem pomocí vrstvy distribuovaného směrování klíčů. Směrování rozděluje klíče podle hardwarového fingerprintu, což zajišťuje, že vyhledávání téhož zařízení vždy dopadne na stejný Redis uzel.
Naše implementace shardingu využívá virtuální uzly (150 na fyzický uzel), aby bylo rozdělení rovnoměrné. Při přidání nebo odebrání uzlu je potřeba přemapovat jen 1/N klíčů, kde N je počet uzlů. Směrovací vrstvu jsme napsali v Go s časem vyhledávání O(log n) a nulovými alokacemi.
Redis jako úložiště identit
Redis jsme zvolili před alternativami (Memcached, ScyllaDB, DynamoDB) kvůli jeho konzistentní době odezvy pod jednu milisekundu a podpoře komplexních datových struktur. Každý profil zařízení je uložen jako Redis hash s poli pro hash každé úrovně signálu, visitor ID, časové razítko posledního výskytu a metadata spolehlivosti.
Dotaz rozlišení identity je jediné volání HGETALL následované porovnáním příchozích hashů signálů s uloženými hashi. Pokud odpovídá hardwarová úroveň, vrátíme existující visitor ID s vysokou spolehlivostí. Pokud odpovídá jen softwarová úroveň, provedeme porovnání podobnosti dat na úrovni signálu, abychom určili, zda jde o totéž zařízení s aktualizovaným prohlížečem. Pokud neodpovídá nic, vygenerujeme nové visitor ID.
ClickHouse pro ukládání událostí
Každá identifikační událost se do ClickHouse zapisuje asynchronně. Používáme bufferovaný zapisovač, který dávkuje inserty — sbírá události po dobu 100 ms nebo dokud se nenahromadí 1 000 událostí, podle toho, co nastane dřív. Toto dávkování je zásadní, protože ClickHouse funguje nejlépe s velkými inserty (tisíce řádků najednou) spíše než s inserty jednotlivých řádků.
Naše schéma ClickHouse je optimalizované pro dva nejběžnější vzory dotazů: vyhledání všech událostí pro konkrétní visitor ID a agregaci událostí za časová období. Používáme engine MergeTree s primárním klíčem (visitor_id, timestamp), který poskytuje rychlé bodové vyhledávání a efektivní rozsahové skeny. Materializované pohledy udržují předagregované denní a hodinové metriky.
Jak dosáhnout méně než 30 ms ve velkém měřítku
Pro splnění našeho cíle latence byla zásadní tři architektonická rozhodnutí. Zaprvé, pipeline je plně streamovaná — zpracovávat signály začínáme dříve, než je přijato celé tělo HTTP požadavku. Zadruhé, vyhledávání v Redisu využívá connection pooling s trvalými spojeními, čímž se eliminuje režie TCP handshaku. Zatřetí, zápisy do ClickHouse jsou plně asynchronní a nikdy neblokují cestu odpovědi.
Při zátěžovém testu na 50 tisících požadavcích za sekundu je naše latence p50 12 ms, p95 24 ms a p99 38 ms. Hodnota p99 příležitostně překročí náš cíl 30 ms během rebalancování Redis clusteru, ale p95 zůstává konzistentně pod 30 ms. Zákazníkům s přísnějšími požadavky na latenci nabízíme dedikované Redis clustery, které eliminují soupeření mezi více nájemci.