CobbleDB: Reconstrución do almacenamento de busca de IA para menor latencia e custo

Unha arquitectura desacoplada con estado duradeiro separado, actualizacións por lotes e recuperación en tempo de consulta.

AutoresPerplexity Engineering

A busca nativa de IA impón diferentes requisitos sobre como se debe procesar, almacenar e servir o contido web. Para satisfacer esas demandas a escala, estivemos reconstruíndo toda a pila desde cero para mellorar o rendemento e reducir o custo.

Construímos a nosa propia infraestrutura de rastreexo, indexación e clasificación, modelos de incrustación e motores de inferencia para potenciar un procesamento eficiente e un servizo rápido. Agora estamos ampliando este enfoque ao almacenamento, o tecido conectivo ao que escribe o procesamento e do que le o servizo.

As bases de datos xestionadas como DynamoDB proporcionaron un almacenamento fiable, pero tivemos un control limitado sobre como se almacenaban os datos e se servían as lecturas, e os prezos baseados no uso creceron co tamaño do noso índice e o volume de consultas. Por outra banda, a nosa canalización anterior acoplaba o procesamento de documentos directamente á base de datos de servizo, o que dificultaba a xestión das actualizacións de forma independente das lecturas en directo.

Coa axuda dun novo efémero interno de axentes de codificación, reconstruímos esta capa arredor de CobbleDB, un almacén quente chave-valor, apoiado por Pillar para o estado duradeiro do documento e a publicación, e Lorry para actualizacións por lotes. O redeseño separa o procesamento de documentos da inxestión do almacén quente e permitenos axustar o servizo á nosa carga de traballo. As medicións de produción antes e despois amosan unha latencia de lectura por lotes aproximadamente 5 veces menor, e o noso modelo de custos interno estima polo menos un 20% de aforro en relación con DynamoDB.

Infraestrutura de almacenamento para a busca nativa de IA

Unha pila de busca clásica está construída para un lector humano, non para un modelo de linguaxe. Esa diferenza cambia a granularidade na que o sistema necesita preparar, almacenar e servir contido web.

Para converter HTML bruto en contido listo para o modelo, a nosa canalización de procesamento limpa cada páxina, divídea en pasaxes semanticamente coherentes, calcula as súas incrustacións e almacena as pasaxes e incrustacións xuntas na base de datos. En tempo de consulta, a canalización de servizo selecciona o texto máis relevante para a consulta e pásao ao modelo.

Dous fluxos de traballo distintos

Polo tanto, a capa de almacenamento debe admitir dous fluxos de traballo con demandas diferentes: inxerir actualizacións da canalización de procesamento e servir lecturas de baixa latencia.

No lado da escritura, as novas páxinas llegan continuamente, as páxinas existentes cambian e os cambios nos métodos de fragmentación ou nos modelos de incrustación poden requirir reprocesar grandes porcións do corpus. A capa de almacenamento debe incorporar estas actualizacións no estado duradeiro do documento, rastrexar as versións actuais, publicar as representacións correctas nos sistemas posteriores e admitir grandes enchidos sen repetir a canalización completa dende o rastreexo bruto.

Pola contra, as lecturas en tempo de consulta solicitan lotes de chaves de páxina e recuperan o texto da pasaxe correspondente e as incrustacións para a clasificación e a selección de pasaxes. Estas lecturas están na ruta cara á resposta final, polo que deben completarse rapidamente.

Estas cargas de traballo requiren deseños diferentes. O procesamento benefíciase de colas duradeiras e da capacidade de entregar actualizacións de forma eficiente, reintentar fallos e poñer ao día os rexistros existentes a escala. O servizo benefíciase de rexistros precomputados, cachés quentes e lecturas rápidas por lotes cun traballo mínimo en tempo de consulta. O acoplamento estreito entre ambos pode facer que o traballo de recuperación e as actualizacións grandes compitan con lecturas sensibles á latencia.

Onde falla a base de datos xestionada

DynamoDB achegounos un almacenamento altamente dispoñible sen a carga de operar unha base de datos distribuída. Unha táboa dedicada de páxinas preparadas contiña pasaxes e incrustacións para lecturas en tempo de consulta, mentres que o contido completo da páxina e os metadatos almacenábanse por separado. Non obstante, a medida que crecía a nosa carga de traballo, fixéronse importantes tres limitacións.

Segundo, o prezo baseado no uso cobra cada byte lido ou escrito. Unha única solicitude da API de busca contén 100-120 chaves correspondentes ás páxinas que necesitamos recuperar. O proceso de recuperación de datos divide unha solicitude en lotes máis pequenos de 10-20 chaves de páxina cun tamaño medio de elemento de aproximadamente 50 KB. A lectura repetida deses rexistros consumiu unha capacidade substancial, mentres que os rastrexos continuos e os cambios nos modelos de fragmentación ou incrustación xeraron máis escrituras. A economía tamén dependía do tamaño e da frecuencia dos rexistros, non só do número de chamadas á API.

Segundo, o servizo xestionado carece da flexibilidade que necesitamos para o axuste do rendemento. A operación que necesitabamos era reducida: dado un lote de identificadores de páxina, devolver o seu contido preparado rapidamente. Queriamos control sobre qué máquina contiña unha partición, cantos memoria se dedicaban á súa caché e qué réplica xestionaba unha solicitude. Eses controis importan porque unha lectura non en caché, un salto entre zonas ou unha réplica lenta poden atrasar todo o lote. DynamoDB xestiona o seu propio almacenamento e comportamento de réplica detrás da API. Podíamos configurar o servizo, pero non podíamos axustar a súa colocación interna, as cachés do motor de almacenamento ou a política de selección de réplicas arredor da nosa carga de traballo.

Terceiro, o procesamento estaba ligado directamente ás escrituras no almacén quente. A nosa canalización de procesamento anterior escribía cada páxina preparada directamente en DynamoDB. Polo tanto, un traballo de reprocesamento grande converteuse nunha onda de actualizacións individuais do almacén quente, e o procesamento tivo que adaptarse aos límites de rendemento e ao comportamento de reintento do almacén quente. Non puidemos executar MapReduce en toda a base de datos en 1-2 días para probar un novo método de fragmentación, cambiar o noso modelo de incrustación ou engadir un novo campo. Non había ningún fluxo independente de actualizacións de páxinas gardadas que un servizo separado puidese agrupar por lotes e entregar ao almacén quente ao seu propio ritmo.

Abordar estas limitacións requiriu máis que substituír a base de datos. Necesitamos separar o estado duradeiro do documento e a entrega de actualizacións do almacenamento en tempo de consulta, e logo optimizar o almacén quente especificamente para lecturas rápidas por lotes.

Separar o estado do documento, a entrega de actualizacións e o servizo

A nova arquitectura asigna cada responsabilidade a un compoñente diferente. Pillar mantén un estado duradeiro do documento e decide que publicar. Lorry convérte eses exportacións en lotes particionados. CobbleDB inxire os lotes e serve rexistros preparados por chave de páxina.

Antes e despois da migración do almacenamento. Pillar e Lorry separan o estado duradeiro dos documentos e a entrega de actualizacións das lecturas no tempo de consulta servidas por CobbleDB.

CobbleDB: a ruta de lectura axustable

CobbleDB é o noso almacén quente optimizado para servir rexistros preparados en tempo de consulta. É un almacén chave-valor distribuído: as chaves son identificadores de páxina (URL hash) e os valores son a representación procesada da páxina (as súas pasaxes prefragmentadas e incrustacións vectoriais por fragmento).

O cambio importante é o noso control sobre a configuración do almacenamento.

Os datos están particionados e divididos entre os nodos de datos. Este formato distribuído garante que a base de datos poida escalar horizontalmente para acomodar un corpus enorme de páxinas procesadas. Cada partición ten tres réplicas en tres nodos diferentes; se unha copia deixa de estar dispoñible, as demais poden continuar servindo lecturas.

A cada nodo de datos asígnase un subconxunto das particións. O nodo almacena os seus datos usando RocksDB, un motor de almacenamento chave-valor integrado de código aberto moi axeitado para cargas de traballo pesadas en lectura alimentadas por inxestión por lotes.

Os datos en caché pódense servir desde a memoria, mentres que as lecturas non en caché usan almacenamento NVMe local. Isto ofrécenos unha forma directa de axustar o equilibrio entre memoria e disco para a carga de traballo observada, en lugar de aceptar a política de caché interna dun servizo xestionado.

Un enrutador de consultas sen estado fai o hash das chaves ás particións e dirixe as solicitudes aos nodos en paralelo. A arquitectura tamén inclúe un servizo de metadatos respaldado por PostgreSQL e almacenamento de obxectos duradeiro.

Deseño admite os patróns de lectura que demanda a nosa carga de traballo. Despois de que unha consulta pasa pola recuperación e a clasificación para obter un conxunto candidato de páxinas relevantes, consúltase o almacén quente en masa para obter o contido desas páxinas. Estas solicitudes de lectura llegan ao enrutador e son dirixidas a un nodo de datos correspondente. O enrutador prioriza o envío de solicitudes a un nodo na mesma zona de dispoñibilidade para minimizar a latencia entre zonas, se é posible. Cada nodo de datos pode recuperar o contido da páxina usando MultiGet, unha API de lectura por lotes de RocksDB deseñada para recuperar múltiples chaves simultáneamente cunha baixa latencia. Se un nodo é lento en responder, o sistema tamén pode consultar outra réplica de partición nun nodo diferente, mellorando a latencia de cola.

Tamén podemos evitar a implementación de funcións comúns que o noso fluxo de traballo non require. Por exemplo, a maioría das bases de datos ofrecen garantías de consistencia e soporte para transaccións, pero o noso almacén quente non require transaccións nin réplicas sincronizadas. É aceptable que haxa un breve intervalo entre o momento en que se escribe un documento e o momento en que está dispoñible para a lectura, e tamén está ben que unha réplica inxira datos máis rápido que outra. Omitir funcións que non necesitamos reduce a sobrecarga e o custo do noso sistema.

Un enrutador sen estado agrupa as chaves das páxinas por partición e le as réplicas en paralelo. As lecturas lentas poden enviarse a outra réplica; a primeira resposta de cada partición combínase no resultado.

Pillar e Lorry: a ruta de escritura duradeira

Unha capa de almacenamento separada fai un seguimento de todo o que rastrexamos e procesamos, fusionando as actualizacións no estado do documento versionado e publicando as representacións necesarias en CobbleDB e no índice de busca. Para este propósito creamos Pillar, a nosa capa de estado e publicación de documentos.

Pillar está respaldado por YTsaurus. Almacena compoñentes de páxina (como metadatos, fragmentos e incrustacións) en familias de táboas de YTsaurus separadas. Tamén fai un seguimento das versións de fragmentos e incrustacións, permitindo que múltiples representacións coexistan sen sobrescribirse entre si para que os modelos subxacentes poidan cambiar. Dado que Pillar e YTsaurus usan almacenamento HDD barato, podemos almacenar moitos máis documentos dos que almacenabamos anteriormente en DynamoDB.

Pillar fai un seguimento de subconxuntos, ou grupos de páxinas definidos por políticas, como páxinas recentes ou páxinas de alto valor. A pertenza ao subconxunto determina que actualizacións deben transmitirse aos compoñentes posteriores como CobbleDB. CobbleDB usa almacenamento NVMe máis caro, polo que só exportamos os documentos que realmente queremos usar nos resultados da busca. Cando se actualiza o contido dunha páxina, ou se se engade ou elimina dun subconxunto, Pillar é responsable de poñer en cola o traballo de exportación correspondente.

As transaccións atómicas de YTsaurus abranguen o estado, as intencións de exportación e a entrada consumida para garantir a corrección. Para que unha transacción se complete con éxito, o sistema debe actualizar o estado duradeiro, marcar a entrada como contabilizada e poñer en cola todas as exportacións necesarias; se a transacción se aborta, non se fai ningún cambio en ningunha capa.

CobbleDB agarda que as actualizacións estean agrupadas por lotes segundo a partición. As exportacións de Pillar convértense a este formato mediante un servizo sen estado que chamamos Lorry. A súa única tarefa é ler os rexistros de exportación dunha cola persistente aliñada con particións, acumulalos en ficheiros de lotes por partición e entregalos a CobbleDB.

Entre Lorry e Cobble, un protocolo adicional separa o plano de datos (S3) do plano de control. Lorry escribe o lote en S3 baixo un identificador único e rexistra o lote dentro de Cobble. As réplicas de partición de Cobble consultan de forma independente a API do plano de control ou de datos para o seguinte lote de S3 que se vai inxerir e aplican de forma asíncrona as actualizacións por lotes en orde cronolóxica. Isto significa que un nodo lento ou en recuperación pode poñerse ao día ao seu propio ritmo, en lugar de retardar o resto do sistema.

Pillar publica exports duradeiros, e Lorry escribe lotes particionados en S3 e rexístraos na plataforma de control. Cada réplica de CobbleDB descobre e ingire de forma independente os seus lotes por orde cronolóxica.

A nova ruta separa a transacción de procesamento inmediato da inxestión do almacén quente e ofrece unha ruta reproducible para actualizacións incrementais e reconstrucións masivas. Esta división do traballo garante que as actualizacións do estado duradeiro do documento non interferiran coa latencia da ruta de lectura.

Validación

5x máis rápido

Despois de migrar o almacén quente, a latencia mediana de lectura por lotes baixou de 31,4 ms a 5,60 ms. O p90 baixou de 56,7 ms a 9,77 ms, e o p99 baixou de 123 ms a 24,2 ms. As proporcións oscilan entre 5,08× e 5,80×, con melloras tanto nas solicitudes típicas como na cola.

Latencia de lectura por lotes en produción antes e despois da migración: a P50 baixou de 31.4 a 5.60 ms, a P90 de 56.7 a 9.77 ms, e a P99 de 123 a 24.2 ms. Trátase de latencias de lectura do almacenamento quente a partir do tráfico real en distintos momentos, non de solicitudes emparelladas nin de latencia de resposta de extremo a extremo.

Estes números derivanse de lecturas por lotes, onde cada consulta solicita aproximadamente 10-15 chaves. O tamaño medio do elemento de carga útil é de 50 KB. Ambos os sistemas foron consultados a aproximadamente 200 mil rps, aínda que tamén executamos probas de carga de ata 500 mil rps sen degradación.

Para illar a contribución de CobbleDB, tódolos demais aspectos da configuración mantiveronse constantes. Non obstante, esta segue a ser unha comparación observacional antes e despois; os sistemas serviron tráfico de produción real en momentos diferentes en lugar de ás mesmas solicitudes en condicións controladas. Polo tanto, tamén medimos o rendemento de ambos os sistemas nun test de rendemento sintético. Para achegarnos ás solicitudes vistas na produción, consultamos ambos con lotes de 10-15 chaves e valores que oscilaban entre 100B e 101 KiB.

Latencia sintética de lectura por lotes para lotes de 10 e 15 chaves a través de tamaños de valor de 100 B a 100 KiB. Cada punto é a media da latencia P50, P90 ou P99 ao nivel de execución de dúas execucións por backend e configuración.

DynamoDB ten dificultades neste contexto porque a súa ruta de lectura xestionada non se pode axustar para lecturas por lotes deste tamaño. Pola contra, CobbleDB está optimizada exactamente para este escenario, xa que xestionamos a colocación, a caché e os controis de réplicas. Agrupa as chaves por partición, executa lecturas en paralelo contra as réplicas de partición e pode protexer as lecturas lentas contra outras réplicas. Os nosos resultados demostran que estas opcións de deseño traduciranse en lecturas máis rápidas nun contorno de produción.

20% máis barato

CobbleDB é tamén significativamente máis barato que DynamoDB. Ademais do almacenamento, DynamoDB tamén cobra cada byte lido ou escrito. A escala do noso corpus e do tráfico de servizo, estes cargos baseados no uso convértense nunha parte substancial do custo total. As actualizacións continuas consumen capacidade de escritura, mentres que cada lote de páxinas preparadas recuperadas para unha consulta consume capacidade de lectura.

Calculamos a aforro de custos en base a estimacións internas do tamaño do almacenamento, unidades de capacidade de escritura e unidades de capacidade de lectura. En cada nivel de compromiso, CobbleDB é polo menos un 20% máis barato que DynamoDB. É probable que o aforro real sexa aínda maior, xa que estes resultados non teñen en conta o probable aforro en copias de seguridade debido á compresión.

Como un enxame de axentes de IA nos axudou a construílo

Construír CobbleDB e a infraestrutura circundante estivo lonxe de ser trivial. Só a base de datos central de CobbleDB ten aproximadamente 40.000 liñas de Rust, e a migración tamén requiriu un traballo extensu para verificar a corrección e o comportamento ante fallos a escala de produción, integrar CobbleDB coas rutas de procesamento e servizo, e facer a transición do tráfico en directo de forma segura.

Aceleramos este esforzo utilizando un sistema interno que opera un efémero de axentes de codificación. O sistema está deseñado para ser altamente persistente; conserva os obxectivos do proxecto, os riscos activos, o historial do repositorio e as decisións anteriores entre sesións.

Asignamos un colectivo de tales axentes proactivos e sempre activos ao proxecto CobbleDB. A súa primeira tarefa foi construír un modelo preciso do traballo que xa estaba en marcha. Auditaron a canle do proxecto e os fíos activos, conectaron os elementos pendentes coas súas pull requests e propietarios, e produciron un informe actualizado da saúde do sistema e as seguintes accións. A partir de aí, operaron en varias partes do bucle de enxeñaría:

  • Revisáronse o código e os cambios na infraestrutura.
  • Amosáronse bloqueadores que eran fáciles de pasar por alto, incluíndo suposicións de restauración inseguras, referencias de compilación obsoletas e configuracións que fallarían en tempo de execución.
  • Preparáronse correccións enfocadas, probas, cambios de monitorización e documentación operacional.
  • Fíxose un seguimento das portas de CI, revisión e infraestrutura á vez que se mantiñan as accións de produción explicitamente propiedade humana.
  • Despregamentos monitorizados e exercicios de copia de seguridade ou restauración, comparando despois o estado observable do sistema cos criterios de éxito do despregamento.
  • Mantivéronse os informes do proxecto para que os enxeñeiros puidesen ver o que se enviara, o que quedaba bloqueado e onde aínda se requiría o criterio humano.

Os enxeñeiros aínda establecen a arquitectura, revisan os cambios consecuentes e autorizan as operacións de produción. Os axentes manexaron gran parte da inspección continua e do seguimento entre esas decisións. Isto permitiu a un pequeno equipo impulsar o proxecto de forma eficiente: a infraestrutura central de CobbleDB foi construída por dous enxeñeiros humanos e centos de axentes de IA en dous meses.

Conclusión

CobbleDB non é unha base de datos de uso xeral; está construída especificamente para a nosa carga de traballo de lecturas por lotes repetidas de rexistros de páxinas preparadas, alimentadas por unha canalización de procesamento asíncrono. Ao separar o estado duradeiro, a entrega de actualizacións e o servizo en tempo de consulta, podemos optimizar cada responsabilidade de forma independente sen obrigar a que unha única interface de base de datos se adapte a todas elas nin empurrar o mantemento á ruta de consulta en directo. A migración a CobbleDB mellorou significativamente tanto a latencia mediana como a de cola á vez que reduciu os custos de infraestrutura.

A medida que seguimos desenvolvendo infraestrutura nativa de IA internamente, podemos optimizar o rendemento e os custos de formas que non son posibles cando estamos ligados a implementacións e modelos de prezos de terceiros. Os potentes axentes de IA desprazan o cálculo aínda máis a favor de construír fronte a mercar.

Planeamos abrir o código fonte de CobbleDB en breve, para que outros equipos que constrúen sistemas de busca nativos de IA poidan beneficiarse da mesma capa de almacenamento que fai que a busca sexa máis rápida e barata dentro dos nosos produtos.

Cita

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