CobbleDB: Az AI-keresőtárhely újjáépítése az alacsonyabb késleltetésért és költségért

Decoupled architektúra különálló tartós állapottal, kötegelt frissítésekkel és lekérdezéskori visszakereséssel.

SzerzőkPerplexity Engineering

A mesterségesintelligencia-alapú keresés eltérő elvárásokat támaszt a webes tartalmak feldolgozásával, tárolásával és kiszolgálásával szemben. Annak érdekében, hogy ezeknek az igényeknek nagy skálán megfeleljünk, a teljes stacket a semmiből újjáépítettük a teljesítmény javítása és a költségek csökkentése érdekében.

Felépítettük saját feltérképezési, indexelési és rangsorolási infrastruktúránkat, beágyazási modelljeinket és inferencia-motorjainkat a hatékony feldolgozás és a gyors kiszolgálás érdekében. Ezt a megközelítést most kiterjesztjük a tárolásra is, amely a kötőszövetként szolgál a feldolgozás írásai és a kiszolgálás olvasásai között.

Az olyan felügyelt adatbázisok, mint a DynamoDB, megbízható tárat biztosítottak, de korlátozott felügyelettel rendelkeztünk az adatok tárolása és az olvasások kiszolgálása felett, a használatalapú árazás pedig az index méretével és a lekérdezések mennyiségével párhuzamosan növekedett. Emellett a korábbi pipeline-unk közvetlenül összekapcsolta a dokumentumfeldolgozást az éles adatbázissal, ami megnehezítette a frissítések független kezelését az élő olvasásoktól.

Egy új belső kódolásiügynök-raj segítségével újjáépítettük ezt a réteget a CobbleDB köré, amely egy kulcs-érték alapú forró tárhely, és a tartós dokumentumállapotért és közzétételért felelős Pillar, valamint a kötegelt frissítésekért felelős Lorry támogatását élvezi. Az újratervezés elválasztja a dokumentumfeldolgozást a forró tárhelyre történő betöltéstől, és lehetővé teszi számunkra, hogy a kiszolgálást a munkaterhelésünkhöz hangoljuk. Az éles előtte-utána mérések nagyságrendileg 5× alacsonyabb kötegelt olvasási késleltetést mutatnak, belső költségmodellünk pedig legalább 20%-os megtakarítást becsül a DynamoDB-hez képest.

Infrastruktúra a mesterségesintelligencia-alapú kereséshez

A klasszikus keresési stack emberi olvasó számára készült, nem pedig nyelvi modell számára. Ez a különbség megváltoztatja azt a részletességi szintet, amelyen a rendszernek elő kell készítenie, tárolnia és ki kell szolgálnia a webes tartalmakat.

Annak érdekében, hogy a nyers HTML-ből modellkész tartalom legyen, feldolgozó pipeline-unk megtisztítja az egyes oldalakat, szemantikailag koherens részekre osztja őket, kiszámítja a beágyazásaikat, majd a szövegrészleteket és a beágyazásokat együtt tárolja az adatbázisban. Lekérdezéskor a kiszolgáló pipeline kiválasztja a lekérdezéshez leginkább releváns szöveget, és átadja a modellnek.

Két különböző munkafolyamat

A tárolási rétegnek ezért két, eltérő igényű munkafolyamatot kell támogatnia: a feldolgozó pipeline-ból érkező frissítések betöltését és az alacsony késleltetésű olvasások kiszolgálását.

Írási oldalon folyamatosan érkeznek új oldalak, a meglévő oldalak megváltoznak, a chunkolási módszerek vagy beágyazási modellek változásai pedig megkövetelhetik a korpusz nagy részeinek újrafeldolgozását. A tárolási rétegnek be kell építenie ezeket a frissítéseket a tartós dokumentumállapotba, nyomon kell követnie az aktuális verziókat, közzé kell tennie a megfelelő reprezentációkat a lefelé áramló (downstream) rendszerekben, és támogatnia kell a nagy háttérbetöltéseket (backfills) anélkül, hogy megismételné a teljes pipeline-t a nyers crawl-tól kezdve.

Ezzel szemben a lekérdezéskori olvasások oldalazási kulcsok kötegeit kérik le, és kinyerik a megfelelő szövegrészleteket és beágyazásokat a rangsoroláshoz és a szövegrészlet-kiválasztáshoz. Ezek az olvasások a végső váshoz vezető úton helyezkednek el, ezért gyorsan kell befejeződniük.

Ezek a munkaterhelések eltérő kialakítást igényelnek. A feldolgozás a tartós sorokból, valamint a frissítések hatékony kézbesítésének, a hibák újrapróbálásának, valamint a meglévő rekordok nagy skálán történő naprakésszé tételének képességéből profitál. A kiszolgálás az előre kiszámított rekordokból, a meleg cache-ekből és a minimális lekérdezési munkával járó, gyors kötegelt olvasásokból profitál. A kettő közötti szoros csatolás miatt a helyreállítási munkák és a nagy frissítések versenyezhetnek a késleltetésre érzékeny olvasásokkal.

Ahol a felügyelt adatbázis kevésnek bizonyul

A DynamoDB nagy rendelkezésre állású tárat biztosított anélkül, hogy egy elosztott adatbázis üzemeltetésének terhét viselnünk kellett volna. Egy dedikált előkészített-oldalak tábla tárolta a lekérdezéskori olvasásokhoz tartozó szövegrészleteket és beágyazásokat, míg a teljes oldalakat és a metaadatokat külön tároltuk. Ahogy azonban a munkaterhelésünk nőtt, három korlát vált fontossá.

Először is, a használatalapú árazás minden egyes leolvasott vagy megírt bájtot számlál. Egyetlen Search API-kérés 100-120 kulcsot tartalmaz, amelyek a lekérni kívánt oldalaknak felelnek meg. Az adatlekérdezési folyamat a kérést 10-20 oldalkulcsból álló kisebb kötegekre bontja, az átlagos elemméret pedig körülbelül 50 KB. Ezen rekordok ismételt olvasása jelentős kapacitást emésztett fel, míg a folyamatos crawlok, valamint a chunkolási vagy beágyazási modellek módosításai több írást generáltak. A gazdaságosság a rekordok méretétől és gyakoriságától is függött, nem csak az API-hívások számától.

Másodszor, a felügyelt szolgáltatásból hiányzik a teljesítményhangoláshoz szükséges rugalmasság. A szükséges művelet szűk körű volt: adott oldalazonosítók kötege alapján gyorsan adja vissza azok előkészített tartalmát. Ellenőrzést akartunk felette, hogy melyik gép tartja a partíciót, mennyi memória van fenntartva a cache-ének, és melyik replika kezel egy kérést. Ezek a vezérlők azért számítanak, porque egy nem cache-elt olvasás, a zónák közötti ugrás vagy egy lassú replika késleltetheti az egész köteget. A DynamoDB az API mögött saját maga kezeli a tárolást és a replikaviselkedést. Konfigurálhattuk a szolgáltatást, de nem tudtuk a munkaterhelésünkhöz igazítani a belső elhelyezését, a tárolómotor gyorsítótárait vagy a replikakiválasztási politikáját.

Harmadszor, a feldolgozás közvetlenül kapcsolódott a forró tárhelyre történő írásokhoz. Korábbi feldolgozó pipeline-unk minden egyes előkészített oldalt közvetlenül a DynamoDB-be írt. Egy nagy újra-feldolgozási feladat így az egyedi forró tárhely frissítések hullámává vált, és a feldolgozásnak igazodnia kellett a forró tárhely áteresztőképességi korlátaihoz és újrapróbálási (retry) viselkedéséhez. Nem tudtunk MapReduce-t futtatni az egész adatbázison 1-2 nap alatt egy új chunkolási módszer kipróbálásához, a beágyazási modellünk megváltoztatásához vagy egy új mező hozzáadásához. Nem létezett a mentett oldalfrissítéseknek olyan független streame, amelyet egy külön szolgáltatás kötegelhetett és a saját tempójában kézbesíthetett volna a forró tárhelynek.

Ezen korlátok kezelése nem merült ki az adatbázis cseréjében. Szét kellett választanunk a tartós dokumentumállapotot és a frissítések kézbesítését a lekérdezéskori tárolástól, majd kifejezetten a gyors, kötegelt olvasásokra kellett optimalizálnunk a forró tárhelyet.

A dokumentumállapot, a frissítéskézbesítés és a kiszolgálás szétválasztása

Az új architektúra minden felelősséget egy másik komponensre bíz. A Pillar fenntartja a tartós dokumentumállapotot, és eldönti, mit kell közzétenni. A Lorry particionált kötegekké alakítja ezeket az exportokat. A CobbleDB betölti a kötegeket, és oldalkulcs alapján kiszolgálja az előkészített rekordokat.

A tárolási migráció előtt és után. A Pillar és a Lorry szétválasztják a tartós dokumentumállapotot és a kézbesítést a CobbleDB által kiszolgált lekérdezéskori olvasásoktól.

CobbleDB: a hangolható olvasási útvonal

A CobbleDB a lekérdezéskori kiszolgáláshoz optimalizált forró tárhelyünk. Ez egy elosztott kulcs-érték tároló: a kulcsok oldalazonosítók (hashelt URL-ek), az értékek pedig az oldal feldolgozott reprezentációi (az előre feldarabolt szövegrészletek és a részenkénti vektorbeágyazások).

A fontos változás a tárolási konfiguráció feletti kontrollunk.

Az adatok partícionálva vannak, és megoszlanak az adatcsomópontok között. Ez az elosztott formátum biztosítja, hogy az adatbázis horizontálisan skálázható legyen a feldolgozott oldalak hatalmas korpuszának kiszolgálásához. Minden partíciónak három replikája van három különböző csomóponton; ha az egyik másolat elérhetetlenné válik, a többiek folytathatják az olvasások kiszolgálását.

Minden adatcsomópont megkapja a partíciók egy részhalmazát. A csomópont az adatait a RocksDB segítségével tárolja, amely egy nyílt forráskódú beágyazott kulcs-érték tárolómotor, és kiválóan alkalmas a kötegelt betöltéssel táplált, olvasásintenzív munkaterhelésekhez.

A cache-elt adatok kiszolgálhatók memóriából, míg a nem cache-elt olvasások helyi NVMe-tárat használnak. Ez közvetlen módot biztosít arra, hogy a megfigyelt munkaterheléshez hangoljuk a memória és a lemez egyensúlyát, ahelyett hogy elfadnánk a felügyelt szolgáltatás belső gyorsítótár-politikáját.

Egy állapottal nem rendelkező (stateless) lekérdezési router hasheli a kulcsokat partíciókra, és párhuzamosan útvonalozza a kéréseket a csomópontokhoz. Az architektúra magában foglal egy PostgreSQL-alapú metaadat-szolgáltatást és egy tartós objektumtárat is.

Ez a kialakítás támogatja a munkaterhelésünk által megkövetelt olvasási mintákat. Miután egy lekérdezés áthalad a visszakeresésen és a rangsoroláson a releváns oldalak jelölt készletének megszerzése érdekében, a forró tárhelyet kötegelve lekérdezzük ezen oldalak tartalmáért. Ezek az olvasási kérések megérkeznek a routerhez, amely továbbítja őket a megfelelő adatcsomóponthoz. A router prioritásként kezeli a kérések küldését egy azonos rendelkezésre állási zónában lévő csomóponthoz, hogy ha lehetséges, minimalizálja a zónák közötti késleltetést. Minden adatcsomópont be tudja tölteni az oldal tartalmát a MultiGet használatával, amely egy olyan RocksDB kötegelt olvasási API, amelyet arra terveztek, hogy alacsony késleltetéssel kérjen le egyszerre több kulcsot. Ha egy csomópont lassan válaszol, a rendszer lekérdezhet egy másik partíció-replikát is egy másik csomóponton, javítva a szélsőérték (tail) késleltetését.

Elkerülhetjük továbbá olyan általános funkciók implementálását is, amelyekre a munkafolyamatunk nincs rászorulva. A legtöbb adatbázis például konzisztencia-garanciákat és tranzakciókezelést kínál, de a forró tárhelyünk nem igényel tranzakciókat vagy szinkronizált replikákat. Teljesen elfogadható, hogy van egy rövidebb időbeli rés a dokumentum megírása és az olvasás számára való elérhetővé válása között, és az sem jelent problémát, ha az egyik replika gyorsabban tölt be adatokat, mint a másik. A nem szükséges funkciók elhagyása csökkenti a rendszerünk többletterhelését és költségeit.

Egy állapotmentes útválasztó partíció szerint csoportosítja az oldalkulcsokat, és párhuzamosan olvassa a replikákat. A lassú olvasások másik replikára küldhetők; az egyes partíciókhoz érkező első válasz bekerül az eredménybe.

Pillar és Lorry: a tartós írási útvonal

Egy külön tárolási réteg nyomon követ minden feltérképezett és feldolgozott elemet, egyesíti a frissítéseket a verziózott dokumentumállapotban, és közzéteszi a szükséges reprezentációkat a CobbleDB-ben és a keresési indexben. Ebből a célból hoztuk létre a Pillar nevű dokumentumállapot- és közzétételi rétegünket.

A Pillart YTsaurus támogatja. Oldalalkotó elemeket (például metaadatokat, chunkokat és beágyazásokat) tárol különálló YTsaurus táblacsaládokban. Emellett nyomon követi a chunkok és beágyazások verzióit, lehetővé téve több reprezentáció egymás melletti létezését anélkül, hogy felülírnák egymást, így az alatta lévő modellek változhatnak. Mivel a Pillar és az YTsaurus olcsó HDD-tárolót használ, sokkal több dokumentumot tudunk tárolni, mint korábban a DynamoDB-ben.

A Pillar részhalmazokat, vagyis szabályok által definiált oldalcsoportokat (például friss oldalakat vagy nagy értékű oldalakat) követ nyomon. A részhalmaz-tagság határozza meg, hogy mely frissítéseket kell továbbítani a lefelé áramló (downstream) komponenseknek, például a CobbleDB-nek. A CobbleDB drágább NVMe-tárat használ, ezért csak azokat a dokumentumokat exportáljuk, amelyeket valóban fel akarunk használni a keresési eredményekben. Amikor egy oldal tartalma frissül, vagy ha hozzáadják/eltávolítják egy részhalmazból, a Pillar felelős a megfelelő exportálási munka sorba állításáért.

Az atomi YTsaurus tranzakciók lefedik az állapotot, az exportálási szándékokat és a feldolgozott bemenetet a helyesség biztosítása érdekében. Ahhoz, hogy egy tranzakció sikeresen befejeződjön, a rendszernek frissítenie kell a tartós állapotot, el kell könyvelnie a bemenetet, és be kell sorba állítania az összes szükséges exportálást; ha a tranzakció megszakad, egyik rétegen sem történik változás.

A CobbleDB azt várja el, hogy a frissítések partíciók szerint legyenek kötegelve. A Pillar exportjait egy Lorry nevű, állapottal nem rendelkező (stateless) szolgáltatás konvertálja ebbe a formátumba. Ennek kizárólagos feladata, hogy exportrekordokat olvasson egy perzisztens, partíciókhoz igazított sorból (queue), partíciónkénti kötegelt fájlokba gyűjtse őket, és átadja a CobbleDB-nek.

A Lorry és a Cobble között egy további protokoll választja el az adatívet (S3) a vezérlőívtől. A Lorry egyedi azonosító alatt írja a köteget az S3-ra, és regisztrálja a köteget a Cobble-ben. A Cobble partícióreplikái függetlenül lekérdezik a köteget vagy a vezérlőív API-ját a következő betöltendő S3-kötegért, és aszinkron módon, kronológiai sorrendben alkalmazzák a kötegfrissítéseket. Ez azt jelenti, hogy egy lassú vagy helyreállási fázisban lévő csomópont a saját tempójában zárkózhat fel, ahelyett hogy lelassítaná a rendszer többi részét.

A Pillar tartós exportokat tesz közzé, a Lorry pedig partícionált kötegeket ír az S3-ba, és regisztrálja őket a vezérlősíkon. A CobbleDB minden replikája egymástól függetlenül felderíti és időrendi sorrendben betölti a saját kötegeit.

Az új útvonal elválasztja az azonnali feldolgozási tranzakciót a forró tárhelyre történő betöltéstől, és újrahasznosítható (replayable) útvonalat biztosít a növekményes frissítésekhez és a tömeges újraépítésekhez. Ez a feladatmegosztás biztosítja, hogy a tartós dokumentumállapot frissítései ne zavarják az olvasási útvonal késleltetését.

Validálás

5x gyorsabb

A forró tárhely migrálását követően a medián kötegelt olvasási késleltetés 31,4 ms-ról 5,60 ms-ra csökkent. A p90 56,7 ms-ról 9,77 ms-ra, a p99 pedig 123 ms-ról 24,2 ms-ra esett vissza. Az arányok 5,08× és 5,80× között mozognak, javulást mutatva mind a tipikus kérések, mind a szélsőértékek (tail) terén.

A migráció előtti és utáni éles kötegolvasási késleltetés: a P50 31,4-ről 5,60 ms-ra csökkent, a P90 56,7-ről 9,77 ms-ra, a P99 pedig 123-ról 24,2 ms-ra. Ezek élő forgalomból származó hot-store olvasási késleltetések különböző időpontokban, nem párosított kérések vagy végponttól végpontig mért válaszkésleltetések.

Ezek a számok kötegelt olvasásokból származnak, ahol minden lekérdezés körülbelül 10-15 kulcsot kér le. Az átlagos hasznos adat (payload) mérete 50 KB. Mindkét rendszert körülbelül 200k rps sebességgel kérdeztük le, bár teszteltünk terhelést 500k rps-ig is, teljesítményromlás nélkül.

A CobbleDB hozzájárulásának elkülönítése érdekében a beállítás minden egyéb aspektusát változatlanul hagytuk. Ez azonban továbbra is egy megfigyelésen alapuló előtte-utána összehasonlítás; a rendszerek éles (production) forgalmat szolgáltak ki különböző időpontokban, nem pedig ugyanazokat a kéréseket ellenőrzött körülmények között. Ezért mindkét rendszer teljesítményét szintetikus benchmarkon is mernékk, illetve mértük. A gyártási környezetben látható kérések közelítése érdekében mindkettőt 10-15 kulcsból álló kötegekkel, valamint 100B és 100 KiB közötti értékekkel kérdeztük le.

10 és 15 kulcsból álló kötegek szintetikus kötegolvasási késleltetése 100 B és 100 KiB közötti értékméretek mellett. Minden pont két futás run-szintű P50, P90 vagy P99 késleltetésének átlaga backendenként és konfigurációnként.

A DynamoDB ebben a kontextusban azért küzd nehézségekkel, mert a felügyelt olvasási útvonala nem hangolható ilyen méretű kötegelt olvasásokhoz. Ezzel szemben a CobbleDB pontosan erre a beállításra van optimalizálva, mivel mi kezeljük az elhelyezést, a gyorsítótárazást és a replikavezérléseket. Kulcsok szerint csoportosítja a partíciókat, párhuzamosan hajt végre olvasásokat a partíció-replikákkal szemben, és képes a lassú olvasásokat más replikákkal fedezni. Eredményeink azt mutatják, hogy ezek a tervezési döntések gyorsabb olvasásokat eredményeznek termelési környezetben.

20%-kal olcsóbb

A CobbleDB ráadásul jelentősen olcsóbb, mint a DynamoDB. A tároláson túl a DynamoDB minden egyes leolvasott vagy megírt bájtot is felszámít. Korpuszunk és kiszolgálási forgalmunk skáláján ezek a használatalapú díjak a teljes költség jelentős részét teszik ki. A folyamatos frissítések írási kapacitást emésztenek fel, míg a lekérdezéshez lehívott előkészített oldalak minden egyes kötege olvasási kapacitást fogyaszt.

A költségmegtakarítást a tárolási méretre, az írási kapacitás egységekre és az olvasási kapacitás egységekre vonatkozó belső becslések alapján számítottuk ki. Minden elköteleződési szinten a CobbleDB legalább 20%-kal olcsóbb, mint a DynamoDB. A valós megtakarítás valószínűleg még ennél is magasabb, mivel ezek az eredmények nem veszik figyelembe a tömörítésből eredő lehetséges biztonsági mentési megtakarításokat.

Hogyan segít nekünk egy AI-ügynökökből álló raj ennek felépítésében?

A CobbleDB és a kapcsolódó infrastruktúra felépítése minden volt, csak nem triviális. Maga a CobbleDB magadatabázisa körülbelül 40 000 sornyi Rust kódból áll, és a migráció emellett kiterjedt munkát igényelt a helyesség és a hibakezelési viselkedés gyártási skálán történő ellenőrzéséhez, a CobbleDB integrálásához a feldolgozási és a kiszolgálási útvonalakhoz, valamint az éles forgalom biztonságos átvezetéséhez.

Ezt az erőfeszítést egy olyan belső rendszerrel gyorsítottuk fel, amely kódolási ügynökök raját működteti. A rendszert úgy tervezték, hogy rendkívül perzisztens legyen; a munkamenetek során megőrzi a projekt céljait, az aktív kockázatokat, a repository előzményeit és a korábbi döntéseket.

Proaktív, folyamatosan futó ügynökökből álló csoportot rendeltünk a CobbleDB projekthez. Első feladatuk az volt, hogy pontos modellt készítsenek a már folyamatban lévő munkáról. Átnézték a projektcsatornát és az aktív szálakat, összekapcsolták a függőben lévő elemeket a pull requestekkel és a felelősökkel, valamint aktuális jelentést készítettek a rendszer egészségügyi állapotáról és a következő lépésekről. Ezt követően a mérnöki ciklus számos területén végeztek tevékenységet:

  • Áttekintettük a kód- és infrastrukturális változtatásokat.
  • Felszínre hoztunk olyan, könnyen figyelmen kívül hagyható blokkoló tényezőket, mint a nem biztonságos visszaállítási feltételezések, elavult build-hivatkozások és futásidőben hibára futó konfigurációk.
  • Előkészített fókuszált javítások, tesztek, figyelési (monitoring) változtatások és üzemeltetési dokumentáció.
  • Nyomon követtük a CI-, felülvizsgálati (review) és infrastrukturális kapukat, miközben a gyártási műveleteket kifejezetten emberi felügyelet alatt tartottuk.
  • Felügyelt bevezetések, valamint biztonsági mentési és visszaállítási gyakorlatok, amelyek során a megfigyelt rendszerállapotot összehasonlították a bevezetés sikerességi kritériumaival.
  • Karbantartottuk a projektriportokat, hogy a mérnökök láthassák, mi került ki (shipped), mi maradt blokkolva, és hol van még szükség emberi döntéshozatalra.

A mérnökök továbbra is ők állították össze az architektúrát, hagyták jóvá a lényeges változtatásokat, és engedélyezték a gyártási műveleteket. Az ügynökök kezelték a folyamatos ellenőrzések és a döntések közötti végrehajtások nagy részét. Ez lehetővé tette, hogy egy kis csapat hatékonyan vigye véghez a projektet – a CobbleDB maginfrastruktúráját két emberi mérnök és több száz AI-ügynök építette fel két hónap alatt.

Összegzés

A CobbleDB nem egy általános célú adatbázis; kifejezetten az aszinkron feldolgozási folyamat által betáplált, előkészített oldalrekordok ismételt kötegelt olvasásából álló, specifikus munkaterhelésünkhöz lett igazítva. A tartós állapot, a frissítések kézbesítése és a lekérdezéskori kiszolgálás szétválasztásával minden egyes feladatot függetlenül optimalizálhatunk anélkül, hogy egyetlen adatbázis-interfésznek kellene mindegyiket kiszolgálnia, vagy karbantartási munkát kellene áttolnunk az éles lekérdezési útvonalra. A CobbleDB-re való áttérés jelentősen javította a medián és a szélsőérték (tail) késleltetését is, miközben csökkentette az infrastrukturális költségeket.

Ahogy továbbra is saját fejlesztésű, AI-natív infrastruktúrát építünk ki házi készítésű módon, olyan teljesítmény- és költségoptimalizálást érhetünk el, amelyre harmadik féltől származó implementációk és árképzési modellek mellett nincs lehetőség. Az erőteljes mesterségesintelligencia-ügynökök még inkább az építés javára billentik a mérleget a vásárlással szemben.

Hamarosan nyílt forráskódúvá tesszük a CobbleDB-t, így más, AI-natív keresési rendszereket építő csapatok is élvezhetik ugyanannak a tárolási rétegnek az előnyeit, amely gyorsabbá és olcsóbbá teszi a keresést a termékeinken belül.

Hivatkozás

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}
}