CobbleDB: відбудова сховища пошуку на базі ШІ для меншої затримки та вартості
Декоупльована архітектура з розділеним постійним станом, пакетними оновленнями та вилученням під час запитів.
Пошук на базі штучного інтелекту ставить інші вимоги до того, як вебконтент має опрацьовуватися, зберігатися та обслуговуватися. Щоб задовольнити ці вимоги в масштабі, ми з нуля відбудовуємо весь стек для підвищення продуктивності та зниження витрат.
Ми створили власні інфраструктуру сканування, індексування та ранжування, моделі ембедингів та рушії інференсу, щоб забезпечити ефективний обробіток та швидке обслуговування. Тепер ми поширюємо цей підхід на сховище — сполучну тканину, куди обробіток записує дані, а обслуговування зчитує їх.
Керовані бази даних, такі як DynamoDB, забезпечували надійне сховище, але ми мали обмежений контроль над тим, як зберігаються дані та обслуговується читання, а ціноутворення на основі використання зростало разом із розміром нашого індексу та обсягом запитів. Окремо наш попередній конвеєр зв'язував обробіток документів безпосередньо з базою даних обслуговування, що ускладнювало незалежне керування оновленнями від живих читань.
За допомогою нової внутрішньої системи агента кодування ми відбудували цей шар навколо CobbleDB — гарячого сховища типу «ключ-значення», що підтримується Pillar для стабільного стану документів і публікації, а також Lorry для пакетних оновлень. Новий дизайн відокремлює обробіток документів від надходження в гаряче сховище та дозволяє налаштовувати обслуговування відповідно до нашого робочого навантаження. Вимірювання у виробництві до і після показують приблизно в 5 разів нижчу затримку пакетного читання, а наша внутрішня модель витрат оцінює економію щонайменше у 20% порівняно з DynamoDB.
Інфраструктура зберігання для пошуку на базі штучного інтелекту
Класичний пошуковий стек створений для читача-людини, а не для мовної моделі. Ця різниця змінює деталізацію, з якою системі потрібно готувати, зберігати та обслуговувати вебконтент.
Щоб перетворити сирий HTML на контент, готовий для моделей, наш конвеєр обробітку очищає кожну сторінку, розбиває її на семантично зв'язні уривки, обчислює їхні ембединги та зберігає уривки разом із ембедингами в базі даних. Під час виконання запиту конвеєр обслуговування вибирає текст, найбільш релевантний до запиту, і передає його моделі.
Два окремі робочі процеси
Тому шар зберігання повинен підтримувати два робочі процеси з різними вимогами: прийом оновлень із конвеєра обробітку та обслуговування читань із низькою затримкою.
З боку запису нові сторінки надходять безперервно, існуючі сторінки змінюються, а зміни в методах чанкингу або моделях ембедингів можуть вимагати повторного обробітку великих частин масиву даних. Шар зберігання повинен включати ці оновлення в постійний стан документа, відстежувати поточні версії, публікувати правильні подання для спадкових систем і підтримувати масове заповнення без повторення повного конвеєра з сирого сканування.
Натомість зчитування під час запитів запитують пакети ключів сторінок і отримують відповідний текст уривка та ембединги для ранжування й вибору уривків. Ці читання знаходяться на шляху до остаточної відповіді, тому їх потрібно виконувати швидко.
Ці робочі навантаження вимагають різних проєктних рішень. Обробіток виграє від надійних черг і можливості ефективно доставляти оновлення, повторювати спроби у разі збоїв та оновлювати існуючі записи в масовому масштабі. Обслуговування виграє від попередньо обчислених записів, теплих кешів і швидкого пакетного читання з мінімальною роботою під час запитів. Тісний зв'язок між ними може призвести до того, що робота з відновлення та масштабні оновлення конкуруватимуть із читаннями, чутливими до затримок.
У чому керована база даних не дотягує до вимог
DynamoDB забезпечила нам високу доступність сховища без тягаря експлуатації розподіленої бази даних. Спеціальна таблиця підготовлених сторінок містила уривки та ембединги для читання під час запитів, тоді як повний вміст сторінок і метадані зберігалися окремо. Однак із ростом нашого робочого навантаження важливими стали три обмеження.
По-перше, ціноутворення на основі використання стягує плату за кожен прочитаний чи записаний байт. Один запит Search API містить 100–120 ключів, які відповідають сторінкам, які нам потрібно отримати. Процес вилучення даних розбиває запит на менші пакети по 10–20 ключів сторінок із середнім розміром елемента близько 50 КБ. Повторне зчитування цих записів споживало значну ємність, тоді як безперервні сканування та зміни в моделях чанкингу або ембедингів генерували більше записів. Економіка також залежала від розміру та частоти записів, а не лише від кількості викликів API.
По-друге, керованому сервісу бракує гнучкості, необхідної для налаштування продуктивності. Операція, яка нам була потрібна, виявилася вузькою: маючи пакет ідентифікаторів сторінок, швидко повернути їхній підготовлений вміст. Ми хотіли контролювати, яка машина містить розділ, скільки пам'яті виділено для його кешу та яка репліка обробляє запит. Ці елементи керування важливі, оскільки некешоване читання, перехід між зонами або повільна репліка можуть затримати весь пакет. DynamoDB керує власним сховищем і поведінкою реплік за межами API. Ми могли налаштувати сервіс, але не могли оптимізувати його внутрішнє розміщення, кеші рушія зберігання чи політику вибору реплік відповідно до нашого робочого навантаження.
По-третє, обробіток був безпосередньо прив'язаний до записів у гарячому сховищі. Наш попередній конвеєр обробітку записував кожну підготовлену сторінку безпосередньо в DynamoDB. Тому масштабне завдання повторного обробітку перетворювалося на хвилю індивідуальних оновлень гарячого сховища, і обробіток мусив пристосовуватися до лімітів пропускної здатності та поведінки повторних спроб гарячого сховища. Ми не могли запустити MapReduce для всієї бази даних за 1–2 дні, щоб випробувати новий метод чанкингу, змінити нашу модель ембедингів чи додати нове поле. Не було незалежного потоку збережених оновлень сторінок, який би окремий сервіс міг згрупувати в пакети та доставити до гарячого сховища у власному темпі.
Вирішення цих обмежень вимагало більшого, ніж просто заміна бази даних. Нам потрібно було відокремити постійний стан документа та доставку оновлень від сховища часу запитів, а потім оптимізувати гаряче сховище спеціально для швидкого пакетного читання.
Розділення стану документа, доставки оновлень та обслуговування
Нова архітектура призначає кожну функцію різному компоненту. Pillar підтримує постійний стан документа та вирішує, що публікувати. Lorry перетворює ці експорти на партиційні пакети. CobbleDB приймає пакети та обслуговує підготовлені записи за ключем сторінки.
CobbleDB: настроюваний шлях читання
CobbleDB — це наше гаряче сховище, оптимізоване для обслуговування підготовлених записів під час запитів. Це розподілене сховище «ключ-значення»: ключі — це ідентифікатори сторінок (хешовані URL-адреси), а значення — опрацьоване представлення сторінки (її попередньо розділені на чанки уривки та вектори ембедингів для кожного чанка).
Важливою зміною є наш контроль над конфігурацією сховища.
Дані секціонуються та розподіляються між вузлами даних. Цей розподілений формат гарантує, що база даних може масштабуватися горизонтально для розміщення величезного масиву опрацьованих сторінок. Кожен розділ має три репліки на трьох різних вузлах; якщо одна копія стає недоступною, інші можуть продовжувати обслуговувати зчитування.
Кожному вузлу даних призначається підмножина розділів. Вузол зберігає свої дані за допомогою RocksDB — вбудованого рушія зберігання типу «ключ-значення» з відкритим кодом, який добре підходить для робочих навантажень із інтенсивним читанням, що живляться пакетним завантаженням.
Керовані дані можуть обслуговуватися з пам'яті, тоді використовується локальне сховище NVMe. Це дає нам прямий спосіб налаштувати баланс між пам'яттю та диском для спостережуваного робочого навантаження, замість того, щоб приймати політику внутрішнього кешування керованого сервісу.
Безстатусний маршрутизатор запитів хешує ключі на партиції та паралельно спрямовує запити до вузлів. Архітектура також містить сервіс метаданих на базі PostgreSQL та постійне сховище об'єктів.
Цей дизайн підтримує шаблони читання, яких вимагає наше робоче навантаження. Після того як запит проходить етапи пошуку та ранжування для отримання набору кандидатів релевантних сторінок, до гарячого сховища надсилається масовий запит на вміст цих сторінок. Ці запити на читання надходять до маршрутизатора та спрямовуються на відповідний вузол даних. Маршрутизатор надає перевагу надсиланню запитів на вузол у тій самій зоні доступності, щоб за можливості мінімізувати затримку між зонами. Кожен вузол даних може отримувати вміст сторінки за допомогою MultiGet — API пакетного читання RocksDB, розробленого для отримання кількоми ключами одночасно з низькою затримкою. Якщо вузол відповідає повільно, система також може надіслати запит до іншої репліки розділу на іншому вузлі, покращуючи хвостову затримку.
Ми також можемо уникнути впровадження поширених функцій, які не потрібні нашому робочому процесу. Наприклад, більшість баз даних пропонують гарантії узгодженості та підтримку транзакцій, але на наше гаряче сховище не покладаються вимоги транзакцій чи синхронізованих реплік. Припустимим є невелика затримка між записом документа та його доступністю для читання, а також нормально, якщо одна репліка приймає дані швидше за іншу. Пропуск непотрібних нам функцій зменшує накладні витрати та вартість нашої системи.
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 для приймання та асинхронно застосовують оновлення пакетів у хронологічному порядку. Це означає, що повільний вузол або вузол, що відновлюється, може наздогнати роботу у власному темпі, а не сповільнювати решту системи.
Новий шлях відокремлює транзакцію негайного обробітку від завантаження в гаряче сховище та забезпечує шлях із можливістю повторного відтворення для інкрементних оновлень і масових відбудов. Такий поділ праці гарантує, що оновлення постійного стану документа не заважають затримці шляху читання.
Перевірка
У 5 разів швидше
Після міграції гарячого сховища медіанна затримка пакетного читання впала з 31,4 мс до 5,60 мс. Показник p90 знизився з 56,7 мс до 9,77 мс, а p99 — зі 123 мс до 24,2 мс. Співвідношення коливаються від 5,08× до 5,80×, при цьому спостерігаються покращення як для типових запитів, так і для хвостих затримок.
Ці цифри отримані з пакетних зчитувань, де кожен запит запитує приблизно 10–15 ключів. Середній розмір елемента корисного навантаження становить 50 КБ. Обидві системи опитувалися на швидкості близько 200 тис. rps, хоча ми також запускали тестування навантаження до 500 тис. rps без погіршення продуктивності.
Щоби відокремити внесок CobbleDB, усі інші аспекти налаштування залишалися незмінними. Проте це залишається спостережним порівнянням «до та після»; системи обслуговували реальний робочий трафік у різний час, а не однакові запити за контрольованих умов. Тому ми також виміряли продуктивність обох систем на синтетичному бенчмарку. Щоб наблизитися до запитів у реальному середовищі, ми надсилали до обох систем запити з пакетами з 10–15 ключів та зі значеннями від 100 Б до 100 КіБ.
DynamoDB стикається з труднощами в цьому контексті, оскільки її керований шлях читання неможливо налаштувати для пакетних зчитувань такого розміру. Натомість CobbleDB оптимізована саме для такого середовища, оскільки ми керуємо розміщенням, кешуванням і контролем реплік. Вона групує ключі за партиціями, виконує читання паралельно до реплік розділу та може страхувати повільні читання через інші репліки. Наші результати демонструють, що ці проєктні рішення перетворюються на швидше читання у виробничому середовищі.
На 20% дешевше
CobbleDB також значно дешевша за DynamoDB. Окрім сховища, DynamoDB також стягує плату за кожен прочитаний чи записаний байт. У масштабах нашого масиву даних і трафіку обслуговування ці плати на основі використання стають суттєвою частиною загальної вартості. Постійні оновлення споживають ємність запису, тоді як кожен пакет підготовлених сторінок, отриманих для запиту, споживає ємність читання.
Ми обчислили економію коштів на основі внутрішніх оцінок розміру сховища, одиниць ємності запису та одиниць ємності читання. На будь-якому рівні зобов'язань CobbleDB принаймні на 20% дешевша за DynamoDB. Реальна економія, ймовірно, навіть вища, оскільки ці результати не беруть до уваги потенційну економію резервного копіювання завдяки стисненню.
Як рій агентів штучного інтелекту допомагає нам це створювати
Створення CobbleDB та супутньої інфраструктури було далеко не тривіальним завданням. Лише ядро бази даних CobbleDB налічує близько 40 000 рядків на Rust, а міграція також вимагала масштабної роботи для перевірки коректності та поведінки у разі збоїв у виробничому масштабі, інтеграції CobbleDB як із конвеєрами обробітку, так і обслуговування, а також безпечного переведення живого трафіку.
Ми прискорили цю роботу, використовуючи внутрішню систему, яка керує роєм агентів кодування. Система розроблена так, щоб бути надзвичайно стійкою; вона зберігає цілі проєкту, активні ризики, історію репозиторію та попередні рішення протягом різних сеансів.
Ми призначили колектив таких проактивних постійно активних агентів для проєкту CobbleDB. Їхнім першим завданням було побудувати точну модель роботи, яка вже виконується. Вони провели аудит каналу проєкту та активних тредів, зв'язали невирішені завдання з їхніми pull request'ами та власниками, а також надали актуальний звіт про стан системи та подальші дії. Звідти вони діяли в кількох частинах інженерного циклу:
- Переглянуто код та інфраструктурні зміни.
- Виявлено блокери, які було легко пропустити, зокрема небезпечні припущення щодо відновлення, застарілі посилання на збірки та конфігурації, які призвели б до збою під час виконання.
- Підготовлені цільові виправлення, тести, зміни моніторингу та експлуатаційна документація.
- Відстежував CI, перевірки та інфраструктурні шлюзи, залишаючи виробничі дії явно під контролем людини.
- Контрольовані розгортання та вправи з резервного копіювання чи відновлення, після чого спостережуваний стан системи порівнювався з критеріями успішності розгортання.
- Підтримував звіти про стан проєктів, щоб інженери бачили, що було випущено, що залишилося заблокованим і де все ще потрібне людське судження.
Інженери й далі визначали архітектуру, переглядали важливі зміни та уповноважували виробничі операції. Агенти взяли на себе більшу частину постійного інспектування та доведення до кінця між цими рішеннями. Це дозволило невеликій команді ефективно просувати проєкт — основна інфраструктура CobbleDB була створена двома інженерами-людьми та сотнями агентів штучного інтелекту за два місяці.
Висновок
CobbleDB не є універсальною базою даних; вона створена спеціально для нашого конкретного робочого навантаження з повторюваними пакетними зчитуваннями записів підготовлених сторінок, які надходять через асинхронний конвеєр обробітку. Розділяючи постійний стан, доставку оновлень та обслуговування під час запитів, ми можемо незалежно оптимізувати кожну функцію, не змушуючи єдиний інтерфейс бази даних підлаштовуватися під усі них і не перекладаючи роботу з обслуговування на шлях живих запитів. Міграція на CobbleDB значно покращила як медіанну, так і хвостову затримку, одночасно знизивши витрати на інфраструктуру.
Продовжуючи розробку інфраструктури, що є нативною для штучного інтелекту, власними силами, ми можемо оптимізувати продуктивність і вартість у спосіб, неможливий у разі прив'язки до сторонніх реалізацій і моделей ціноутворення. Потужні агенти штучного інтелекту ще більше зміщують баланс на користь створення власними силами, а не купівлі.
Ми плануємо незабаром відкрити вихідний код CobbleDB, щоб інші команди, які створюють пошукові системи на базі штучного інтелекту, могли використовувати той самий шар зберігання, який робить пошук швидшим і дешевшим всередині наших продуктів.
Цитування
@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}
}