Presentamos Loon: un nuevo motor de almacenamiento para datos vectoriales que nunca dejan de cambiar
Puntos clave
Esta es una inmersión de ingeniería larga y detallada, así que aquí están los puntos clave antes de entrar en los detalles.
- Los datasets de IA no son tablas estáticas. Las mismas filas siguen cambiando a medida que los equipos reemplazan modelos de embeddings, añaden vectores dispersos, revisan subtítulos, rellenan etiquetas, reconstruyen índices y ejecutan análisis offline.
- Los diseños de almacenamiento tradicionales fallan de tres maneras: las columnas de vectores largas hacen que los backfills sean costosos, un único formato de archivo no puede servir bien tanto a los escaneos como a las lecturas puntuales, y el almacenamiento privado de bases de datos obliga a los pipelines externos a crear copias adicionales de la fuente de verdad.
- Loon es el nuevo motor de almacenamiento para Milvus y Zilliz Vector Lakebase. Está construido en torno a formatos de archivo híbridos, alineación de ID de fila y un Manifest que define el estado versionado del dataset.
- El objetivo es permitir que un único dataset vectorial admita búsqueda online, análisis offline, backfills, compactación y cómputo externo sin copiar, reescribir o reimportar datos constantemente.
Introducción
Durante un tiempo, hubo un argumento contra las bases de datos vectoriales que sonaba razonable.
Las bases de datos tradicionales ya almacenan enteros, cadenas, JSON, blobs e índices. ¿Por qué no añadir un tipo _vector_, construir un índice ANN junto a él y darlo por terminado?
Para la búsqueda semántica temprana, eso funciona lo suficientemente bien. Una columna vectorial más un índice puede soportar una demo, una pequeña aplicación RAG o una función de búsqueda interna. El problema aparece más tarde, cuando el dataset empieza a comportarse menos como una tabla y más como un sistema de datos de IA.
Un dataset vectorial de producción tiene filas, claves primarias, campos escalares y columnas consultables. En ese sentido, se parece a una tabla de base de datos. Pero también tiene la escala y la forma de flujo de trabajo de un data lake. Puede contener cientos de millones de registros. Es leído y reescrito repetidamente por Spark, Ray, DuckDB, pipelines de entrenamiento, trabajos de evaluación y sistemas de calidad de datos.
También depende del almacenamiento de objetos. Los objetos fuente suelen ser videos, imágenes, PDF, archivos de audio o documentos web que permanecen en S3, GCS, OSS u otro almacén de objetos. La base de datos almacena referencias, metadatos, características derivadas e índices. Luego añade cosas que los modelos de almacenamiento tradicionales no fueron diseñados para gestionar como objetos de primera clase: embeddings densos, vectores dispersos, subtítulos, índices vectoriales, índices de texto, registros de eliminación, estadísticas, versiones de modelos, versiones de parsers, referencias a blobs externos y las relaciones de versión entre todos ellos.
Ahí es donde “simplemente añadir una columna vectorial” empieza a fallar. El problema no es si una base de datos puede almacenar bytes vectoriales. Muchos sistemas pueden. La pregunta más difícil es si el modelo de almacenamiento puede manejar cómo cambian los datos vectoriales, cómo se consultan y cómo se comparten en toda la pila de datos de IA.
Por eso construimos Loon, el nuevo motor de almacenamiento para Milvus y Zilliz Vector Lakebase (la próxima evolución de Zilliz Cloud).
Loon está diseñado con tres ideas:
- Usar diferentes formatos físicos para distintos tipos de columnas.
- Alinear esas columnas mediante un espacio compartido de ID de fila.
- Usar un Manifest para definir el estado versionado del dataset.
Para ver por qué importan esas piezas, empecemos con un flujo de trabajo multimodal común.
Un dataset vectorial nunca está realmente terminado.
Imagina un equipo de IA que construye un dataset de video para entrenamiento multimodal.
Un video largo se sube al almacenamiento de objetos. Un pipeline lo corta en clips en función de cambios de escena, límites de toma o ventanas de tiempo. Los clips demasiado largos o demasiado cortos, borrosos, duplicados o de baja calidad se filtran. Los clips restantes son puntuados por un modelo estético, subtitulados por otro modelo, convertidos en embeddings por un modelo de visión-lenguaje y almacenados en una base de datos vectorial para búsqueda, deduplicación y filtrado de datos de entrenamiento.
A alto nivel, el flujo de trabajo parece simple:
video
→ clips
→ metadata
→ aesthetic_score
→ caption
→ embedding
→ search / dedup / training data filtering
Pero el dataset no llega completamente formado.
- En la primera semana, la tabla puede contener solo
clip_id,video_id,start_offsetyduration. - En la segunda semana, el equipo añade
aesthetic_score. - En la tercera semana, se ejecuta un modelo de generación de subtítulos, y cada clip obtiene un
caption. - En la cuarta semana, el primer modelo de embeddings entra en línea, y cada clip obtiene un embedding CLIP de 768 dimensiones.
- Un mes después, el equipo cambia de modelo y rellena
embedding_v2, ahora con 1024 dimensiones. - Dos meses después, la búsqueda híbrida se convierte en un requisito, así que el equipo añade una columna de vectores dispersos.
- Tres meses después, los subtítulos pasan por revisión humana y deben corregirse in situ.
El dataset nunca se completó. Siguió acumulando nuevas interpretaciones de las mismas filas subyacentes.
Esa es una de las diferencias fundamentales entre los datos vectoriales y los datos empresariales tradicionales. La misma fila se reprocesa una y otra vez. Y la escala convierte esto de una molestia en un problema de almacenamiento: los datasets multimodales a menudo no tienen millones de registros, sino cientos de millones o miles de millones. LAION-5B es una referencia útil para entender la forma: miles de millones de pares imagen-texto, cada uno con metadatos, subtítulos y embeddings. Así que la parte difícil no es la primera inserción. La parte difícil es todo lo que ocurre después de que el dataset empieza a evolucionar. Esa evolución expone tres problemas.
El primer problema: las columnas largas encarecen la amplificación de escritura
Los formatos columnares como Parquet son excelentes para muchas cargas de trabajo analíticas. Funcionan bien cuando los esquemas son bastante estables, los datos se leen con más frecuencia de la que se reescriben, los escaneos solo tocan un subconjunto de columnas y la compresión importa. Ese es el mundo para el que se optimizaron muchos formatos analíticos.
Las filas vectoriales son mucho más anchas que las filas analíticas
TPC-H lineitem es una buena referencia. Tiene 16 columnas: claves enteras, valores decimales, fechas, cadenas cortas y un pequeño campo de comentario. Una fila sin comprimir ocupa aproximadamente 150 bytes. Después de la compresión, puede ser mucho más pequeña. Con un grupo de filas de 64 MB, un sistema de almacenamiento puede empaquetar cientos de miles de filas en un solo grupo.
Los datasets vectoriales no se parecen a eso.
Un dataset imagen-texto al estilo LAION se parece mucho más a lo que muchas canalizaciones de IA producen hoy. Cada fila sigue teniendo metadatos ordinarios: una URL, un subtítulo, ancho, alto, puntuaciones de calidad, etiquetas, etc. Pero una vez que se añade el embedding, la forma física de la fila cambia.
Un vector CLIP de 768 dimensiones ocupa alrededor de 1,5 KB en fp16 o 3 KB en fp32. Esa única columna puede ser mucho más grande que una fila completa de TPC-H lineitem.
Y 768 dimensiones no son inusuales ni grandes según los estándares actuales. Un embedding de 1024 o 2048 dimensiones es común en las canalizaciones multimodales. text-embedding-3-large de OpenAI llega hasta 3072 dimensiones, lo que supone unos 12 KB por vector en fp32.
La comparación es contundente:
| Forma del dataset | Tamaño aproximado de fila | Campo dominante |
|---|---|---|
| TPC-H lineitem | ~150 bytes sin comprimir | escalares y cadenas cortas |
| Fila al estilo LAION con vector fp16 de 768 dimensiones | ~1,5 KB+ | embedding |
| Fila al estilo LAION con vector fp32 de 768 dimensiones | ~3 KB+ | embedding |
| Fila con vector fp32 de 3072 dimensiones | ~12 KB+ solo para el vector | embedding |
En muchos datasets de IA, la columna vectorial no es simplemente otro campo. Físicamente, es la mayor parte de la fila. Eso cambia el coste de la evolución del esquema.
Añadir una columna vectorial puede significar cientos de gigabytes
Supongamos que un dataset tiene 100 millones de clips de vídeo. Añadir una nueva columna de embeddings fp32 de 1024 dimensiones implica escribir aproximadamente 400 GB de datos vectoriales sin procesar. Eso no incluye estadísticas, índices, actualizaciones de metadatos, sobrecarga del almacenamiento de objetos, validación ni integración con la ruta de servicio.
Si el equipo agrega una o dos columnas similares a vectores cada mes, como embedding_v2, sparse_vector o características de rerank, la evolución del esquema se convierte en un trabajo recurrente de ingeniería de datos medido en cientos de gigabytes o terabytes.
Pequeñas actualizaciones lógicas pueden desencadenar grandes reescrituras físicas
Las actualizaciones son igual de importantes.
En los sistemas columnares, los datos antiguos normalmente no se actualizan in situ. Un registro de eliminación registra lo que cambió, y la compactación reescribe más tarde las filas activas en nuevos archivos. Ese modelo es manejable cuando las filas son pequeñas.
Con datos vectoriales, una pequeña actualización lógica puede desencadenar una gran reescritura física.
Un trabajo de revisión humana puede corregir solo unos cientos de bytes en un pie de foto. Pero si el pie de foto, el vector denso, el vector disperso y otras características derivadas comparten el mismo ciclo de vida de archivo físico, el sistema puede terminar reescribiendo también los vectores. El cambio lógico es pequeño. La E/S física puede ser enorme.
Este es el problema de amplificación de escritura en el almacenamiento vectorial. La parte costosa no es solo que los vectores sean grandes. Es que los campos derivados grandes y los campos mutables pequeños a menudo quedan vinculados por un diseño de almacenamiento que los trata como una sola unidad.
Para conjuntos de datos de IA, el backfill es una carga de trabajo rutinaria
Para las tablas analíticas tradicionales, la evolución del esquema puede ocurrir solo ocasionalmente. Para los conjuntos de datos de IA, es rutinaria. Los modelos de subtitulado se actualizan. Los modelos de embedding se reemplazan. Los vectores dispersos se agregan más tarde. Aparecen características de rerank. Las etiquetas humanas se corrigen. Las etiquetas de gobernanza se rellenan retrospectivamente. Los índices se reconstruyen.
Estas operaciones no son simples anexiones. Con frecuencia modifican o amplían filas existentes.
Por eso el almacenamiento vectorial no puede optimizar solo el rendimiento de escaneo. También tiene que hacer que los backfills y las actualizaciones parciales sean más baratos.
El segundo problema: los mismos datos deben admitir escaneos y lecturas puntuales
Después de escribir los datos, la ruta de lectura se divide. El mismo conjunto de datos vectoriales normalmente tiene dos patrones de acceso distintos: escaneo analítico y lecturas puntuales.
Las cargas de trabajo analíticas quieren escaneos amplios y comprimidos
Una canalización puede ejecutar filtros como:
WHERE aesthetic_score > 0.8 AND duration > 5
O puede ejecutar análisis offline, evaluación completa de embeddings, estadísticas BM25, construcción de bitmaps, comprobaciones de calidad de datos, conteos y agrupaciones.
Este patrón lee muchas filas pero solo unas pocas columnas. Prefiere E/S secuencial, grupos de filas más grandes, compresión, poda de columnas, decodificación por lotes y ejecución vectorizada.
Los grupos de filas grandes ayudan aquí. Permiten que una sola solicitud de E/S recupere una gran cantidad de datos útiles, mejoran la eficiencia de la compresión y proporcionan al motor de ejecución suficientes datos contiguos para amortizar la sobrecarga. Cuando varias columnas se leen juntas, mantenerlas organizadas para el rendimiento de escaneo también ayuda a reducir los fallos de caché durante la ejecución vectorizada.
Parquet es sólido en esta ruta.
Los resultados de ANN necesitan búsquedas estrechas a nivel de fila
Después de que la búsqueda ANN devuelve los ID de filas candidatas, el sistema a menudo necesita recuperar campos como:
caption
embedding
rerank feature
video_uri
metadata
Este patrón lee menos filas, a menudo cientos o miles, pero necesita acceso preciso por ID de fila. Quiere localizar una fila y columna específicas, recuperar solo el rango de bytes requerido y evitar traer un grupo de filas completo solo para recuperar unos pocos registros.
La búsqueda puntual tiene casi la preferencia opuesta al escaneo. Quiere una granularidad de lectura más pequeña. Idealmente, la capa de almacenamiento puede encontrar el segmento o rango de bytes relevante por ID de fila, leer solo ese rango y decodificar solo los datos necesarios para el resultado.
La compresión también tiene una compensación diferente. Para los escaneos, una compresión más fuerte suele valer la pena porque el sistema lee muchos datos y ahorra E/S. Para la búsqueda puntual, la compresión puede convertirse en una desventaja si recuperar una fila requiere decodificar un bloque comprimido mucho más grande.
Un solo diseño no puede optimizar ambas rutas
Este es el conflicto central. El filtrado escalar y la analítica quieren diseños amplios, comprimidos y fáciles de escanear. La búsqueda vectorial quiere diseños estrechos, precisos y direccionables por fila.
Un único formato de archivo puede soportar ambos hasta cierto punto, pero no puede ser óptimo para ambos simultáneamente.
Si todas las columnas viven en Parquet, los escaneos escalares son cómodos. Pero la búsqueda ANN después del recall se vuelve más difícil. El sistema puede necesitar solo unos pocos cientos de vectores, captions o registros de metadatos, mientras que la capa de almacenamiento puede tener que leer grandes grupos de filas que contienen en su mayoría filas irrelevantes.
En un SSD local, la caché y mmap pueden ocultar parte de este coste. Una vez que los datos se almacenan en object storage, el coste se vuelve más visible. Cada fallo de caché puede convertirse en una lectura remota por rango. Si las filas candidatas están dispersas en muchos grupos de filas, una sola consulta puede desencadenar múltiples lecturas, cada una trayendo más datos de los que la consulta necesita. En un diseño mal distribuido, obtener 1.000 filas candidatas puede resultar fácilmente en decenas o cientos de megabytes de E/S innecesaria, y en casos extremos, mucho más.
Hacer los grupos de filas más pequeños ayuda a la búsqueda puntual, pero perjudica los escaneos. Demasiados fragmentos pequeños reducen la eficiencia de compresión, aumentan la sobrecarga de metadatos y rompen las lecturas secuenciales largas de las que dependen los motores analíticos.
Así que el problema no consiste en encontrar un único tamaño mágico de grupo de filas. El problema es que al mismo conjunto de datos se le está pidiendo que se comporte como dos sistemas de almacenamiento distintos.
La búsqueda híbrida fuerza ambos caminos en una sola consulta
La búsqueda híbrida hace que el conflicto sea más difícil de ignorar. Una sola consulta puede aplicar primero filtros escalares:
aesthetic_score > 0.8 AND duration > 5
Luego ejecuta la búsqueda ANN.
Luego obtiene caption, vector y metadatos por ID de fila.
Para el usuario, esto es una solicitud de búsqueda. Para la capa de almacenamiento, es tanto un escaneo analítico como una búsqueda aleatoria de baja latencia.
Por eso el almacenamiento vectorial necesita algo más que una mejor configuración de Parquet. Necesita una forma de ubicar distintas columnas según cómo se leen realmente.
El tercer problema: el conjunto de datos no vive dentro de un solo motor
Los dos primeros problemas ocurren dentro de la base de datos. El tercero ocurre en el límite entre sistemas.
Las canalizaciones de datos de IA abarcan muchos sistemas
En el flujo de trabajo de vídeo, muy poco ocurre dentro de la propia base de datos vectorial.
Los vídeos sin procesar viven en object storage. La generación de clips puede ejecutarse en Spark o Ray. La puntuación estética puede ejecutarse en un servicio de GPU. El subtitulado puede ejecutarse en una canalización de inferencia de LLM. Los embeddings pueden ser generados por otro trabajo de GPU. Los vectores dispersos pueden provenir de un servicio SPLADE. La evaluación offline, el filtrado de datos de entrenamiento, la revisión humana y los trabajos de gobernanza pueden ejecutarse todos en otros lugares.
La base de datos vectorial sirve la búsqueda online, pero el conjunto de datos es producido, corregido, evaluado y ampliado por muchos sistemas.
Los formatos de almacenamiento privados crean múltiples copias de la verdad
Si la base de datos utiliza un formato físico privado que solo ella puede leer y escribir, cada trabajo externo necesita una exportación, una conversión, una copia y una importación. La misma colección puede existir en la base de datos, en un directorio temporal de Spark, en una salida de evaluación y en un directorio local de backfill. Entonces la verdadera pregunta pasa a ser:
- ¿Qué copia es la fuente de la verdad?
- ¿Cuál contiene el modelo de caption del mes pasado?
- ¿Qué filas ya han sido corregidas por revisión humana?
- ¿Qué columna de vector disperso fue generada por qué modelo?
- ¿Qué índice vectorial sigue siendo válido después del backfill?
- ¿A qué objeto de vídeo original se refiere esta fila?
A pequeña escala, los equipos a veces pueden sobrevivir con convenciones de nombres y comprobaciones manuales. Con cientos de millones de filas y terabytes de embeddings, esto se convierte en un problema de consistencia.
Los conjuntos de datos vectoriales necesitan un estado compartido y versionado
Los sistemas lakehouse abordaron una versión de este problema para datos estructurados. Iceberg, Delta Lake y Hudi no se tratan solo de almacenar archivos. Su contribución principal es permitir que múltiples motores se coordinen en torno al mismo estado de tabla.
Las bases de datos vectoriales ahora necesitan una capacidad similar, pero el estado es más complejo. Debe incluir no solo archivos de tabla y particiones, sino también índices vectoriales, índices de texto, características dispersas, registros de eliminación, estadísticas, rangos de ID de fila y referencias a blobs externos.
La pregunta no es simplemente: “¿Puede Spark leer archivos de Milvus?”
La pregunta es: después de que Spark rellena retrospectivamente una columna de vector disperso, ¿cómo sabe Milvus a qué versión pertenece esa columna, qué filas cubre, qué modelo la produjo y cuándo pueden las consultas en línea usarla de forma segura?
La respuesta tiene que residir en el modelo de almacenamiento.
Por qué los parches no son suficientes
Es tentador tratar estos como tres problemas de ingeniería separados.
- ¿Amplificación de escritura? Añadir procesamiento por lotes.
- ¿Lecturas puntuales? Añadir una caché.
- ¿Sistemas externos? Añadir herramientas de exportación e importación.
Esos parches pueden ayudar, pero no abordan el problema subyacente: un conjunto de datos vectoriales es físicamente heterogéneo.
En el ejemplo del video, clip_id, video_id, duration y aesthetic_score son campos escalares cortos. Son útiles para filtrar y analizar.
captiones texto. Puede usarse para BM25, revisión, corrección y relleno retrospectivo.embeddinges un vector denso y largo. Se usa para recuperación ANN y luego para búsqueda a nivel de fila o reranking.embedding_v2es la salida de un nuevo modelo, a menudo rellenada retrospectivamente mucho después de que se insertaran los datos originales.sparse_vectoradmite búsqueda híbrida y tiene su propio patrón de acceso.- El video sin procesar debe permanecer en el almacenamiento de objetos. La base de datos debe almacenar una referencia, una suma de comprobación, un tipo MIME, una versión del analizador y una relación a nivel de fila.
- Los índices vectoriales, los índices de texto, las estadísticas y los registros de eliminación son objetos derivados con su propia semántica de versión.
Estos objetos comparten una fila lógica, pero no deberían compartir todos el mismo diseño físico ni ciclo de vida.
- Si se los fuerza a un diseño de tabla ordinario, las actualizaciones se vuelven costosas.
- Si se los fuerza a un formato de archivo columnar, las lecturas puntuales se vuelven costosas.
- Si se tratan como archivos de objetos no relacionados, la gestión de versiones se vuelve frágil.
Así que el modelo de almacenamiento tiene que partir del hecho de que el conjunto de datos es heterogéneo.
Eso conduce a tres requisitos de diseño:
- Primero, diferentes grupos de columnas deben almacenarse en distintos formatos físicos.
- Segundo, esos grupos de columnas necesitan un espacio compartido de ID de fila, para que aún puedan comportarse como una única tabla lógica.
- Tercero, el conjunto de datos necesita un Manifest versionado que declare qué archivos, índices, registros, estadísticas y referencias a objetos pertenecen a la vista actual.
Este es el diseño detrás de Loon, nuestro nuevo motor de almacenamiento detrás de Milvus y Zilliz Cloud.
Loon: un motor de almacenamiento detrás de Milvus y Zilliz Cloud para conjuntos de datos vectoriales en evolución
Para resolver todos los problemas anteriores, creamos Loon, el nuevo motor de almacenamiento para Milvus y Zilliz Vector Lakebase (la próxima evolución de Zilliz Cloud), diseñado para conjuntos de datos vectoriales en evolución.
El nombre sigue la tradición de Zilliz de usar nombres de aves. Un loon es un ave buceadora que vive en lagos, lo cual se ajusta bien al objetivo del sistema: una base de datos vectorial no debería tener que mover, escanear ni reescribir todo un lago de datos cada vez que ejecuta una consulta, rellena retrospectivamente una columna o construye un índice. Primero debería comprender la versión actual del conjunto de datos, incluidas sus columnas, índices, estadísticas, registros de eliminación y referencias a objetos, y luego leer solo la parte que realmente necesita.
Los formatos de archivo híbridos, la alineación de ID de fila y Manifest no son tres funciones separadas. Surgen del mismo supuesto de diseño: un conjunto de datos vectoriales es inherentemente heterogéneo.
Tres piezas, un modelo de almacenamiento
Los formatos de archivo híbridos reconocen que diferentes columnas tienen diferentes patrones de acceso. Los campos escalares son adecuados para escaneos y filtros. Los campos vectoriales necesitan una búsqueda eficiente a nivel de fila. Los objetos sin procesar, como videos, PDF, imágenes y archivos de audio, pertenecen al almacenamiento de objetos, no al interior de los archivos de datos de la base de datos.
La alineación de ID de fila reconoce que estas columnas pueden estar separadas físicamente, pero aun así describen las mismas filas lógicas. Un pie de foto, un embedding, un vector disperso y un URI de video pueden residir en diferentes archivos y formatos, pero aun así deben volver a reunirse como un único resultado.
El Manifest reconoce que el conjunto de datos no se escribe una vez y se deja intacto. Será modificado por múltiples sistemas, en múltiples versiones, para múltiples tareas. Los índices, las estadísticas, los registros de eliminación, las referencias a objetos externos y los grupos de columnas deben aparecer todos en la misma vista versionada.
Por eso Loon no es solo un formato de archivo vectorial más rápido. Un formato más rápido ayuda a la búsqueda puntual, pero no resuelve la evolución del esquema ni la coordinación entre múltiples motores. La alineación de ID de fila permite que las columnas divididas se comporten como una sola tabla, pero no especifica qué archivos pertenecen a la versión actual. Un Manifest puede describir el estado de un conjunto de datos, pero sin grupos de columnas y alineación de ID de fila, no puede representar limpiamente diferentes diseños físicos dentro de una colección lógica.
El modelo de almacenamiento necesita los tres elementos: diferentes formatos para diferentes grupos de columnas, un espacio compartido de ID de fila para reconstruir filas y un Manifest versionado que indique a cada lector y escritor qué es actualmente el conjunto de datos.
Dónde encaja Loon en Milvus y Zilliz Vector Lakebase
En Milvus, reemplaza la antigua capa de almacenamiento de binlogs de segmentos por un modelo construido alrededor de abstracciones de Manifest, ColumnGroup, formato de archivo y sistema de archivos. En Zilliz Vector Lakebase (la próxima evolución de Zilliz Cloud), la misma dirección se aplica a la arquitectura de Vector Lakebase: mantener rápida la ruta de servicio de la base de datos vectorial, al tiempo que se facilita la evolución, el análisis y la coordinación de los datos subyacentes con sistemas externos.
Los componentes de nivel superior de Milvus aún conservan sus roles familiares. Proxy se encarga del enrutamiento. QueryCoord y DataCoord se encargan de la programación. IndexNode construye índices. Las APIs orientadas a aplicaciones para colecciones, inserciones, búsquedas y búsquedas híbridas no necesitan exponer archivos Manifest ni ColumnGroups.
El cambio está por debajo.
DataNode, QueryNode, segcore, compaction y los conectores externos pueden operar a través de la misma abstracción de almacenamiento. Eso importa porque el conjunto de datos ya no es escrito y leído únicamente por la base de datos. Puede ser extendido por sistemas de cómputo externos y consumido simultáneamente por la búsqueda en línea.
A alto nivel, las capas se ven así:
Manifest
→ ColumnGroup
→ file format layer
→ filesystem abstraction
El Manifest describe el estado versionado del conjunto de datos. Los ColumnGroups asignan una colección lógica a grupos físicos de columnas. La capa de formato de archivo permite que cada ColumnGroup elija un formato apropiado. La abstracción del sistema de archivos funciona tanto en almacenamiento de objetos como en almacenamiento local.
El punto importante es que los formatos de archivo híbridos, la alineación de ID de fila y el Manifest no son características separadas. Juntos, definen el modelo de almacenamiento.
Con ese modelo en marcha, podemos analizar las tres decisiones de diseño una por una: cómo Loon almacena diferentes ColumnGroups, cómo los alinea de nuevo en filas y cómo el Manifest convierte esos archivos en un conjunto de datos versionado.
Diseño 1: usar el formato de archivo adecuado para el grupo de columnas adecuado
Diferentes columnas tienen diferentes patrones de acceso. No deberían verse obligadas a usar el mismo formato de archivo.
Loon separa una colección lógica en ColumnGroups.
- Los campos escalares, los campos de filtro, las claves de negocio y los campos estadísticos a menudo se escanean, filtran, agregan o se usan para la planificación de consultas. Se benefician de la compresión, la poda de columnas y la compatibilidad con el ecosistema. Parquet es una buena opción para estas columnas.
- Los vectores densos, los vectores dispersos y las características de rerank a menudo se leen después de la recuperación ANN por ID de fila. Necesitan acceso aleatorio de baja latencia, lecturas precisas de rangos de bytes y decodificación selectiva. Un diseño orientado a segmentos es una mejor opción. Loon usa Vortex en esta dirección.
- Los objetos sin procesar, como videos, PDF, imágenes y archivos de audio, no deberían incrustarse en los archivos de datos de la base de datos vectorial. Deberían permanecer en el almacenamiento de objetos. La base de datos registra referencias, sumas de verificación, tipos MIME, versiones de parser y relaciones a nivel de fila.
Para el ejemplo de video, un diseño físico podría verse así:
Parquet ColumnGroup:
clip_id / video_id / start_offset / duration / aesthetic_score / caption
Vortex ColumnGroups:
embedding
embedding_v2
sparse_vector
Object storage:
raw video objects
Para la aplicación, esto sigue siendo una colección. Para la capa de almacenamiento, diferentes partes de esa colección usan diferentes formatos físicos. Esto reduce directamente las reescrituras innecesarias. Agregar embedding_v2 puede convertirse en un nuevo ColumnGroup vectorial más una confirmación de Manifest. No requiere reescribir la columna de captions, los metadatos escalares ni la columna de embedding existente.
La misma idea se aplica a vectores dispersos, características de rerank u otros campos derivados. Si una nueva columna puede ser físicamente independiente y estar alineada por ID de fila, no tiene que arrastrar columnas no relacionadas por la misma ruta de reescritura.
Loon también adapta el uso de formatos de archivo.
Para Parquet, la configuración predeterminada no siempre es ideal para datos con muchos vectores. Un grupo de filas de 64 MB puede ser demasiado grande para búsquedas puntuales porque una pequeña lectura aleatoria puede traer muchos más datos de los necesarios. Loon reduce los grupos de filas a 1 MB en rutas relevantes y desactiva codificaciones, como la codificación de diccionario en columnas vectoriales, cuando no ayudan con datos vectoriales de apariencia aleatoria.
Para Vortex, el trabajo más importante es el diseño. Loon usa un diseño que equilibra la eficiencia de escaneo y la búsqueda puntual. Dentro de un grupo de filas, los segmentos de columnas relacionadas pueden colocarse cerca unos de otros para admitir el escaneo. Para realizar operaciones, las lecturas de subsegmentos permiten que el sistema obtenga solo los bytes relevantes en lugar de traer un segmento completo.
Loon también admite integración de solo lectura con Lance, por lo que los datasets Lance existentes pueden montarse como ColumnGroups cuando la compatibilidad importa.
Qué muestra el benchmark
En una prueba local, usando un solo archivo con 40.000 filas y el esquema {id: int64, name: utf8, value: float64, vector: list<float32>[128]}, Vortex mostró estos resultados frente a Parquet con grupos de filas de 1 MB:
| Operación | Vortex | Parquet | Diferencia |
|---|---|---|---|
| Take, K=1000 filas aleatorias | 5,8 ms | 144 ms | 25 veces más rápido |
| Escaneo completo de la columna vectorial | 21 ms | 142 ms | 6,76 veces más rápido |
| Tamaño de archivo, ~21 MB de datos sin procesar | 6,62 MB | 7,16 MB | 7% más pequeño |
El resultado de take proviene de reducir la cantidad de datos irrelevantes que deben leerse y decodificarse. El resultado del escaneo proviene de la compresión y las elecciones de implementación.
Estos números deben permanecer vinculados a su configuración: 8 vCPU Ubuntu 22.04 KVM, sistema de archivos local, un archivo, 40.000 filas, grupos de filas de 1 MB y el esquema anterior. En almacenamiento de objetos, la E/S de red puede dominar, por lo que reducir la amplificación de lectura puede importar aún más. Los resultados reales dependen de la forma del dataset, el comportamiento del almacenamiento de objetos, el estado de la caché y el patrón de consulta.
El punto más amplio no es que todas las columnas deban usar Vortex.
El punto es que los datasets vectoriales necesitan una elección de formato de archivo a nivel de ColumnGroup.
Diseño 2: alinear archivos físicos mediante ID de fila
Los formatos de archivo híbridos resuelven un problema: ahora diferentes columnas pueden residir en los formatos que mejor se ajusten a ellas.
Pero eso crea un segundo problema. Si los campos escalares residen en Parquet, los vectores residen en Vortex y los objetos sin procesar residen en almacenamiento de objetos, ¿cómo sigue el sistema tratándolos como una sola colección?
Loon resuelve esto con la alineación de ID de fila.
El ID de fila es el sistema de coordenadas de la capa de almacenamiento
Cada ColumnGroupFile físico registra la ruta del archivo y el rango de ID de fila que cubre:
path
start_index
end_index
Diferentes ColumnGroups pueden cubrir el mismo espacio de ID de fila aunque residan en diferentes archivos y formatos.
Para el ID de fila 12345, los metadatos escalares pueden estar en un ColumnGroup de Parquet, el embedding puede estar en un ColumnGroup de Vortex y el video sin procesar puede estar representado por una referencia de almacenamiento de objetos. Lógicamente, siguen siendo una sola fila. Esto le da a la capa de almacenamiento un sistema de coordenadas estable.
El ID de fila no es la clave primaria de negocio. Es el sistema de coordenadas de la capa de almacenamiento que permite a Loon dividir físicamente una colección sin perder la capacidad de reconstruirla lógicamente.
Las nuevas columnas no tienen que reescribir las columnas antiguas
Agregar embedding_v2 no requiere reescribir los ColumnGroups originales de subtítulo, metadatos o embedding_v1. Loon puede escribir un nuevo ColumnGroup vectorial, registrar el rango de ID de fila que cubre y confirmar ese cambio mediante el Manifest.
Lo mismo se aplica a vectores dispersos, funciones de rerank u otros campos derivados que lleguen más tarde.
Siempre que el nuevo ColumnGroup cubra el rango de ID de fila correcto, puede unirse a la misma colección lógica sin obligar a que los datos no relacionados se muevan.
Las eliminaciones y la compactación pueden ser más específicas
La alineación de ID de fila también ayuda con las eliminaciones.
Una eliminación puede expresarse primero mediante un registro de eliminación. La fila se vuelve invisible a nivel lógico, mientras que la limpieza física se retrasa hasta la compactación. Cuando finalmente se ejecuta la compactación, no siempre necesita reescribir todos los ColumnGroups vinculados a las filas afectadas. Puede centrarse en los ColumnGroups que necesitan limpieza.
Esto importa porque no todas las columnas tienen el mismo perfil de coste. Reescribir un ColumnGroup escalar corto es muy diferente de reescribir cientos de gigabytes de vectores densos.
La búsqueda híbrida puede recuperar solo las columnas que necesita
La alineación de ID de fila también es lo que hace que la búsqueda híbrida sea práctica sobre formatos de archivo híbridos.
Después de que la búsqueda ANN devuelve los ID de fila candidatos, el sistema puede recuperar solo los campos necesarios para el resultado final: subtítulos, metadatos, vectores, funciones de rerank o referencias de objetos.
Por ejemplo, una consulta puede necesitar:
caption
embedding
video_uri
Esos campos pueden residir en diferentes ColumnGroups. Loon puede localizar los archivos relevantes por rango de ID de fila, leer los rangos de bytes necesarios y ensamblar el resultado.
Sin alineación de ID de fila, los formatos híbridos serían simplemente archivos separados colocados uno al lado del otro. Con alineación de ID de fila, se comportan como una sola colección lógica.
Packed Reader oculta la división a la capa superior
El componente de tiempo de ejecución que hace que esto sea utilizable es el Packed Reader.
La capa superior ve un flujo unificado de Arrow RecordBatch. Por debajo, los datos pueden provenir de múltiples ColumnGroups en diferentes formatos de archivo. El Packed Reader oculta esas diferencias, alinea los datos por rangos de ID de fila y programa la E/S multiarchivo con uso de memoria controlado.
También admite take directo por ID de fila. Dado un conjunto de ID de fila, localiza los ColumnGroupFiles relevantes, emite lecturas de rango y devuelve los campos solicitados.
Para el flujo de trabajo de video, una consulta ANN puede necesitar caption, embedding y video_uri. El Packed Reader puede recuperar el ColumnGroup escalar y el ColumnGroup vectorial sin tocar columnas no relacionadas.
Esa es la diferencia entre “archivos separados” y “una tabla con múltiples diseños físicos.”
Diseño 3: hacer del Manifiesto la fuente de verdad
Los formatos de archivo híbridos definen cómo se almacenan físicamente los datos. La alineación de ID de fila determina cómo los ColumnGroups separados siguen formando una única tabla lógica. Pero el sistema aún necesita responder a una pregunta más amplia: ¿qué archivos, logs, estadísticas, índices y referencias a objetos pertenecen a la versión actual del dataset? Ese es el trabajo del Manifiesto.
Los directorios de almacenamiento de objetos no son suficientes
El almacenamiento de objetos no es un catálogo de base de datos. Un directorio puede contener archivos antiguos, archivos nuevos, salidas de trabajos fallidos, archivos temporales, logs de eliminación, archivos que aún están referenciados por snapshots anteriores y archivos en espera de limpieza. El hecho de que un archivo exista no significa que pertenezca a la versión actual del dataset.
Un dataset de Loon puede organizarse en directorios como:
_metadata/
_data/
_delta/
_stats/
_index/
Pero la estructura de directorios no es la fuente de verdad. El Manifiesto lo es. Los lectores no deberían listar directorios e inferir el estado a partir de los archivos que casualmente existan. Deberían leer el Manifiesto actual y seguir la vista versionada que declara.
El Manifiesto define una vista versionada del dataset
El Manifiesto define el dataset en una versión dada. Registra:
- qué ColumnGroups existen
- qué rangos de ID de fila cubren
- qué formato físico usa cada ColumnGroup
- dónde residen los archivos
- qué logs de eliminación están activos
- qué estadísticas están disponibles
- qué índices existen
- qué blobs externos se referencian
- qué columnas y rangos de filas cubren esas estadísticas o índices
Cada actualización escribe una nueva versión del Manifiesto. Un lector que abre la versión N ve una vista estable del dataset en la versión N. Un escritor puede preparar la versión N+1 sin interrumpir a los lectores que todavía están usando la versión N.
El Manifiesto rastrea más que archivos de tabla
En Loon, el cuerpo del Manifiesto se codifica con Apache Avro y se organiza en torno a cuatro secciones principales.
- ColumnGroups describe las columnas, los formatos, los archivos y los rangos de ID de fila.
- DeltaLogs describe las eliminaciones. Diferentes tipos de eliminación cubren distintas fuentes de cambio, como eliminaciones por clave primaria desde clientes, eliminaciones posicionales desde compactación interna o eliminaciones por igualdad desde motores externos.
- Stats incluye metadatos de planificación como filtros de Bloom, estadísticas BM25 y valores mínimos/máximos.
- Indexes describe el tipo de índice, los parámetros, las columnas cubiertas y los rangos de ID de fila. Esto puede incluir índices vectoriales como HNSW o IVF, índices de texto, índices invertidos, índices bitmap y estructuras relacionadas.
Aquí es donde Loon difiere de un manifiesto de tabla tradicional.
Un dataset vectorial necesita rastrear no solo archivos de datos y particiones. También necesita rastrear índices vectoriales, índices de texto, features dispersas, logs de eliminación, estadísticas, referencias a objetos externos y los rangos de ID de fila que los conectan.
El Manifiesto debe poder ser escrito por más que la base de datos
La parte más importante no es solo lo que contiene el Manifiesto. Es quién puede escribirlo.
- Si solo la base de datos puede escribir el Manifiesto, sigue siendo metadatos internos. Metadatos más limpios, pero aún privados de un solo motor.
- Si los motores externos pueden generar nuevos ColumnGroups, estadísticas y entradas de Manifiesto, el Manifiesto se convierte en una interfaz de coordinación.
- Un trabajo de Spark, por ejemplo, puede rellenar una columna vectorial dispersa. Escribe un nuevo ColumnGroup, registra la cobertura de filas y las estadísticas, y confirma un nuevo Manifiesto. Las consultas online pueden seguir leyendo la versión antigua durante el trabajo. Una vez que la confirmación tiene éxito, la nueva versión se vuelve visible.
Esto es similar en espíritu a Iceberg y Delta Lake, pero el modelo de objetos es más amplio. Un dataset vectorial necesita rastrear índices vectoriales, índices de texto, features dispersas, logs de eliminación, estadísticas, referencias a blobs y rangos de ID de fila, no solo archivos de tabla y particiones.
Las confirmaciones optimistas mantienen simples las actualizaciones de versión
Cada confirmación escribe una nueva versión del Manifest. Un escritor puede crear nuevo contenido basado en la versión N y luego intentar escribir manifest-{N+1}.avro. La escritura condicional del almacenamiento de objetos o la semántica de coincidencia de generación pueden hacer que la confirmación falle si esa versión ya existe. El escritor puede entonces reintentar contra la versión más reciente.
Esto le da a Loon concurrencia optimista sin obligar a que cada actualización pase por una ruta de coordinación pesada y fuertemente consistente. Sin un Manifest, el almacenamiento multiformato y multimotor acaba convirtiéndose en convenciones de nomenclatura y conciliación manual. Eso puede funcionar para conjuntos de datos pequeños. No funciona para datos vectoriales a escala de TB.
El Manifest es lo que convierte archivos heterogéneos en un conjunto de datos que múltiples sistemas pueden leer y actualizar de forma segura.
Qué cambia para los usuarios cuando el almacenamiento se vuelve versionado
Para los desarrolladores de aplicaciones, Loon no debería convertirse en una nueva carga de API.
Los usuarios deberían seguir trabajando con conceptos familiares de Milvus: colecciones, inserciones, búsqueda y búsqueda híbrida. No deberían necesitar pensar en archivos Manifest, ColumnGroups, rangos de ID de fila ni diseño de archivos durante el desarrollo normal de aplicaciones.
El cambio está por debajo. El almacenamiento se vuelve más consciente de cómo evolucionan realmente los conjuntos de datos de IA.
Añadir un nuevo embedding no debería mover los datos antiguos
Anteriormente, añadir embedding_v2 a una colección existente a menudo requería exportar datos, entrenar un nuevo modelo, generar vectores y luego reimportar o actualizar en masa la colección mediante el SDK. Esa ruta crea mucho trabajo operativo: seguimiento de versiones, reintentos de trabajos fallidos, reconstrucciones de índices, impacto en el servicio y comprobaciones de consistencia.
Con Loon, esto puede convertirse en una evolución de esquema más una nueva confirmación de ColumnGroup. La nueva columna de embedding puede escribirse como su propio ColumnGroup físico, alineado por ID de fila, y hacerse visible a través del Manifest. La columna de título antigua, la columna de metadatos escalares y la columna de embedding original no necesitan moverse.
Los backfills no deberían requerir un bucle de actualización del lado del cliente
Muchas actualizaciones de datos de IA son backfills. Un equipo puede añadir vectores dispersos después de que la búsqueda híbrida se vuelva importante. Puede añadir características de rerank después de entrenar un nuevo modelo. Puede corregir títulos tras una revisión humana. Puede añadir etiquetas de gobernanza después de una actualización de políticas.
En un diseño tradicional, estos cambios suelen ocurrir mediante actualizaciones del SDK del cliente o rutas de escritura solo de base de datos, incluso cuando los datos son producidos por Spark, Ray u otro motor externo.
Con Loon, los sistemas de cómputo externos pueden producir nuevos ColumnGroups y confirmarlos a través del Manifest. La base de datos ya no tiene que ser el único punto de entrada para cada reescritura.
El análisis offline no debería requerir otra copia de la verdad
Anteriormente, los equipos a menudo volcaban una colección online a Parquet para evaluación o análisis offline. Eso crea dos versiones del mismo conjunto de datos: la colección online y la copia de análisis. Una vez que se corrigen los títulos, se regeneran los embeddings, se aplican registros de eliminación o se reconstruyen índices, el equipo tiene que preguntarse qué copia está actualizada.
Con un modelo de almacenamiento basado en Manifest, los motores de análisis pueden leer la misma vista versionada del conjunto de datos que el sistema de servicio. Pueden proyectar solo las columnas que necesitan, escanear solo los rangos de filas relevantes y trabajar contra una versión declarada del conjunto de datos en lugar de una instantánea exportada manualmente.
Las eliminaciones y correcciones deberían tocar solo lo que cambió
Las eliminaciones, correcciones de títulos, arreglos de etiquetas y actualizaciones de gobernanza son rutinarias en los conjuntos de datos de IA. No deberían forzar a que cada columna vectorial larga pase por la misma ruta de reescritura.
Con Loon, los registros de eliminación pueden tratarse primero como eliminación lógica. Más adelante, la compactación puede limpiar los ColumnGroups afectados sin reescribir datos no relacionados. Si cambia un campo de texto corto, la capa de almacenamiento no debería tener que reescribir cientos de gigabytes de vectores densos solo porque comparten la misma fila lógica.
Los motores externos se convierten en parte del flujo de trabajo, no en una vía de escape
El cambio más amplio es que los motores externos ya no se tratan como sistemas fuera de la base de datos vectorial.
Spark, Ray, los trabajos de evaluación, los sistemas de etiquetado y las canalizaciones de gobernanza ya producen y modifican gran parte de los datos. La capa de almacenamiento debería permitirles colaborar en torno a una única fuente de verdad en lugar de exportar, copiar y reimportar constantemente.
Eso es lo que una versión de Manifest hace posible. Proporciona al servicio en línea, el análisis sin conexión, los trabajos de backfill y la compactación una vista compartida del conjunto de datos.
Estos pueden sonar como detalles internos de almacenamiento, pero afectan la rapidez con la que los equipos pueden iterar sobre conjuntos de datos de IA. Cada cambio de modelo, backfill de características, corrección de subtítulos, filtro de calidad y reconstrucción de índice depende de la misma pregunta: "¿Puede el sistema actualizar el conjunto de datos sin mover datos que no necesita mover? "
Ese es el valor práctico del modelo de almacenamiento.
Loon está disponible en Milvus 3.0 beta y Zilliz Vector Lakebase
Loon está disponible en Milvus 3.0 beta y también forma parte de la capa de almacenamiento en Zilliz Vector Lakebase, la próxima evolución de Zilliz Cloud. Y esta versión se centra en tres áreas principales:
- El Manifest. El objetivo es que las escrituras, backfills, eliminaciones, estadísticas y actualizaciones de índices produzcan vistas versionadas del conjunto de datos que los lectores puedan abrir de forma coherente. Para los lectores, esto significa que una consulta puede abrir una versión específica de Manifest y ver una vista estable del conjunto de datos. Para los escritores, esto significa que los nuevos archivos de datos, registros de eliminación, estadísticas o archivos de índice pueden prepararse primero y luego hacerse visibles mediante un commit versionado.
- El ColumnGroup y el soporte de formatos. Parquet admite columnas escalares y compatibles con el ecosistema. Vortex admite patrones de acceso intensivos en vectores. Lance puede integrarse en modo de solo lectura para compatibilidad con conjuntos de datos Lance existentes.
- El Index on Lake. Las estadísticas escalares, los índices de filtrado y los índices invertidos de texto pueden participar en la planificación basada en Manifest por rango de filas. Los índices vectoriales nativos del lake son más complejos. HNSW e IVF tienen comportamientos diferentes en el almacenamiento de objetos, y HNSW en particular es sensible al acceso aleatorio y a la localidad de caché. No puede simplemente reutilizar un diseño pensado para un SSD local y esperar el mismo resultado.
Todavía queda trabajo por delante
- Las rutas de escritura externas importan porque Spark y Ray deberían poder producir ColumnGroups y commits de Manifest sin obligar a que cada backfill pase por un bucle de SDK de cliente.
- La interoperabilidad con lakehouse importa porque muchos equipos ya usan catálogos y motores de consulta como Iceberg, Delta Lake, Trino, DuckDB y Athena. Los datos vectoriales deberían poder participar en ese ecosistema sin perder rendimiento de búsqueda vectorial.
- El diseño del índice importa porque los índices de grafos y las estructuras invertidas tienen patrones de acceso diferentes en el almacenamiento de objetos.
- La semántica de objetos grandes importa porque los videos, PDF, imágenes y archivos de audio sin procesar requieren gestión de referencias, versionado y comportamiento de eliminación que se alineen con el conjunto de datos vectorial derivado.
El comportamiento exacto de la versión, la configuración predeterminada y la ruta de migración deben seguir las notas de la versión relevantes de Milvus y Zilliz Cloud. Sin embargo, la dirección del almacenamiento es clara: las bases de datos vectoriales necesitan una base versionada y nativa del lake debajo de la capa de servicio.
Prueba Loon en Zilliz Vector Lakebase
Si tu stack actual separa el servicio en línea, el análisis sin conexión, los backfills y los flujos de trabajo externos de data lake en sistemas diferentes, vale la pena echarle un vistazo a Zilliz Vector Lakebase. Puedes probarlo en Zilliz Cloud. Los nuevos registros con correo electrónico de trabajo obtienen $100 en créditos gratuitos. También puedes hablar con nosotros sobre tu caso de uso.
También puedes seguir la versión Milvus 3.0 para ver cómo evoluciona Loon en el motor de código abierto.
Zilliz Vector Lakebase reúne:
- Servicio por niveles para diferentes equilibrios entre rendimiento en tiempo real y coste
- Búsqueda bajo demanda para cargas de trabajo a gran escala o exploratorias sin computación siempre activa
- Búsqueda en lagos de datos externos, para que puedas indexar y buscar directamente sobre los datos existentes del lago
- Búsqueda de espectro completo en vectores, texto, JSON y datos geoespaciales, con recuperación híbrida y reranking
- Almacenamiento unificado nativo del lago construido sobre Vortex, un formato abierto diseñado para lecturas aleatorias más rápidas y de menor coste sobre datos con gran presencia de vectores
Sigue leyendo

What Is a Vector Lakebase?
A Vector Lakebase is a unified, lake-native data architecture for AI that combines vector-database-grade serving with open lake storage, reusable lake-level indexes, and a shared semantic layer.

Why Not All VectorDBs Are Agent-Ready
Explore why choosing the right vector database is critical for scaling AI agents, and why traditional solutions fall short in production.

Introducing DeepSearcher: A Local Open Source Deep Research
In contrast to OpenAI’s Deep Research, this example ran locally, using only open-source models and tools like Milvus and LangChain.


