CobbleDB: Реконструисање складишта AI претраге за мање кашњење и цијену

Декуплирана архитектура са одвојеним трајним стањем, груписаним ажурирањима и преузимањем у вријеме упита.

АуториPerplexity Engineering

Претрага заснована на вјештачкој интелигенцији намеће различите захтјеве у погледу тога како веб садржај треба да се обрађује, складишти и опслужује. Да бисмо испунили те захтјеве у великом обиму, реконструисали смо цијелу стекову од нуле како бисмо побољшали перформансе и смањили цијену.

Изградили смо нашу сопствену инфраструктуру за индексирање и рангирање, моделе уградње и инферентне машине за покретање ефикасне обраде и брзог опслуживања. Сада ширимо тај приступ на складиште, везивно ткиво на које обрада уписује и из којег читања опслужују.

Управљане базе података као што је DynamoDB пружале су поуздано складиште, али смо имали ограничену контролу над тим како се подаци складиште и како се читања опслужују, а цијене засноване на коришћењу расле су са величином нашег индекса и запремином упита. Одвојено, наш претходни цевовод је повезао обраду документа директно са базом података за опслуживање, што је отежавало управљање ажурирањима независно од читања уживо.

Уз помоћ новог интерног роја агената за кодирање, овај слој смо поново изградили око CobbleDB-а, вруће key-value меморије, уз подршку Pillar-а за трајно стање докумената и објављивање, и Lorry-ја за групна ажурирања. Редизајн одваја обраду докумената од уноса у врућу меморију и омогућава нам да сервис прилагодимо нашем радном терету. Производна мјерења прије и послије показују отприлике 5× нижу латенцију групног читања, а наш интерни модел трошкова процјењује најмање 20% уштеде у односу на DynamoDB.

Инфраструктура за складиштење за претрагу засновану на вјештачкој интелигенцији

Класична претраживачка стекова је направљена за људског читаоца, а не за језички модел. Та разлика мијења грануларност на којој систем треба да припреми, складишти и опслужује веб садржај.

Да би се сирови HTML претворио у садржај спреман за модел, наш цевовод за обраду чисти сваку страницу, дијели је на семантички кохерентне одсечке, израчунава њихове уградње и складишти одсечке и уградње заједно у бази података. У вријеме упита, цевовод за опслуживање бира текст најрелевантнији за упит и прослеђује га моделу.

Два различита тока рада

Слој за складиштење стога мора подржавати два тока рада са различитим захтјевима: унос ажурирања из цевовода за обраду и опслуживање читања са ниским кашњењем.

На страни уписа, нове странице стижу континуирано, постојеће странице се мијењају, а промјене у методама раздвајања или моделима уградње могу захтијевати поновну обраду великих дјелова корпуса. Слој за складиштење мора укључити ова ажурирања у трајно стање документа, пратити тренутне верзије, објавити праве репрезентације у низводне системе и подржати велика пуњења назад без понављања цијелог цевовода од сировог пробијања.

Супротно томе, читања у вријеме упита захтијевају серије кључева страница и преузимају одговарајући текст пролаза и уградње за рангирање и избор пролаза. Ова читања се налазе на путу до коначног одговора, тако да морају бити завршена брзо.

Ова радна оптерећења захтијевају различите дизајне. Обрада има користи од трајних редова чекања и способности да ефикасно испоручује ажурирања, поново покушава неуспјехе и доводи постојеће записе у актуелно стање у великом обиму. Опслуживање има користи од унапријед израчунатих записа, топлих кешова и брзих групних читања са минималним послом у вријеме упита. Чврста спрега између ова два може довести до тога да се посао опоравка и велика ажурирања такмиче са читањима осјетљивим на кашњење.

Гдје управљана база података подбацује

DynamoDB нам је пружио високо доступно складиште без терета управљања дистрибуираном базою података. Наменска табела припремљених страница садржала је одсечке и уградње за читања у вријеме упита, док су пун садржај странице и метаподаци били ускладиштени одвојено. Међутим, како је наше радно оптерећење расло, три ограничења су постала важна.

Друго, цијене засноване на коришћењу наплаћују сваки прочитани или уписани бајт. Један захтјев Search API садржи 100-120 кључева који одговарају страницама које морамо да добавимо. Процес преузимања података дијели захтјев на мање серије од 10-20 кључева страница са просјечном величином ставке од око 50 KB. Поновљено читање тих записа троши значајан капацитет, док континуирано пробијање кроз садржај и промјене у моделима раздвајања или уградње генеришу више уписа. Економика је такође зависила од величине и учесталости записа, а не само од броја API позива.

Друго, управљаној услузи недостаје флексибилност која нам је потребна за подешавање перформанси. Операција која нам је била потребна била је уска: дат је скуп идентификатора страница, брзо вратите њихов припремљени садржај. Жељели смо контролу над тим која машина држи партицију, колико меморије је посвећено њеном кешу и која реплика рукује захтјевом. Те контроле су важне зато што некаширано читање, скок између зона или спора реплика могу одгодити цијелу серију. DynamoDB управља сопственим складиштем и понашањем реплика иза API-ја. Могли смо да конфигуришемо услугу, али нисмо могли да подесимо њено интерно постављање, кешове механизма за складиштење или политику избора реплика око нашег односа радног оптерећења.

Треће, обрада је била директно везана за уписе у вруће складиште. Наш претходни цевовод за обраду уписивао је сваку припремљену страницу директно у DynamoDB. Велики посао поновне обраде стога је постао талас појединачних ажурирања врућег складишта, а обрада је морала да се прилагоди ограничењима пропусног опсега врућег складишта и понашању поновног покушаја. Нисмо могли да покренемо MapReduce преко цијеле базе података за 1-2 дана да бисмо пробали нови метод раздвајања, променили наш модел уградње или додали ново поље. Није постојал независан ток сачуваних ажурирања страница који засебна услуга може да групише и испоручи врућем складишту сопственим темпом.

Рјешавање ових ограничења захтијевало је више од замјене базе података. Морали смо одвојити трајно стање документа и испоруку ажурирања од складишта за вријеме упита, а затим оптимизовати вруће складиште посебно за брза групна читања.

Одвојено стање документа, испорука ажурирања и опслуживање

Нова архитектура додељује сваку одговорност другој компоненти. Pillar одржава трајно стање документа и одлучује шта да објави. Lorry претвара те извозе у партиционисане серије. CobbleDB уноси серије и опслужује припремљене записе по кључу странице.

Prije i poslije migracije skladišta. Pillar i Lorry razdvajaju trajno stanje dokumenata i isporuku ažuriranja od čitanja u vrijeme upita koje opslužuje CobbleDB.

CobbleDB: путања читања која се може подешавати

CobbleDB је наше вруће складиште оптимизовано за опслуживање припремљених записа у вријеме упита. То је дистрибуирано складиште кључ-вриједност: кључеви су идентификатори страница (хеширани URL-ови), а вриједности су обрађена репрезентација странице (њихови унапријед исечени одсечци и векторске уградње по одсечку).

Важна промјена је наша контрола над конфигурацијом складишта.

Подаци су партиционисани и подељени на чворове података. Овај дистрибуирани формат осигурава да база података може хоризонтално да се скалира како би примила невероватан корпус обрађених страница. Свака партиција има три реплике на три различитна чвора; ако једна копија постане недоступна, остале могу наставити да опслужују читања.

Сваком чвору података је додијељен подскуп партиција. Чвор складишти своје податке користећи RocksDB, отворени уграђени механизам за складиштење кључ-вриједност који је врло погоддан за радне оптерећења са тешком путањом читања подржана групним уносом.

Кеширани подаци могу се опслуживати из меморије, док некаширана читања користе локално NVMe складиште. Ово нам даје директан начин да подесимо баланс меморије и диска за уочено радно оптерећење, умјесто прихватања интерне политике кеширања управљане услуге.

Безначајни рутер упита хешира кључеве у партиције и усмјерава захтјеве на чворове паралелно. Архитектура такође укључује услугу метаподатака засновану на PostgreSQL-у и трајно складиште објеката.

Овај дизајн подржава обрасце читања које наш радни терет захтијева. Након што упит прође кроз проналажење и рангирање да би се добио кандидатски скуп релевантних страница, врућа меморија се у групи упитује за садржај тих страница. Ови захтјеви за читање стижу до рутира и просљеђују се одговарајућем чвору података. Рутер даје предност слању захтјева чвору у истој зони доступности како би се, ако је могуће, умањила латенција преко зона. Сваки чвор података може да преузме садржај странице помоћу MultiGet-а, RocksDB batch-read API-ја осмишљеног да истовремено преузима више кључева уз ниску латенцију. Ако чвор споро одговара, систем може да упита и другу реплику партиције на другом чвору, што побољшава латенцију у репу.

Такође можемо избјећи имплементацију уобичајених функција које наш ток рада не захтијева. На примјер, већина база података нуди гаранције конзистентности и подршку за трансакције, али наше вруће складиште не захтијева трансакције нити синхронизоване реплике. Прихватљиво је да постоји кратак размак између тренутка када је документ уписан и када је доступан за читање, а такође је у реду и да једна реплика уноси податке брже од друге. Изостављањем функција које нам нису потребне смањује се оптерећење и цијена нашег система.

Bezustavni ruter grupiše ključeve stranica po particiji i paralelno čita replike. Spora čitanja mogu se poslati na drugu repliku; prvi odgovor za svaku particiju spaja se u rezultat.

Pillar и Lorry: путања трајног уписа

Посебан слој за складиштење прати све што индексирамо и обрађујемо, спајајући ажурирања у верзионисано стање документа и објављујући неопходне репрезентације у CobbleDB и индекс претраге. У ту сврху смо изградили Pillar, наш слој за стање докумената и објављивање.

Pillar је подржан од стране YTsaurus. Он складишти компоненте страница (као што су метаподаци, одсечци и уградње) у одвојеним породицама табела YTsaurus. Такође прати верзије одсечака и уградњи, омогућавајући да више репрезентација коегзистирају без преписивања једна друге како би се основни модели могли мијењати. Пошто Pillar и YTsaurus користе јефтино HDD складиште, можемо складиштити много више докумената него што смо претходно складиштили у DynamoDB.

Pillar прати подскупове, или групе страница дефинисане политиком као што су свеже странице или странице високе вриједности. Чланство у подскупу одређује која ажурирања треба пренијети на низводне компоненте као што је CobbleDB. CobbleDB користи скупље NVMe складиште, тако да извозимо само документе које заиста желимо да користимо у резултатима претраге. Када се садржај странице ажурира, или ако је додан или уклоњен из подскупа, Pillar је одговоран за стављање одговарајућег извозног посла у ред чекања.

Атомне YTsaurus трансакције обухватају стање, намјере извоза и конзумирани улаз како би се осигурала исправност. Да би се трансакција успјешно завршила, систем мора да ажурира трајно стање, означи улаз као евидентиран и стави у ред чекања све потребне извозе; ако се трансакција прекине, ниједна промјена се не врши ни на једном слоју.

CobbleDB очекује да ажурирања буду груписана према партицији. Pillar извози се претварају у овај формат помоћу безначајне услуге коју називамо Lorry. Њен једини задатак је да чита извозне записе из упорног реда поравнатог по партицијама, акумулира их у датотеке серија по партицијама и преда их у CobbleDB.

Између Lorry-ја и Cobble-а, додатни протокол раздваја раван података (S3) од равни контроле. Lorry уписује серију у S3 под јединственим идентификатором и региструје серију унутар Cobble-а. Реплике партиција Cobble независно упитају серију или API равни контроле за сљедећу S3 серију за унос и асинхроно примјењују ажурирања серија хронолошким редом. То значи да спор или чвор који се опоравља може да стигне сопственим темпом, умјесто да успорава остатак система.

Pillar objavljuje trajne izvoze, a Lorry upisuje particionisane serije u S3 i registruje ih kod kontrolne ravni. Svaka CobbleDB replika nezavisno otkriva i učitava svoje serije hronološkim redosledom.

Нова путања одваја тренутну обраду трансакције од уноса у вруће складиште и пружа путању која се може поново извршити за инкрементална ажурирања и масовне реконструкције. Ова подела рада осигурава да ажурирања трајног стања документа не ометају кашњење путање читања.

Валидација

5x брже

Након миграције врућег складишта, медијана кашњења групног читања пала је са 31,4 ms на 5,60 ms. P90 је пао са 56,7 ms на 9,77 ms, а p99 је пао са 123 ms на 24,2 ms. Односи се крећу од 5,08× до 5,80×, уз побољшања како код типичних захтјева, тако и код крајњих вриједности.

Latencija grupnog čitanja u produkciji prije i poslije migracije: P50 je pao sa 31,4 na 5,60 ms, P90 sa 56,7 na 9,77 ms, a P99 sa 123 na 24,2 ms. Ovo su latencije čitanja iz vrućeg skladišta iz stvarnog saobraćaja u različitim trenucima, a ne upoređeni zahtjevi niti latencija odgovora od početka do kraja.

Ови бројеви су изведени из груписаних читања, гдје сваки упит захтијева отприлике 10-15 кључева. Prosječna veličina stavke korisnog tereta je 50KB. Оба система су упитана са око 200k rps, мада смо такође покренули тестове оптерећења до 500k rps без деградације.

Да би се изоловао допринос система CobbleDB, сви други аспекти поставке су задржани непромијењенима. Међутим, ово остаје опсервационо поређење прије и послије; системи су опслуживали продукцијски саобраћај у различито вријеме умјесто истих захтјева под контролисаним условима. Због тога смо такође измјерили перформансе оба система на синтетичком реперу. Да бисмо приближили захтјеве виђене у продукцији, упитали смо оба са групама од 10-15 кључева и вриједностима у распону од 100B до 101 KiB.

Sintetička latencija grupnog čitanja za serije od 10 i 15 ključeva preko veličina vrijednosti od 100 B do 100 KiB. Svaka tačka je srednja vrijednost latencije na nivou pokretanja za P50, P90 ili P99 iz dva pokretanja po backendu i konfiguraciji.

DynamoDB се мучи у овом контексту зато што се његова управљана путања читања не може подесити за груписана читања ове величине. Насупрот томе, CobbleDB је оптимизован за управо ову поставку, пошто управљамо постављањем, кеширањем и контролама реплика. Он групише кључеве по партицији, извршава читања паралелно у односу на реплике партиција, и може хеџовати спора читања у односу на друге реплике. Наши резултати показују да се ови избори дизајна преводе на бржа читања у продукцијском окружењу.

20% јефтиније

CobbleDB је такође значајно јефтинији од DynamoDB-а. Поред складишта, DynamoDB такође наплаћује сваки прочитани или уписани бајт. На скали нашег корпуса и саобраћаја опслуживања, ови наплати засновани на коришћењу постају значајан дио укупне цијене. Континуирана ажурирања троше капацитет уписа, док свака серија припремљених страница добављена за упит троши капацитет читања.

Израчунали смо уштеде трошкова на основу интерних процјена величине складишта, јединица капацитета уписа и јединица капацитета читања. На сваком нивоу посвећености, CobbleDB је барем 20% јефтинији од DynamoDB. Праве уштеде су вјероватно још веће, пошто ови резултати не узимају у обзир вјероватне уштеде резервних копија од компресије.

Како нам рој агената вјештачке интелигенције помаже да га изградимо

Изградња CobbleDB-а и пратеће инфраструктуре била је далеко од једноставне. Само основна база података CobbleDB има приближно 40.000 линија Rust-а, а миграција је такође захтијевала опсежан рад на верификацији исправности и понашања у случају кварова у продукцијском скалирању, интеграцији CobbleDB-а са путањама обраде и опслуживања, и сигурном прелазу продукцијског саобраћаја.

Убрзали смо овај напор користећи интерни систем који управља ројем агената за кодирање. Систем је дизајниран да буде врло упоран; задржава циљеве пројекта, активне ризике, историју репозиторијума и претходне одлуке кроз сесије.

Додијелили смо колектив таквих проактивних агента који су увијек укључени на пројекат CobbleDB. Њихов први задатак био је да изграде тачан модел посла који је већ у току. Ревидирали су пројектни тред и активне тредове, повезали неријешене ставке са њиховим pull request-овима и власницима, и произвели тренутни извештај о здрављу система и сљедећим акцијама. Одатле су дјеловали у неколико дијелова инжењерске петље:

  • Прегледане промјене кода и инфраструктуре.
  • Истакнути блокадери које је било лако пропустити, укључујући несигурне претпоставке о обнављању, застареле референце градње и конфигурације које би пропале у вријеме извршавања.
  • Припремљене циљане исправке, тестови, промјене надгледања и оперативна документација.
  • Праћени CI, преглед и инфраструктурне капије док су продукцијске акције експлицитно задржале људско власништво.
  • Праћена увођења и вјежбе резервне копије или обнављања, затим упоређено уочено стање система са критеријумима успјеха увођења.
  • Одржавани су пројектни извештаји тако да су инжењери могли да виде шта је испоручено, шта је остало блокирано и гдје је људска процјена и даље потребна.

Инжењери и даље постављају архитектуру, прегледали су значајне промјене и овластили продукцијске операције. Агенти су руковали већим дијелом континуиране инспекције и праћења између тих одлука. Ово је омогућило малом тиму да ефикасно води пројекат — основна инфраструктура CobbleDB изграђена је од стране два људска инжењера и стотина агената вјештачке интелигенције за два мјесеца.

Закључак

CobbleDB није база података опште намјене; она је намјенски направљена за наше специфично радно оптерећење поновљених групних читања записа припремљених страница, храњених асинхроним цевоводом за обраду. Одвајањем трајног стања, испоруке ажурирања и опслуживања у вријеме упита, можемо независно оптимизовати сваку одговорност без присиљавања једног интерфејса базе података да их све прилагоди или гурања посла одржавања на путању упита уживо. Миграција на CobbleDB значајно је побољшала како медијану тако и кашњење на крајевима уз смањење трошкова инфраструктуре.

Како настављамо да интерно развијамо инфраструктуру засновану на вјештачкој интелигенцији, можемо оптимизовати перформансе и цијену на начине који нису могући када смо везани за имплементације трећих страна и мождане цијена. Моћни агенти вјештачке интелигенције помичу рачуницу још више у корист изградње умјесто куповине.

Планирамо да ускоро отворимо изворни код CobbleDB-а, тако да други тимови који граде системе за претрагу засноване на вјештачкој интелигенцији могу имати користи од истог слоја за складиштење који чини претрагу бржом и јефтинијом унутар наших производа.

Цитирање

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