CobbleDB: Přebudování úložiště AI vyhledávání pro nižší latenci a náklady

Decentralizovaná architektura s odděleným trvalým stavem, dávkovými aktualizacemi a vyhledáváním v době dotazu.

AutořiPerplexity Engineering

Vyhledávání nativní pro AI klade odlišné požadavky na to, jak je třeba webový obsah zpracovávat, ukládat a obsluhovat. Abychom těmto požadavkům ve velkém měřítku dostáli, přestavujeme celý zásobník od základu, abychom zlepšili výkon a snížili náklady.

Vybudovali jsme vlastní infrastrukturu pro procházení, indexování a hodnocení, modely vnoření a odvozovací motory pro zajištění efektivního zpracování a rychlé obsluhy. Nyní tento přístup rozšiřujeme na úložiště, pojivo, do kterého zpracování zapisuje a ze kterého obsluha čte.

Spravované databáze, jako je DynamoDB, poskytovaly spolehlivé úložiště, ale měli jsme omezenou kontrolu nad tím, jak jsou data ukládána a jak je obsluhováno čtení, a ceny založené na využití rostly s velikostí našeho indexu a objemem dotazů. Odděleně náš předchozí kanál propojil zpracování dokumentů přímo s obslužnou databází, což ztížilo správu aktualizací nezávisle na živém čtení.

S pomocí nového interního hejna kódovacích agentů jsme tuto vrstvu přebudovali kolem CobbleDB, horkého úložiště typu klíč-hodnota, podporovaného Pillar pro trvalý stav dokumentů a publikování a Lorry pro dávkové aktualizace. Redesign odděluje zpracování dokumentů od příjmu horkého úložiště a umožňuje nám vyladit obsluhu na naši pracovní zátěž. Produkční měření před a po ukazují zhruba 5× nižší latenci dávkového čtení a náš interní model nákladů odhaduje úsporu alespoň 20 % oproti DynamoDB.

Infrastruktura úložiště pro vyhledávání nativní pro AI

Klasický vyhledávací zásobník je vytvořen pro lidského čtenáře, nikoli pro jazykový model. Tento rozdíl mění granularitu, v jaké systém potřebuje připravovat, ukládat a obsluhovat webový obsah.

Aby bylo možné přeměnit surové HTML na obsah připravený pro model, náš zpracovatelský kanál čistí každou stránku, rozděluje ji na sémanticky soudržné pasáže, vypočítává jejich vnoření a ukládá pasáže a vnoření společně v databázi. V době dotazu vybírá obslužná pipeline text nejrelevantnější pro dotaz a předává jej modelu.

Dva odlišné pracovní postupy

Úložná vrstva proto musí podporovat dva pracovní postupy s odlišnými nároky: příjem aktualizací z pipeline zpracování a obsluhu čtení s nízkou latencí.

Na straně zápisu přicházejí nové stránky nepřetržitě, stávající stránky se mění a změny metod dělení nebo modelů vnoření mohou vyžadovat přepracování velkých částí korpusu. Úložná vrstva musí tyto aktualizace začlenit do trvalého stavu dokumentu, sledovat aktuální verze, publikovat správné reprezentace do navazujících systémů a podporovat rozsáhlé doplňování bez opakování celé pipeline ze surového procházení.

Naproti tomu čtení v době dotazu požaduje dávky klíčů stránek a načítá odpovídající text pasáže a vnoření pro hodnocení a výběr pasáží. Tato čtení leží na cestě ke konečné odpovědi, takže je třeba je dokončit rychle.

Tyto pracovní zátěže vyžadují odlišné návrhy. Zpracování těží z trvalých front a schopnosti efektivně doručovat aktualizace, opakovat selhání a uvádět stávající záznamy do aktuálního stavu ve velkém měřítku. Obsluha těží z předem spočítaných záznamů, teplých mezipamětí a rychlého dávkového čtení s minimální prací v době dotazu. Úzké propojení mezi nimi může způsobit, že práce na obnově a velké aktualizace budou konkurovat čtením citlivým na latenci.

Kde spravovaná databáze nestačí

DynamoDB nám poskytla vysoce dostupné úložiště bez zátěže spojené s provozováním distribuované databáze. Vyhrazená tabulka připravených stránek uchovávala pasáže a vnoření pro čtení v době dotazu, zatímco úplný obsah stránky a metadata byly uloženy odděleně. Jak však naše pracovní zátěž rostla, tři omezení se stala důležitými.

Za prvé, ceny založené na využití účtují každý přečtený nebo zapsaný bajt. Jediný požadavek rozhraní Search API obsahuje 100–120 klíčů odpovídajících stránkám, které potřebujeme načíst. Proces načítání dat rozdělí požadavek na menší dávky po 10–20 klíčích stránek s průměrnou velikostí položky přibližně 50 KB. Opakované čtení těchto záznamů spotřebovávalo značnou kapacitu, zatímco nepřetržité procházení a změny modelů dělení nebo vnoření generovaly více zápisů. Ekonomika také závisela na velikosti a frekvenci záznamů, nejen na počtu volání API.

Za druhé, spravovaná služba postrádá flexibilitu, kterou potřebujeme pro ladění výkonu. Operace, kterou jsme potřebovali, byla úzká: vzhledem k dávce identifikátorů stránek rychle vraťte jejich připravený obsah. Chtěli jsme kontrolu nad tím, který stroj drží oddíl, kolik paměti je věnováno jeho mezipaměti a která replika zpracovává požadavek. Tyto ovládací prvky jsou důležité, protože čtení bez mezipaměti, skok mezi zónami nebo pomalá replika mohou zpozdit celou dávku. DynamoDB spravuje své vlastní úložiště a chování replik za API. Službu jsme mohli konfigurovat, ale nemohli jsme vyladit její interní umístění, mezipaměti úložného engine nebo politiku výběru replik s ohledem na naši pracovní zátěž.

Zatřetí, zpracování bylo svázáno přímo se zápisy do horkého úložiště. Náš předchozí zpracovatelský kanál zapisoval každou připravenou stránku přímo do DynamoDB. Velká úloha přepracování se proto stala vlnou jednotlivých aktualizací horkého úložiště a zpracování se muselo přizpůsobit limitům propustnosti horkého úložiště a chování při opakování. Nemohli jsme spustit MapReduce nad celou databází během 1–2 dnů, abychom vyzkoušeli novou metodu dělení, změnili náš model vnoření nebo přidali nové pole. Neexistoval žádný nezávislý proud uložených aktualizací stránek, který by samostatná služba mohla dávkovat a doručovat do horkého úložiště svým vlastním tempem.

Řešení těchto omezení vyžadovalo víc než jen výměnu databáze. Potřebovali jsme oddělit trvalý stav dokumentu a doručování aktualizací od úložiště pro dobu dotazu a poté optimalizovat horké úložiště specificky pro rychlé dávkové čtení.

Oddělit stav dokumentu, doručování aktualizací a obsluhu

Nová architektura přiřazuje každou odpovědnost jiné komponentě. Pillar udržuje trvalý stav dokumentu a rozhoduje, co publikovat. Lorry mění tyto exporty na rozdělené dávky. CobbleDB přijímá dávky a obsluhuje připravené záznamy podle klíče stránky.

Před migrací úložiště a po ní. Pillar a Lorry oddělují trvalý stav dokumentů a doručování aktualizací od čtení v době dotazu, která zajišťuje CobbleDB.

CobbleDB: cesta laditelného čtení

CobbleDB je naše horké úložiště optimalizované pro obsluhu připravených záznamů v době dotazu. Je to distribuované úložiště typu klíč-hodnota: klíče jsou identifikátory stránek (hashované adresy URL) a hodnoty jsou zpracovanou reprezentací stránky (jejich předem rozdělené pasáže a vektorová vnoření pro každý úsek).

Důležitou změnou je naše kontrola nad konfigurací úložiště.

Data jsou rozdělena na oddíly a rozložena mezi datové uzly. Tento distribuovaný formát zajišťuje, že databáze může horizontálně škálovat tak, aby pojala obrovský korpus zpracovaných stránek. Každý oddíl má tři repliky na třech různých uzlech; pokud se jedna kopie stane nedostupnou, ostatní mohou pokračovat v obsluze čtení.

Každému datovému uzlu je přiřazena podmnožina oddílů. Uzel ukládá svá data pomocí RocksDB, open-source vestavěného úložiště typu klíč-hodnota, které se skvěle hodí pro pracovní zátěže s náročným čtením napájené dávkovým příjmem.

Uložená data v mezipaměti lze obsluhovat z paměti, zatímco čtení bez mezipaměti využívá lokální úložiště NVMe. To nám dává přímý způsob, jak vyladit rovnováhu mezi pamětí a diskem pro pozorovanou pracovní zátěž, namísto přijímání interní politiky mezipaměti spravované služby.

Bezstavový směrovač dotazů hashuje klíče do oddílů a paralelně směruje požadavky na uzly. Architektura také zahrnuje metadatovou službu založenou na PostgreSQL a trvalé objektové úložiště.

Tento design podporuje vzorce čtení, které naše pracovní zátěž vyžaduje. Poté, co dotaz projde vyhledáváním a hodnocením za účelem získání kandidátní sady relevantních stránek, horké úložiště je hromadně dotazováno na obsah těchto stránek. Tyto požadavky na čtení dorazí do routeru a jsou směrovány do odpovídajícího datového uzlu. Router upřednostňuje odesílání požadavků do uzlu ve stejné zóně dostupnosti, aby se, pokud možno, minimalizovala latence mezi zónami. Každý datový uzel může načíst obsah stránky pomocí MultiGet, dávkového rozhraní API RocksDB navrženého pro načítání několika klíčů současně s nízkou latencí. Pokud uzel reaguje pomalu, systém může také dotazovat jinou repliku oddílu na jiném uzlu, což zlepšuje chvostovou latenci.

Můžeme se také vyhnout implementaci běžných funkcí, které náš pracovní postup nevyžaduje. Například většina databází nabízí záruky konzistence a podporu transakcí, ale naše horké úložiště transakce ani synchronizované repliky nevyžaduje. Je přijatelné, pokud existuje krátká prodleva mezi zápisem dokumentu a jeho zpřístupněním pro čtení, a je také v pořádku, když jedna replika přijímá data rychleji než jiná. Vynechání funkcí, které nepotřebujeme, snižuje režii a náklady našeho systému.

Bezstavový router seskupuje klíče stránek podle oddílu a čte repliky paralelně. Pomalá čtení lze odeslat na jinou repliku; první odpověď pro každý oddíl se sloučí do výsledku.

Pillar a Lorry: cesta odolného zápisu

Samostatná úložná vrstva sleduje vše, co procházíme a zpracováváme, slučuje aktualizace do verzovaného stavu dokumentu a publikuje potřebné reprezentace do CobbleDB a vyhledávacího indexu. Pro tento účel jsme vytvořili Pillar, naši vrstvu stavu dokumentů a publikování.

Pillar je podporován YTsaurus. Ukládá komponenty stránek (jako jsou metadata, úseky a vnoření) v samostatných rodinách tabulek YTsaurus. Sleduje také verze úseků a vnoření, což umožňuje koexistenci více reprezentací bez vzájemného přepsání, takže základní modely se mohou měnit. Vzhledem k tomu, že Pillar a YTsaurus používají levné HDD úložiště, můžeme uložit mnohem více dokumentů, než jsme dříve ukládali v DynamoDB.

Pillar sleduje podmnožiny neboli skupiny stránek definované politikou, jako jsou čerstvé stránky nebo vysoce hodnotné stránky. Členství v podmnožině určuje, které aktualizace je třeba předat navazujícím komponentám, jako je CobbleDB. CobbleDB používá dražší úložiště NVMe, takže exportujeme pouze dokumenty, které skutečně chceme použít ve výsledcích vyhledávání. Když je aktualizován obsah stránky nebo je-li přidána do podmnožiny či z ní odstraněna, Pillar odpovídá za zařazení odpovídající exportní práce do fronty.

Atomové transakce YTsaurus pokrývají stav, záměry exportu a spotřebovaný vstup s cílem zajistit správnost. Aby byla transakce úspěšně dokončena, musí systém aktualizovat trvalý stav, označit vstup jako zaúčtovaný a zařadit do fronty všechny požadované exporty; pokud se transakce přeruší, na žádné vrstvě nedojde k žádné změně.

CobbleDB očekává, že aktualizace budou dávkovány podle oddílu. Exporty Pillar jsou převedeny do tohoto formátu bezstavovou službou, kterou nazýváme Lorry. Jejím jediným úkolem je číst záznamy exportu z trvalé fronty zarovnané na oddíly, kumulovat je do dávkových souborů pro jednotlivé oddíly a předávat je do CobbleDB.

Mezi Lorry a Cobble další protokol odděluje datovou rovinu (S3) od roviny řízení. Lorry zapisuje dávku do S3 pod jedinečným identifikátorem a registruje dávku v rámci Cobble. Repliky oddílu Cobble nezávisle dotazují dávku nebo API roviny řízení na další dávku S3, kterou je třeba přijmout, a asynchronně aplikují aktualizace dávky v chronologickém pořadí. To znamená, že pomalý nebo se zotavující uzel může dohnat zpoždění svým vlastním tempem, namísto zpomalování zbytku systému.

Pillar publikuje trvalé exporty a Lorry zapisuje dělené dávky do S3 a registruje je v control plane. Každá replika CobbleDB nezávisle objevuje a ingestuje své dávky v chronologickém pořadí.

Nová cesta odděluje okamžitou transakci zpracování od příjmu horkého úložiště a poskytuje opakovatelnou cestu pro přírůstkové aktualizace a hromadné rekonstrukce. Toto rozdělení práce zajišťuje, že aktualizace trvalého stavu dokumentu nenarušují latenci cesty čtení.

Ověření

5× rychlejší

Po migrování horkého úložiště klesla mediánová latence dávkového čtení z 31,4 ms na 5,60 ms. P90 klesla z 56,7 ms na 9,77 ms a p99 klesla z 123 ms na 24,2 ms. Poměry se pohybují od 5,08× do 5,80× se zlepšením u typických požadavků i u chvostu.

Latence dávkového čtení v produkci před migrací a po ní: P50 klesla z 31,4 na 5,60 ms, P90 z 56,7 na 9,77 ms a P99 z 123 na 24,2 ms. Jde o latence čtení z hot store z živého provozu v různých časech, nikoli o spárované požadavky ani o latenci odpovědi end-to-end.

Tato čísla jsou odvozena z dávkového čtení, kde každý dotaz požaduje přibližně 10–15 klíčů. Průměrná velikost položky datového zatížení je 50 KB. U obou systémů byly dotazy prováděny rychlostí přibližně 200 tisíc rps, přičemž jsme prováděli i zátěžové testy do 500 tisíc rps bez zhoršení výkonu.

Aby byl izolován přínos CobbleDB, všechny ostatní aspekty nastavení byly zachovány konstantní. Přesto se jedná o observační porovnání před a po; systémy obsluhovaly živý produkční provoz v různou dobu namísto stejných požadavků za kontrolovaných podmínek. Proto jsme také změřili výkon obou systémů na syntetickém benchmarku. Abychom se přiblížili požadavkům v produkci, dotazovali jsme se obou pomocí dávek 10–15 klíčů a hodnot v rozsahu 100 B až 10 KiB.

Syntetická latence dávkového čtení pro dávky o 10 a 15 klíčích napříč velikostmi hodnot od 100 B do 100 KiB. Každý bod je průměr latence P50, P90 nebo P99 na úrovni spuštění ze dvou běhů pro každý backend a konfiguraci.

DynamoDB v tomto kontextu bojuje, protože její spravovaná cesta čtení nemůže být laděna pro dávkové čtení této velikosti. Naproti tomu CobbleDB je optimalizována přesně pro toto nastavení, protože spravujeme umístění, ukládání do mezipaměti a ovládací prvky replik. Seskupuje klíče podle oddílu, provádí čtení paralelně proti replikám oddílu a může zajistit pomalé čtení proti jiným replikám. Naše výsledky ukazují, že tato designová rozhodnutí se promítají do rychlejšího čtení v produkčním prostředí.

o 20 % levnější

CobbleDB je také výrazně levnější než DynamoDB. Kromě úložiště účtuje DynamoDB také každý přečtený nebo zapsaný bajt. V měřítku našeho korpusu a obslužného provozu se tyto poplatky založené na využití stávají podstatnou částí celkových nákladů. Nepřetržité aktualizace spotřebovávají kapacitu zápisu, zatímco každá dávka připravených stránek načtených pro dotaz spotřebovává kapacitu čtení.

Úspory nákladů jsme vypočítali na základě interních odhadů velikosti úložiště, jednotek kapacity zápisu a jednotek kapacity čtení. Při každé úrovni závazku je CobbleDB nejméně o 20 % levnější než DynamoDB. Skutečné úspory jsou pravděpodobně ještě vyšší, protože tyto výsledky neberou v úvahu pravděpodobné úspory na zálohování díky kompresi.

Jak nám roj AI agentů pomohl s jeho vytvořením

Vybudování CobbleDB a související infrastruktury nebylo vůbec triviální. Samotná jádrová databáze CobbleDB má přibližně 40 000 řádků v jazyce Rust a migrace si rovněž vyžádala rozsáhlou práci k ověření správnosti a chování při selhání v produkčním měřítku, integraci CobbleDB s cestami zpracování i obsluhy a bezpečný přechod živého provozu.

Tuto snahu jsme urychlili pomocí interního systému, který provozuje hejno kódovacích agentů. Systém je navržen tak, aby byl vysoce perzistentní; uchovává cíle projektu, aktivní rizika, historii repozitáře a předchozí rozhodnutí napříč relacemi.

K projektu CobbleDB jsme přiřadili kolektiv takových proaktivních agentů, kteří fungují nepřetržitě. Jejich prvním úkolem bylo vytvořit přesný model práce, která již probíhá. Zkontrolovali kanál projektu a aktivní vlákna, propojili nevyřízené položky s jejich pull requesty a vlastníky a vytvořili aktuální přehled o stavu systému a dalších akcích. Odtud působili v několika oblastech inženýrské smyčky:

  • Zkontrolován kód a změny infrastruktury.
  • Odhaleny blokátory, které bylo snadné přehlédnout, včetně nebezpečných předpokladů obnovy, zastaralých odkazů na sestavení a konfigurací, které by za běhu selhaly.
  • Připravené cílené opravy, testy, změny sledování a provozní dokumentace.
  • Sledováno CI, revize a brány infrastruktury při zachování výslovného vlastnictví produkčních akcí lidmi.
  • Sledovaná uvedení do provozu a cvičení zálohování nebo obnovy pak porovnala pozorovaný stav systému s kritérii úspěšnosti uvedení do provozu.
  • Udržovány výstupy projektu, aby inženýři viděli, co bylo odesláno, co zůstalo zablokováno a kde je stále vyžadován lidský úsudek.

Inženýři stále nastavovali architekturu, kontrolovali důsledné změny a schvalovali produkční operace. Agenti zvládli velkou část nepřetržité kontroly a následných kroků mezi těmito rozhodnutími. To umožnilo malému týmu efektivně vést projekt – základní infrastrukturu CobbleDB vyvinuli dva lidští inženýři a stovky AI agentů za dva měsíce.

Závěr

CobbleDB není databáze pro obecné účely; je vytvořena pro specifickou pracovní zátěž opakovaného dávkového čtení připravených záznamů stránek, napájená asynchronním zpracovatelským kanálem. Oddělením trvalého stavu, doručování aktualizací a obsluhy v době dotazu můžeme nezávislé optimalizovat každou zodpovědnost, aniž bychom nutili jediné databázové rozhraní pojmout všechny nebo přenášeli údržbu na živou cestu dotazu. Migrace na CobbleDB výrazně zlepšila mediánovou i chvostovou latenci a zároveň snížila náklady na infrastrukturu.

Jak budeme i nadále interně vyvíjet infrastrukturu nativní pro AI, můžeme optimalizovat výkon a náklady způsobem, který není možný, když jsme vázáni na implementace a cenové modely třetích stran. Výkonní AI agenti posouvají kalkulaci ještě více ve prospěch budování namísto nákupu.

Plánujeme brzy uvolnit CobbleDB jako open-source, aby ostatní týmy budující vyhledávací systémy nativní pro AI mohly těžit ze stejné úložné vrstvy, díky které je vyhledávání v našich produktech rychlejší a levnější.

Citace

BibTeX
@online{perplexity2026Cobbledb,
  author = {{Perplexity} and Alexandr Yarats and Marco Montagna and Aleksei Bykov and Alexey Gusev and Ali Abdullaev and Amir Mokhammed-Ali and Andrey Zosimov and Denis Kremko and Gleb Vishnevsky and Micah Zirn and Phi Hoang and Claire Beamer and Jeremy Yang and Jerry Ma and Denis Yarats},
  title = {{CobbleDB: Rebuilding AI search storage for lower latency and cost}},
  date = {2026-09-14},
  url = {https://www.perplexity.ai/hub/blog/cobbledb}
}