CobbleDB: Genopbygning af AI-søgelagring for lavere latens og omkostninger

En afkoblet arkitektur med adskilt holdbar tilstand, batchopdateringer og forespørgselshentning.

ForfatterePerplexity Engineering

AI-native søgning stiller forskellige krav til, hvordan webindhold skal behandles, gemmes og serveres. For at imødekomme disse krav i skala har vi genopbygget hele stakken helt fra bunden for at forbedre ydeevnen og reducere omkostningerne.

Vi byggede vores egne crawling-, indekserings- og rangeringsinfrastrukturer, embedding-modeller og inferensmotorer til at drive effektiv behandling og hurtig servering. Vi udvider nu den tilgang til lagring, det bindevæv som behandlingen skriver til, og som serveringen læser fra.

Håndterede databaser som DynamoDB leverede pålidelig lagring, men vi havde begrænset kontrol over, hvordan data blev gemt, og læsninger blev serveret, og forbrugsbaseret prissætning voksede med vores indeksstørrelse og forespørgselsvolumen. Separat koblede vores tidligere pipeline dokumentbehandling direkte til servedatabasen, hvilket gjorde det svært at administrere opdateringer uafhængigt af live-læsninger.

Med hjælp fra en ny intern kodningsagentsværm genopbyggede vi dette lag omkring CobbleDB, et nøgle-værdi-hot-lager, understøttet af Pillar til holdbar dokumenttilstand og udgivelse, samt Lorry til batchopdateringer. Redesignet adskiller dokumentbehandling fra hot-lager-indsamling og lader os finjustere serveringen til vores arbejdsbelastning. Produktion før-og-efter-målinger viser ca. 5× lavere batchlæsningslatens, og vores interne omkostningsmodel anslår mindst 20 % besparelser i forhold til DynamoDB.

Infrastruktur til lagring til AI-native søgning

En klassisk søgestak er bygget til et menneskeligt læser, ikke en sprogmodel. Den forskel ændrer den granularitet, hvormed systemet skal forberede, gemme og servere webindhold.

For at omdanne rå HTML til modelklart indhold renser vores behandlingspipeline hver side, opdeler den i semantisk sammenhængende passager, beregner deres embeddings og gemmer passagerne og embeddings sammen i databasen. På forespørgselstidspunktet vælger serveringspipelinen den tekst, der er mest relevant for forespørgslen, og sender den til modellen.

To forskellige arbejdsprocesser

Lagerlaget skal derfor understøtte to arbejdsprocesser med forskellige krav: indsamling af opdateringer fra behandlingspipelinen og betjening af lav-latens-læsninger.

På skriv siden ankommer nye sider kontinuerligt, eksisterende sider ændrer sig, og ændringer i chunking-metoder eller embedding-modeller kan kræve genbehandling af store dele af korpuset. Lagerlaget skal inkorporere disse opdateringer i holdbar dokumenttilstand, spore de aktuelle versioner, udgive de rigtige repræsentationer til nedstrøms systemer og understøtte store backfills uden at gentage hele pipelinen fra det rå crawl.

I modsætning hertil anmoder forespørgselslæsninger om batches af sidenøgler og henter de tilsvarende passage-tekster og embeddings til rangering og passage-udvælgelse. Disse læsninger sidder på stien til det endelige svar, så de skal afsluttes hurtigt.

Disse arbejdsbelastninger kræver forskellige design. Behandling drager fordel af holdbare køer og evnen til at levere opdateringer effektivt, genforsøge fejl og bringe eksisterende poster op til dato i skala. Servering drager fordel af præudregnede poster, varme cacher og hurtige batchlæsninger med minimalt forespørgselsarbejde. Stram kobling mellem de to kan få genopretningsarbejde og store opdateringer til at konkurrere med latensfølsomme læsninger.

Hvor den administrerede database kommer til kort

DynamoDB gav os højtilgængelig lagring uden byrden ved at drive en distribueret database. En dedikeret tabel med forberedte sider indeholdt passager og embeddings til forespørgselslæsninger, mens fuldt sideindhold og metadata blev lagret separat. Men efterhånden som vores arbejdsbelastning voksede, blev tre begrænsninger vigtige.

For det første opkræver den forbrugsbaserede prissætning for hver byte, der læses eller skrives. En enkelt Search API-forespørgsel indeholder 100-120 nøgler svarende til sider, vi skal hente. Dataindhentningsprocessen opdel en forespørgsel i mindre batches på 10-20 sidenøgler med en gennemsnitlig elementstørrelse på ca. 50 KB. Gentagen læsning af disse poster forbrugte betydelig kapacitet, mens løbende crawls og ændringer i chunking- eller embedding-modeller genererede flere skrivninger. Økonomien afhang også af posternes størrelse og hyppighed, ikke kun antallet af API-kalde.

For det andet mangler den administrerede tjeneste den fleksibilitet, vi har brug for til ydeevneoptimering. Den handling, vi havde brug for, var snæver: Givet en batch af sidenøgler, returner deres forberedte indhold hurtigt. Vi ønskede kontrol over, hvilken maskine der indeholdt en partition, hvor meget hukommelse der blev afsat til dens cache, og hvilken replika der håndterede en forespørgsel. Disse kontroller er vigtige, fordi en ukachederet læsning, et hop på tværs af zoner eller en langsom replika kan forsinke hele batchen. DynamoDB administrerer sin egen lagrings- og replikaadfærd bag API'et. Vi kunne konfigurere tjenesten, men vi kunne ikke finjustere dens interne placering, lagringsmotorcacher eller replikavalgspolitik omkring vores arbejdsbelastning.

For det tredje var behandlingen knyttet direkte til hot-lager-skrivninger. Vores tidligere behandlingspipeline skrev hver forberedt side direkte til DynamoDB. Et stort genbehandlingsjob blev derfor til en bølge af individuelle hot-lager-opdateringer, og behandlingen var nødt til at tage højde for hot-lagerets gennemløbsgrænser og genforsøgsadfærd. Vi kunne ikke køre MapReduce over hele databasen på 1-2 dage for at afprøve en ny chunking-metode, ændre vores embedding-model eller tilføje et nyt felt. Der var ingen uafhængig strøm af gemte sideopdateringer, som en separat tjeneste kunne batche og levere til hot-lageret i sit eget tempo.

Håndtering af disse begrænsninger krævede mere end blot at udskifte databasen. Vi var nødt til at adskille holdbar dokumenttilstand og opdateringslevering fra forespørgselslageret og derefter optimere hot-lageret specifikt til hurtige, batchbaserede læsninger.

Adskil dokumenttilstand, opdateringslevering og servering

Den nye arkitektur tildeler hvert ansvarsområde til en anden komponent. Pillar opretholder en holdbar dokumenttilstand og beslutter, hvad der skal udgives. Lorry omdanner disse eksporter til partitionerede batches. CobbleDB indsamler batchene og betjener forberedte poster efter sidenøgle.

Før og efter lagringsmigreringen. Pillar og Lorry adskiller holdbar dokumenttilstand og leveringen af opdateringer fra læsetidspunktslæsninger, der leveres af CobbleDB.

CobbleDB: den justerbare læsesti

CobbleDB er vores hot-lager, der er optimeret til at betjene forberedte poster på forespørgselstidspunktet. Det er et distribueret nøgle-værdi-lager: nøglerne er side-identifikatorer (hashede URL'er), og værdierne er sidernes bearbejdede repræsentation (deres præ-chunkede passager og pr.-chunk vektor-embeddings).

Den vigtige ændring er vores kontrol over lagerkonfigurationen.

Data er partitioneret og opdelt på tværs af datanoder. Dette distribuerede format sikrer, at databasen kan skalere horisontalt for at rumme et enormt korpus af behandlede sider. Hver partition har tre replikaer på tre forskellige noder; hvis en kopi bliver utilgængelig, kan de andre fortsætte med at betjene læsninger.

Hver datanode tildeles et delset af partitionerne. Noden gemmer sine data ved hjælp af RocksDB, en open source-indlejret nøgle-værdi-lageringsmotor, der er velegnet til læsetunge arbejdsbelastninger, der fødes af batch-indsamling.

Cachede data kan serveres fra hukommelsen, mens ukachede læsninger bruger lokal NVMe-lagring. Dette giver os en direkte måde at finjustere hukommelse-til-disk-balancen for den observerede arbejdsbelastning i stedet for at acceptere en administreret tjenestes interne cache-politik.

En statsløs forespørgselsrouter hasher nøgler til partitioner og dirigerer forespørgsler til noder parallelt. Arkitekturen inkluderer også en PostgreSQL-baseret metadatatjeneste og holdbar objektlagring.

Dette design understøtter de læsemønstre, som vores arbejdsbelastning kræver. Når en forespørgsel har gennemgået hentning og rangering for at opnå et kandidatsæt af relevante sider, forespørges hot-lageret i bulk for indholdet af disse sider. Disse læseforespørgsler ankommer til routeren og dirigeres til en tilsvarende datanode. Routeren prioriterer at sende forespørgsler til en node i den samme tilgængelighedszone for at minimere latens på tværs af zoner, hvis det er muligt. Hver datanode kan hente sideindholdet ved hjælp af MultiGet, en RocksDB-batchlæsnings-API designet til at hente flere nøgler samtidigt ved lav latens. Hvis en node er langsom til at svare, kan systemet også forespørge en anden partitionsreplika på en anden node, hvilket forbedrer halelatensen.

Vi kan også undgå at implementere almindelige funktioner, som vores arbejdsproces ikke kræver. For eksempel tilbyder de fleste databaser konsistensgarantier og transaktionsunderstøttelse, men vores hot-lager kræver ikke transaktioner eller synkroniserede replikaer. Det er acceptabelt, at der er en kort tidsforskel mellem, hvornår et dokument skrives, og hvornår det er tilgængeligt for læsninger, og det er også fint, at en replika indsamler data hurtigere end en anden. At udelade funktioner, vi ikke har brug for, reducerer vores systems overhead og omkostninger.

En statsløs router grupperer sidesnøgler efter partition og læser replikaer parallelt. Langsomme læsninger kan sendes til en anden replika; det første svar for hver partition flettes ind i resultatet.

Pillar og Lorry: den holdbare skrivesti

Et separat lagerlag holder styr på alt det, vi crawler og behandler, og fletter opdateringer sammen til en versioneret dokumenttilstand og udgiver de nødvendige repræsentationer til CobbleDB og søgeindekset. Vi byggede Pillar, vores dokumenttilstands- og udgivelseslag, til dette formål.

Pillar understøttes af YTsaurus. Den gemmer sidekomponenter (såsom metadata, chunks og embeddings) i separate YTsaurus-tabelfamilier. Den sporer også versionerne af chunks og embeddings, hvilket gør det muligt for flere repræsentationer at co-eksistere uden at overskrive hinanden, så de underliggende modeller kan ændre sig. Da Pillar og YTsaurus bruger billig HDD-lagring, kan vi gemme langt flere dokumenter, end vi tidligere gemte i DynamoDB.

Pillar sporer delmængder eller politikdefinerede grupper af sider såsom friske sider eller sider med høj værdi. Delmængdemedlemskab bestemmer, hvilke opdateringer der skal sendes videre til nedstrøms komponenter som CobbleDB. CobbleDB bruger dyrere NVMe-lagring, så vi eksporterer kun de dokumenter, vi faktisk ønsker at bruge i søgeresultater. Når en sides indhold opdateres, eller hvis den tilføjes eller fjernes fra en delmængde, er Pillar ansvarlig for at sætte det tilsvarende eksportarbejde i kø.

Atomeære YTsaurus-transaktioner dækker over tilstand, eksporthensigter og forbrugt input for at sikre korrekthed. For at en transaktion kan fuldføres med succes, skal systemet opdatere den holdbare tilstand, markere inputtet som bogført og sætte alle nødvendige eksporter i kø; hvis transaktionen afbrydes, foretages der ingen ændringer på noget lag.

CobbleDB forventer, at opdateringer batches i henhold til partition. Pillar-eksport konverteres til dette format af en statsløs tjeneste, vi kalder Lorry. Dens eneste job er at læse eksportposter fra en vedholdende, partitionsjusteret kø, akkumulere dem til pr. partition-batchfiler og overlevere dem til CobbleDB.

Mellem Lorry og Cobble adskiller en yderligere protokol dataplanet (S3) fra kontrolplanet. Lorry skriver batchen til S3 under en unikt identifikator og registrerer batchen i Cobble. Cobble-partitionsreplikaer forespørger uafhængigt batchen eller kontrolplanets API for den næste S3-batch, der skal indsamles, og anvender asynkront batchopdateringer i kronologisk rækkefølge. Det betyder, at en langsom eller genoprettende node kan indhente det forsømte i sit eget tempo i stedet for at bremse resten af systemet.

Pillar publicerer holdbare eksportfiler, og Lorry skriver partitionerede batches til S3 og registrerer dem hos kontrolplanet. Hver CobbleDB-replika opdager og indlæser uafhængigt sine batches i kronologisk rækkefølge.

Den nye sti adskiller den umiddelbare behandlingstransaktion fra hot-lager-indsamling og giver en genafspillelig sti til inkrementelle opdateringer og bulngenopbygninger. Denne arbejdsdeling sikrer, at opdateringer til holdbar dokumenttilstand ikke forstyrrer læsestiens latens.

Validering

5x hurtigere

Efter migrering af hot-lageret faldt median-latensen for batchlæsning fra 31,4 ms til 5,60 ms. P90 faldt fra 56,7 ms til 9,77 ms, og p99 faldt fra 123 ms til 24,2 ms. Forholdene varierer fra 5,08× til 5,80× med forbedringer i både typiske forespørgsler og halen.

Produktions-batch-læseforsinkelse før og efter migreringen: P50 faldt fra 31,4 til 5,60 ms, P90 fra 56,7 til 9,77 ms, og P99 fra 123 til 24,2 ms. Dette er læseforsinkelser fra hot store fra live trafik på forskellige tidspunkter, ikke matchede forespørgsler eller svarforsinkelse fra ende til ende.

Disse tal er afledt af batchbaserede læsninger, hvor hver forespørgsel anmoder om ca. 10-15 nøgler. Den gennemsnitlige nyttelast-elementstørrelse er 50 KB. Begge systemer blev forespurgt med ca. 200k rps, selvom vi også kørte belastningstests op til 500k rps uden forringelse.

For at isolere CobbleDB's bidrag blev alle andre aspekter af opsætningen holdt konstante. Dette forbliver dog en observationel før-og-efter-sammenligning; systemerne betjente live produktionstrafik på forskellige tidspunkter i stedet for de samme forespørgsler under kontrollerede forhold. Vi målte derfor også ydeevnen for begge systemer på en syntetisk benchmark. For at tilnærme os de forespørgsler, der ses i produktionen, forespurgte vi begge med batches på 10-15 nøgler og værdier fra 100B til 100 KiB.

Syntetisk batch-læseforsinkelse for batches på 10 og 15 nøgler på tværs af værdistørrelser fra 100 B til 100 KiB. Hvert punkt er gennemsnittet af run-niveau P50-, P90- eller P99-forsinkelse fra to runs pr. backend og konfiguration.

DynamoDB kæmper i denne sammenhæng, fordi dens administrerede læsesti ikke kan justeres til batchlæsninger af denne størrelse. I modsætning hertil er CobbleDB optimeret til netop denne opsætning, da vi styrer placering, caching og replikakontroller. Den grupperer nøgler efter partition, udfører læsninger parallelt mod partitionsreplikaer og kan afdække langsomme læsninger mod andre replikaer. Vores resultater viser, at disse designvalg oversættes til hurtigere læsninger i et produktionsmiljø.

20 % billigere

CobbleDB er også betydeligt billigere end DynamoDB. Ud over lagring opkræver DynamoDB også for hver byte, der læses eller skrives. I skalaen af vores korpus og serveringstrafik bliver disse forbrugsbaserede gebyrer en væsentlig del af de samlede omkostninger. Løbende opdateringer forbruger skrivekapacitet, mens hver batch af forberedte sider, der hentes til en forespørgsel, forbruger læsekapacitet.

Vi beregnede omkostningsbesparelserne baseret på interne skøn over lagerstørrelse, skrivekapacitetsenheder og læsekapacitetsenheder. På alle forpligtelsestrin er CobbleDB mindst 20 % billigere end DynamoDB. De reelle besparelser er sandsynligvis endnu højere, da disse resultater ikke tager højde for de sandsynlige backup-besparelser fra komprimering.

Hvordan en sværm af AI-agenter hjalp os med at bygge det

At bygge CobbleDB og den omgivende infrastruktur var langt fra trivielt. CobbleDB's kerne-database alene er på ca. 40.000 linjer Rust, og migreringen krævede også et omfattende arbejde med at verificere korrekthed og fejl-adfærd i produktionsskala, integrere CobbleDB med både behandlings- og serveringsstier samt sikkert overgå live-trafik.

Vi accelererede dette arbejde ved hjælp af et internt system, der driver en sværm af kodningsagenter. Systemet er designet til at være yderst vedholdende; det bevarer projektets mål, aktive risici, repositoriehistorik og tidligere beslutninger på tværs af sessioner.

Vi tildelte et kollektiv af sådanne proaktive, stedseaktive agenter til CobbleDB-projektet. Deres første opgave var at bygge en nøjagtig model af det arbejde, der allerede var i gang. De reviderede projektets tråd og aktive tråde, forbød udestående elementer til deres pull requests og ejere, og producerede en aktuel udlæsning af systemets sundhed og næste handlinger. Derfra opererede de på tværs af flere dele af ingeniørløkken:

  • Gennemgik kode- og infrastrukturændringer.
  • Fremhævede blokeringer, der var nemme at overse, herunder usikre gendannelsesantagelser, forældede build-referencer og konfigurationer, der ville fejle ved køretid.
  • Forberedte målrettede rettelser, tests, overvågningsændringer og operationel dokumentation.
  • Sporede CI-, review- og infrastrukturporte, samtidig med at produktionstiltag blev holdt eksplicit menneskeejede.
  • Overvågede udrulninger og backup- eller gendannelsesøvelser sammenlignede derefter den observerede systemtilstand med udrulningens succeskriterier.
  • Vedligeholdt projektudlæsninger, så ingeniører kunne se, hvad der var sendt, hvad der forblev blokeret, og hvor menneskelig dømmekraft stadig var påkrævet.

Ingeniører fastsætter stadig arkitekturen, gennemgik konsekvente ændringer og autoriserede produktionsoperationer. Agenterne håndterede en stor del af den løbende inspektion og opfølgning mellem disse beslutninger. Dette tillod et lille team at drive projektet effektivt — kernetjenesten i CobbleDB-infrastrukturen blev bygget af to menneskelige ingeniører og hundrevis af AI-agenter på to måneder.

Konklusion

CobbleDB er ikke en generisk database; den er specialbygget til vores specifikke arbejdsbelastning med gentagne batchlæsninger af forberedte siderækker, fødet af en asynkron behandlingspipeline. Ved at adskille holdbar tilstand, opdateringslevering og forespørgselsservering kan vi optimere hvert ansvarsområde uafhængigt uden at tvinge en enkelt databasegrænseflade til at rumme dem alle eller skubbe vedligeholdelsesarbejde over på live-forespørgselsstien. Migrering til CobbleDB forbedrede markant både median- og halelatens og reducerede samtidig infrastruktur-omkostningerne.

Da vi fortsætter med at udvikle AI-native infrastruktur internt, kan vi optimere ydeevne og omkostninger på måder, der ikke er mulige, når vi er bundet til tredjepartsimplementeringer og prissætningsmodeller. Kraftfulde AI-agenter skubber regnestykket endnu mere i favør af at bygge frem for at købe.

Vi planlægger at open-source CobbleDB snart, så andre teams, der bygger AI-native søgesystemer, kan drage fordel af det samme lagerlag, der gør søgning hurtigere og billigere i vores produkter.

Citering

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