Apresentando o Loon: um novo mecanismo de armazenamento para dados vetoriais que nunca param de mudar
Principais conclusões
Este é um mergulho longo e aprofundado em engenharia, então aqui estão os pontos principais antes de entrarmos nos detalhes.
- Datasets de IA não são tabelas estáticas. As mesmas linhas continuam mudando à medida que as equipes substituem modelos de embedding, adicionam vetores esparsos, revisam legendas, preenchem rótulos retroativamente, reconstroem índices e executam análises offline.
- Layouts de armazenamento tradicionais falham de três maneiras: colunas vetoriais longas tornam backfills caros, um único formato de arquivo não consegue atender bem tanto a varreduras quanto a leituras pontuais, e o armazenamento privado de bancos de dados força pipelines externos a criar cópias extras da verdade.
- Loon é o novo mecanismo de armazenamento para Milvus e Zilliz Vector Lakebase. Ele é construído em torno de formatos de arquivo híbridos, alinhamento por IDs de linha e um Manifest que define o estado versionado do dataset.
- O objetivo é permitir que um único dataset vetorial dê suporte a busca online, análise offline, backfills, compactação e computação externa sem copiar, reescrever ou reimportar dados constantemente.
Introdução
Por um tempo, houve um argumento contra bancos de dados vetoriais que parecia razoável.
Bancos de dados tradicionais já armazenam inteiros, strings, JSON, blobs e índices. Por que não adicionar um tipo _vector_, construir um índice ANN ao lado dele e dar o trabalho por encerrado?
Para busca semântica inicial, isso funciona bem o suficiente. Uma coluna vetorial mais um índice pode dar suporte a uma demonstração, uma pequena aplicação RAG ou um recurso de busca interna. O problema aparece depois, quando o dataset começa a se comportar menos como uma tabela e mais como um sistema de dados de IA.
Um dataset vetorial de produção tem linhas, chaves primárias, campos escalares e colunas consultáveis. Nesse sentido, ele se parece com uma tabela de banco de dados. Mas também tem a escala e o formato de fluxo de trabalho de um data lake. Ele pode conter centenas de milhões de registros. Ele é repetidamente lido e reescrito por Spark, Ray, DuckDB, pipelines de treinamento, jobs de avaliação e sistemas de qualidade de dados.
Ele também depende de armazenamento de objetos. Os objetos de origem frequentemente são vídeos, imagens, PDFs, arquivos de áudio ou documentos da web que permanecem em S3, GCS, OSS ou outro object store. O banco de dados armazena referências, metadados, features derivadas e índices. Então ele adiciona coisas que modelos de armazenamento tradicionais não foram construídos para gerenciar como objetos de primeira classe: embeddings densos, vetores esparsos, legendas, índices vetoriais, índices de texto, logs de exclusão, estatísticas, versões de modelos, versões de parsers, referências a blobs externos e as relações de versão entre todos eles.
É aí que “basta adicionar uma coluna vetorial” começa a falhar. A questão não é se um banco de dados consegue armazenar bytes vetoriais. Muitos sistemas conseguem. A pergunta mais difícil é se o modelo de armazenamento consegue lidar com a forma como os dados vetoriais mudam, como são consultados e como são compartilhados em toda a stack de dados de IA.
É por isso que construímos o Loon, o novo mecanismo de armazenamento para Milvus e Zilliz Vector Lakebase (a próxima evolução do Zilliz Cloud).
Loon é projetado com três ideias:
- Usar formatos físicos diferentes para diferentes tipos de colunas.
- Alinhar essas colunas por meio de um espaço compartilhado de IDs de linha.
- Usar um Manifest para definir o estado versionado do dataset.
Para entender por que essas peças importam, vamos começar com um fluxo de trabalho multimodal comum.
Um dataset vetorial nunca está realmente concluído.
Imagine uma equipe de IA construindo um dataset de vídeos para treinamento multimodal.
Um vídeo longo é enviado para armazenamento de objetos. Um pipeline o corta em clipes com base em mudanças de cena, limites de tomadas ou janelas de tempo. Clipes que são longos demais ou curtos demais, desfocados, duplicados ou de baixa qualidade são filtrados. Os clipes restantes são pontuados por um modelo estético, legendados por outro modelo, embedados por um modelo visão-linguagem e armazenados em um banco de dados vetorial para busca, deduplicação e filtragem de dados de treinamento.
Em alto nível, o fluxo de trabalho parece simples:
vídeo
→ clipes
→ metadados
→ aesthetic_score
→ legenda
→ embedding
→ busca / dedup / filtragem de dados de treinamento
Mas o dataset não chega totalmente formado.
- Na primeira semana, a tabela pode conter apenas
clip_id,video_id,start_offseteduration. - Na segunda semana, a equipe adiciona
aesthetic_score. - Na terceira semana, um modelo de legendagem é executado, e cada clipe recebe uma
caption. - Na quarta semana, o primeiro modelo de embedding entra em operação, e cada clipe recebe um embedding CLIP de 768 dimensões.
- Um mês depois, a equipe troca de modelos e faz o backfill de
embedding_v2, agora com 1024 dimensões. - Dois meses depois, a busca híbrida se torna um requisito, então a equipe adiciona uma coluna de vetor esparso.
- Três meses depois, as legendas passam por revisão humana e precisam ser corrigidas no próprio local.
O conjunto de dados nunca foi concluído. Ele continuou acumulando novas interpretações das mesmas linhas subjacentes.
Essa é uma das diferenças centrais entre dados vetoriais e dados empresariais tradicionais. A mesma linha é reprocessada repetidas vezes. E a escala transforma isso de um inconveniente em um problema de armazenamento: conjuntos de dados multimodais muitas vezes não têm milhões de registros, mas centenas de milhões ou bilhões. O LAION-5B é uma referência útil para o formato — bilhões de pares imagem-texto, cada um com metadados, legendas e embeddings. Portanto, a parte difícil não é a primeira inserção. A parte difícil é tudo o que acontece depois que o conjunto de dados começa a evoluir. Essa evolução expõe três problemas.
O primeiro problema: colunas longas tornam a amplificação de escrita cara
Formatos colunares como Parquet são excelentes para muitas cargas de trabalho analíticas. Eles funcionam bem quando os esquemas são razoavelmente estáveis, os dados são lidos com mais frequência do que reescritos, as varreduras acessam apenas um subconjunto de colunas e a compressão importa. Esse é o mundo para o qual muitos formatos analíticos foram otimizados.
Linhas vetoriais são muito mais largas do que linhas analíticas
O lineitem do TPC-H é uma boa referência. Ele tem 16 colunas: chaves inteiras, valores decimais, datas, strings curtas e um pequeno campo de comentário. Uma linha não comprimida tem aproximadamente 150 bytes. Após a compressão, pode ser muito menor. Com um grupo de linhas de 64 MB, um sistema de armazenamento consegue empacotar centenas de milhares de linhas em um grupo.
Conjuntos de dados vetoriais não se parecem com isso.
Um conjunto de dados imagem-texto no estilo LAION é muito mais próximo do que muitos pipelines de IA produzem hoje. Cada linha ainda tem metadados comuns: uma URL, uma legenda, largura, altura, pontuações de qualidade, rótulos e assim por diante. Mas, quando o embedding é adicionado, a forma física da linha muda.
Um vetor CLIP de 768 dimensões ocupa cerca de 1,5 KB em fp16 ou 3 KB em fp32. Essa única coluna pode ser muito maior do que uma linha inteira de lineitem do TPC-H.
E 768 dimensões não são incomuns nem grandes pelos padrões atuais. Um embedding de 1024 ou 2048 dimensões é comum em pipelines multimodais. O text-embedding-3-large da OpenAI chega a 3072 dimensões, o que equivale a cerca de 12 KB por vetor em fp32.
A comparação é marcante:
| Formato do conjunto de dados | Tamanho aproximado da linha | Campo dominante |
|---|---|---|
| lineitem do TPC-H | ~150 bytes não comprimidos | escalares e strings curtas |
| Linha no estilo LAION com vetor fp16 de 768 dimensões | ~1,5 KB+ | embedding |
| Linha no estilo LAION com vetor fp32 de 768 dimensões | ~3 KB+ | embedding |
| Linha com vetor fp32 de 3072 dimensões | ~12 KB+ apenas para o vetor | embedding |
Em muitos conjuntos de dados de IA, a coluna vetorial não é apenas mais um campo. Fisicamente, ela representa a maior parte da linha. Isso altera o custo da evolução do esquema.
Adicionar uma coluna vetorial pode significar centenas de gigabytes
Suponha que um conjunto de dados tenha 100 milhões de clipes de vídeo. Adicionar uma nova coluna de embedding fp32 de 1024 dimensões significa escrever aproximadamente 400 GB de dados vetoriais brutos. Isso não inclui estatísticas, índices, atualizações de metadados, sobrecarga de armazenamento de objetos, validação ou integração ao caminho de serviço.
Se a equipe adiciona uma ou duas colunas semelhantes a vetores todos os meses, como embedding_v2, sparse_vector ou recursos de rerank, a evolução do esquema se torna um trabalho recorrente de engenharia de dados medido em centenas de gigabytes ou terabytes.
Pequenas atualizações lógicas podem acionar grandes reescritas físicas
Atualizações são igualmente importantes.
Em sistemas colunares, dados antigos geralmente não são atualizados no local. Um log de exclusão registra o que mudou, e a compactação posteriormente reescreve as linhas ativas em novos arquivos. Esse modelo é gerenciável quando as linhas são pequenas.
Com dados vetoriais, uma pequena atualização lógica pode acionar uma grande reescrita física.
Um trabalho de revisão humana pode corrigir apenas algumas centenas de bytes em uma legenda. Mas, se a legenda, o vetor denso, o vetor esparso e outros recursos derivados compartilharem o mesmo ciclo de vida do arquivo físico, o sistema pode acabar reescrevendo os vetores também. A mudança lógica é pequena. A E/S física pode ser enorme.
Esse é o problema de amplificação de escrita no armazenamento de vetores. A parte cara não é apenas o fato de os vetores serem grandes. É que campos derivados grandes e campos mutáveis pequenos frequentemente acabam vinculados por um layout de armazenamento que os trata como uma única unidade.
Para conjuntos de dados de IA, backfill é uma carga de trabalho rotineira
Para tabelas analíticas tradicionais, a evolução do esquema pode ocorrer apenas ocasionalmente. Para conjuntos de dados de IA, ela é rotineira. Modelos de legenda são atualizados. Modelos de embedding são substituídos. Vetores esparsos são adicionados posteriormente. Recursos de rerank aparecem. Rótulos humanos são corrigidos. Tags de governança recebem backfill. Índices são reconstruídos.
Essas operações não são simples appends. Elas frequentemente modificam ou estendem linhas existentes.
É por isso que o armazenamento de vetores não pode otimizar apenas para taxa de varredura. Ele também precisa tornar backfills e atualizações parciais mais baratos.
O segundo problema: os mesmos dados precisam dar suporte a varreduras e leituras pontuais
Depois que os dados são gravados, o caminho de leitura se divide. O mesmo conjunto de dados vetoriais normalmente tem dois padrões de acesso distintos: varredura analítica e leituras pontuais.
Cargas de trabalho analíticas querem varreduras amplas e compactadas
Um pipeline pode executar filtros como:
WHERE aesthetic_score > 0.8 AND duration > 5
Ou pode executar análise offline, avaliação completa de embeddings, estatísticas de BM25, construção de bitmaps, verificações de qualidade de dados, contagens e agrupamentos.
Esse padrão lê muitas linhas, mas apenas algumas colunas. Ele prefere E/S sequencial, grupos de linhas maiores, compressão, poda de colunas, decodificação em lote e execução vetorizada.
Grupos de linhas grandes ajudam aqui. Eles permitem que uma única solicitação de E/S traga uma grande quantidade de dados úteis, melhoram a eficiência da compressão e fornecem ao mecanismo de execução dados contíguos suficientes para amortizar a sobrecarga. Quando várias colunas são lidas juntas, mantê-las organizadas para taxa de varredura também ajuda a reduzir cache misses durante a execução vetorizada.
Parquet é forte nesse caminho.
Resultados de ANN precisam de consultas estreitas em nível de linha
Depois que a busca ANN retorna IDs de linhas candidatas, o sistema frequentemente precisa buscar campos como:
caption
embedding
rerank feature
video_uri
metadata
Esse padrão lê menos linhas, muitas vezes centenas ou milhares, mas precisa de acesso preciso por ID de linha. Ele quer localizar uma linha e coluna específicas, buscar apenas o intervalo de bytes necessário e evitar carregar um grupo de linhas inteiro apenas para recuperar alguns registros.
A consulta pontual tem uma preferência quase oposta à varredura. Ela quer uma granularidade de leitura menor. Idealmente, a camada de armazenamento consegue encontrar o segmento ou intervalo de bytes relevante por ID de linha, ler apenas esse intervalo e decodificar somente os dados necessários para o resultado.
A compressão também tem uma relação de troca diferente. Para varreduras, uma compressão mais pesada costuma valer a pena porque o sistema lê muitos dados e economiza E/S. Para consulta pontual, a compressão pode se tornar um problema se recuperar uma linha exigir decodificar um bloco compactado muito maior.
Um único layout não consegue otimizar para ambos os caminhos
Este é o conflito central. Filtragem e análises escalares querem layouts largos, comprimidos e amigáveis a varreduras. Busca vetorial quer layouts estreitos, precisos e endereçáveis por linha.
Um único formato de arquivo pode oferecer suporte a ambos até certo ponto, mas não pode ser ideal para ambos simultaneamente.
Se todas as colunas vivem em Parquet, as varreduras escalares são confortáveis. Mas a busca ANN após a recuperação se torna mais difícil. O sistema pode precisar de apenas algumas centenas de vetores, legendas ou registros de metadados, enquanto a camada de armazenamento pode ter que ler grandes grupos de linhas que contêm majoritariamente linhas irrelevantes.
Em um SSD local, cache e mmap podem ocultar parte desse custo. Quando os dados são armazenados em object storage, o custo se torna mais visível. Cada falha de cache pode se tornar uma leitura remota de intervalo. Se as linhas candidatas estiverem espalhadas por muitos grupos de linhas, uma única consulta pode acionar várias leituras, cada uma trazendo mais dados do que a consulta precisa. Em um layout mal organizado, buscar 1.000 linhas candidatas pode facilmente resultar em dezenas ou centenas de megabytes de I/O desnecessário e, em casos extremos, muito mais.
Tornar os grupos de linhas menores ajuda na busca pontual, mas prejudica as varreduras. Fragmentos pequenos demais reduzem a eficiência da compressão, aumentam a sobrecarga de metadados e quebram as longas leituras sequenciais das quais os mecanismos analíticos dependem.
Portanto, o problema não é encontrar um único tamanho mágico de grupo de linhas. O problema é que o mesmo dataset está sendo solicitado a se comportar como dois sistemas de armazenamento diferentes.
A busca híbrida força ambos os caminhos em uma única consulta
A busca híbrida torna o conflito mais difícil de ignorar. Uma única consulta pode primeiro aplicar filtros escalares:
aesthetic_score > 0.8 AND duration > 5
Depois executa a busca ANN.
Depois busca legenda, vetor e metadados por ID de linha.
Para o usuário, isso é uma única solicitação de busca. Para a camada de armazenamento, é tanto uma varredura analítica quanto uma busca aleatória de baixa latência.
É por isso que o armazenamento vetorial precisa de mais do que uma configuração melhor do Parquet. Ele precisa de uma forma de posicionar diferentes colunas de acordo com a maneira como elas são realmente lidas.
O terceiro problema: o dataset não vive dentro de um único mecanismo
Os dois primeiros problemas acontecem dentro do banco de dados. O terceiro acontece na fronteira entre sistemas.
Pipelines de dados de IA abrangem muitos sistemas
No fluxo de trabalho de vídeo, muito pouco acontece dentro do próprio banco de dados vetorial.
Os vídeos brutos vivem em object storage. A geração de clipes pode rodar em Spark ou Ray. A pontuação estética pode rodar em um serviço de GPU. A geração de legendas pode rodar em um pipeline de inferência de LLM. Embeddings podem ser gerados por outro job de GPU. Vetores esparsos podem vir de um serviço SPLADE. Avaliação offline, filtragem de dados de treinamento, revisão humana e jobs de governança podem todos rodar em outros lugares.
O banco de dados vetorial atende à busca online, mas o dataset é produzido, corrigido, avaliado e estendido por muitos sistemas.
Formatos de armazenamento privados criam múltiplas cópias da verdade
Se o banco de dados usa um formato físico privado que somente ele pode ler e escrever, todo job externo precisa de uma exportação, uma conversão, uma cópia e uma importação. A mesma coleção pode existir no banco de dados, em um diretório temporário do Spark, em uma saída de avaliação e em um diretório local de backfill. Então a verdadeira questão se torna:
- Qual cópia é a fonte da verdade?
- Qual delas contém o modelo de legendas do mês passado?
- Quais linhas já foram corrigidas por revisão humana?
- Qual coluna de vetor esparso foi gerada por qual modelo?
- Qual índice vetorial ainda é válido após o backfill?
- A qual objeto de vídeo original esta linha se refere?
Em pequena escala, às vezes as equipes conseguem sobreviver com convenções de nomenclatura e verificações manuais. Com centenas de milhões de linhas e terabytes de embeddings, isso se torna um problema de consistência.
Datasets vetoriais precisam de um estado versionado compartilhado
Os sistemas Lakehouse abordaram uma versão desse problema para dados estruturados. Iceberg, Delta Lake e Hudi não tratam apenas de armazenar arquivos. Sua principal contribuição é permitir que múltiplos mecanismos se coordenem em torno do mesmo estado de tabela.
Bancos de dados vetoriais agora precisam de uma capacidade semelhante, mas o estado é mais complexo. Ele deve incluir não apenas arquivos de tabela e partições, mas também índices vetoriais, índices de texto, recursos esparsos, logs de exclusão, estatísticas, intervalos de IDs de linha e referências a blobs externos.
A questão não é simplesmente: “O Spark consegue ler arquivos do Milvus?”
A questão é: depois que o Spark preenche retroativamente uma coluna de vetor esparso, como o Milvus sabe a qual versão essa coluna pertence, quais linhas ela cobre, qual modelo a produziu e quando as consultas online podem usá-la com segurança?
A resposta precisa estar no modelo de armazenamento.
Por que patches não são suficientes
É tentador tratar isso como três problemas de engenharia separados.
- Amplificação de escrita? Adicione batching.
- Leituras pontuais? Adicione um cache.
- Sistemas externos? Adicione ferramentas de exportação e importação.
Esses patches podem ajudar, mas não abordam o problema subjacente: um dataset vetorial é fisicamente heterogêneo.
No exemplo de vídeo, clip_id, video_id, duration e aesthetic_score são campos escalares curtos. Eles são úteis para filtragem e análise.
captioné texto. Ele pode ser usado para BM25, revisão, correção e preenchimento retroativo.embeddingé um vetor denso e longo. Ele é usado para recall ANN e, posteriormente, para busca em nível de linha ou reranking.embedding_v2é a saída de um novo modelo, frequentemente preenchida retroativamente muito depois de os dados originais terem sido inseridos.sparse_vectordá suporte à busca híbrida e tem seu próprio padrão de acesso.- O vídeo bruto deve permanecer no armazenamento de objetos. O banco de dados deve armazenar uma referência, um checksum, um tipo MIME, uma versão do parser e uma relação em nível de linha.
- Índices vetoriais, índices de texto, estatísticas e logs de exclusão são objetos derivados com sua própria semântica de versão.
Esses objetos compartilham uma linha lógica, mas não devem todos compartilhar o mesmo layout físico ou ciclo de vida.
- Se forem forçados a um layout de tabela comum, as atualizações se tornam caras.
- Se forem forçados a um único formato de arquivo colunar, as leituras pontuais se tornam caras.
- Se forem tratados como arquivos de objeto não relacionados, o gerenciamento de versões se torna frágil.
Portanto, o modelo de armazenamento precisa partir do fato de que o dataset é heterogêneo.
Isso leva a três requisitos de design:
- Primeiro, diferentes grupos de colunas devem ser armazenados em diferentes formatos físicos.
- Segundo, esses grupos de colunas precisam de um espaço de IDs de linha compartilhado, para que ainda possam se comportar como uma única tabela lógica.
- Terceiro, o dataset precisa de um Manifest versionado que declare quais arquivos, índices, logs, estatísticas e referências a objetos pertencem à visão atual.
Esse é o design por trás do Loon, nosso novo mecanismo de armazenamento por trás do Milvus e do Zilliz Cloud.
Loon: um mecanismo de armazenamento por trás do Milvus e do Zilliz Cloud para datasets vetoriais em evolução
Para resolver todos os problemas acima, criamos o Loon, o novo mecanismo de armazenamento para o Milvus e o Zilliz Vector Lakebase (a próxima evolução do Zilliz Cloud), projetado para datasets vetoriais em evolução.
O nome segue a tradição da Zilliz de nomear com aves. Um loon é uma ave mergulhadora que vive em lagos, o que se alinha bem ao objetivo do sistema: um banco de dados vetorial não deveria precisar mover, varrer ou reescrever um lago inteiro de dados toda vez que executa uma consulta, preenche retroativamente uma coluna ou constrói um índice. Ele deve primeiro entender a versão atual do dataset, incluindo suas colunas, índices, estatísticas, logs de exclusão e referências a objetos, e então ler apenas a parte de que realmente precisa.
Formatos de arquivo híbridos, alinhamento de IDs de linha e Manifest não são três recursos separados. Eles derivam da mesma premissa de design: um dataset vetorial é inerentemente heterogêneo.
Três peças, um modelo de armazenamento
Formatos de arquivo híbridos reconhecem que colunas diferentes têm padrões de acesso diferentes. Campos escalares são bons para varreduras e filtros. Campos vetoriais precisam de busca eficiente em nível de linha. Objetos brutos, como vídeos, PDFs, imagens e arquivos de áudio, pertencem ao armazenamento de objetos, não dentro de arquivos de dados de banco de dados.
O alinhamento de ID de linha reconhece que essas colunas podem estar fisicamente separadas, mas ainda descrevem as mesmas linhas lógicas. Uma legenda, um embedding, um vetor esparso e uma URI de vídeo podem residir em arquivos e formatos diferentes, mas ainda precisam ser reunidos novamente como um único resultado.
O Manifest reconhece que o conjunto de dados não é escrito uma vez e deixado de lado. Ele será modificado por vários sistemas, em várias versões, para várias tarefas. Índices, estatísticas, logs de exclusão, referências a objetos externos e grupos de colunas devem todos aparecer na mesma visualização versionada.
É por isso que o Loon não é apenas um formato de arquivo vetorial mais rápido. Um formato mais rápido ajuda na busca pontual, mas não resolve a evolução de esquema nem a coordenação entre múltiplos mecanismos. O alinhamento de ID de linha permite que colunas divididas se comportem como uma única tabela, mas não especifica quais arquivos pertencem à versão atual. Um Manifest pode descrever o estado de um conjunto de dados, mas, sem grupos de colunas e alinhamento de ID de linha, ele não consegue representar de forma limpa diferentes layouts físicos dentro de uma coleção lógica.
O modelo de armazenamento precisa dos três: formatos diferentes para diferentes grupos de colunas, um espaço compartilhado de IDs de linha para reconstruir linhas e um Manifest versionado que informe a todos os leitores e escritores o que o conjunto de dados é atualmente.
Onde o Loon se encaixa no Milvus e no Zilliz Vector Lakebase
No Milvus, ele substitui a antiga camada de armazenamento de binlog de segmentos por um modelo construído em torno de abstrações de Manifest, ColumnGroup, formato de arquivo e sistema de arquivos. No Zilliz Vector Lakebase (a próxima evolução do Zilliz Cloud), a mesma direção se aplica à arquitetura do Vector Lakebase: manter rápido o caminho de atendimento do banco de dados vetorial, ao mesmo tempo em que torna os dados subjacentes mais fáceis de evoluir, analisar e coordenar com sistemas externos.
Os componentes de nível superior do Milvus ainda mantêm suas funções familiares. O Proxy lida com o roteamento. QueryCoord e DataCoord lidam com o agendamento. IndexNode cria índices. As APIs voltadas para a aplicação para coleções, inserções, buscas e buscas híbridas não precisam expor arquivos Manifest ou ColumnGroups.
A mudança está por baixo.
DataNode, QueryNode, segcore, compactação e conectores externos podem operar por meio da mesma abstração de armazenamento. Isso importa porque o conjunto de dados não é mais escrito e lido apenas pelo banco de dados. Ele pode ser estendido por sistemas de computação externos e consumido por busca online simultaneamente.
Em alto nível, as camadas se parecem com isto:
Manifest
→ ColumnGroup
→ file format layer
→ filesystem abstraction
O Manifest descreve o estado versionado do conjunto de dados. ColumnGroups mapeiam uma coleção lógica para grupos físicos de colunas. A camada de formato de arquivo permite que cada ColumnGroup escolha um formato apropriado. A abstração de sistema de arquivos funciona em armazenamento de objetos e armazenamento local.
O ponto importante é que formatos de arquivo híbridos, alinhamento de ID de linha e Manifest não são recursos separados. Juntos, eles definem o modelo de armazenamento.
Com esse modelo em vigor, podemos examinar as três decisões de design uma a uma: como o Loon armazena diferentes ColumnGroups, como ele as alinha de volta em linhas e como o Manifest transforma esses arquivos em um conjunto de dados versionado.
Design 1: use o formato de arquivo certo para o grupo de colunas certo
Colunas diferentes têm padrões de acesso diferentes. Elas não devem ser forçadas ao mesmo formato de arquivo.
Loon separa uma coleção lógica em ColumnGroups.
- Campos escalares, campos de filtro, chaves de negócio e campos estatísticos são frequentemente varridos, filtrados, agregados ou usados para planejamento de consultas. Eles se beneficiam de compressão, eliminação de colunas e compatibilidade com o ecossistema. Parquet é uma boa opção para essas colunas.
- Vetores densos, vetores esparsos e recursos de rerank são frequentemente lidos após a recuperação ANN por ID de linha. Eles precisam de acesso aleatório de baixa latência, leituras precisas de intervalos de bytes e decodificação seletiva. Um layout orientado a segmentos é mais adequado. Loon usa Vortex nessa direção.
- Objetos brutos, como vídeos, PDFs, imagens e arquivos de áudio, não devem ser incorporados aos arquivos de dados do banco de dados vetorial. Eles devem permanecer no armazenamento de objetos. O banco de dados registra referências, checksums, tipos MIME, versões de parser e relacionamentos em nível de linha.
Para o exemplo de vídeo, um layout físico poderia se parecer com isto:
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 a aplicação, isso ainda é uma única coleção. Para a camada de armazenamento, diferentes partes dessa coleção usam diferentes formatos físicos. Isso reduz diretamente regravações desnecessárias. Adicionar embedding_v2 pode se tornar um novo ColumnGroup vetorial mais um commit de Manifest. Isso não exige regravar a coluna de legenda, os metadados escalares ou a coluna de embedding existente.
A mesma ideia se aplica a vetores esparsos, recursos de rerank ou outros campos derivados. Se uma nova coluna puder ser fisicamente independente e alinhada por ID de linha, ela não precisa arrastar colunas não relacionadas pelo mesmo caminho de regravação.
Loon também adapta o uso de formatos de arquivo.
Para Parquet, as configurações padrão nem sempre são ideais para dados com muitos vetores. Um grupo de linhas de 64 MB pode ser grande demais para busca pontual, porque uma pequena leitura aleatória pode trazer muito mais dados do que o necessário. Loon reduz os grupos de linhas para 1 MB nos caminhos relevantes e desativa codificações, como codificação por dicionário em colunas vetoriais, quando elas não ajudam dados vetoriais de aparência aleatória.
Para Vortex, o trabalho mais importante é o layout. Loon usa um layout que equilibra eficiência de varredura e busca pontual. Dentro de um grupo de linhas, segmentos de colunas relacionadas podem ser colocados próximos uns dos outros para dar suporte à varredura. Para executar operações, leituras de subsegmentos permitem que o sistema busque apenas os bytes relevantes, em vez de puxar um segmento inteiro.
Loon também oferece suporte à integração Lance somente leitura, para que datasets Lance existentes possam ser montados como ColumnGroups quando a compatibilidade for importante.
O que o benchmark mostra
Em um teste local, usando um único arquivo com 40.000 linhas e o schema {id: int64, name: utf8, value: float64, vector: list<float32>[128]}, Vortex apresentou estes resultados em comparação com Parquet com grupos de linhas de 1 MB:
| Operação | Vortex | Parquet | Diferença |
|---|---|---|---|
| Take, K=1000 linhas aleatórias | 5,8 ms | 144 ms | 25x mais rápido |
| Varredura completa da coluna vetorial | 21 ms | 142 ms | 6,76x mais rápido |
| Tamanho do arquivo, ~21 MB de dados brutos | 6,62 MB | 7,16 MB | 7% menor |
O resultado de take vem da redução da quantidade de dados irrelevantes que precisam ser lidos e decodificados. O resultado da varredura vem da compressão e das escolhas de implementação.
Esses números devem permanecer vinculados à sua configuração: 8 vCPU Ubuntu 22.04 KVM, sistema de arquivos local, um arquivo, 40.000 linhas, grupos de linhas de 1 MB e o schema acima. Em armazenamento de objetos, a E/S de rede pode dominar, então reduzir a amplificação de leitura pode importar ainda mais. Os resultados reais dependem do formato do dataset, do comportamento do armazenamento de objetos, do estado do cache e do padrão de consulta.
O ponto mais amplo não é que toda coluna deva usar Vortex.
O ponto é que datasets vetoriais precisam de uma escolha de formato de arquivo no nível do ColumnGroup.
Design 2: alinhar arquivos físicos por meio de IDs de linha
Formatos de arquivo híbridos resolvem um problema: colunas diferentes agora podem ficar nos formatos que melhor se ajustam a elas.
Mas isso cria um segundo problema. Se campos escalares ficam em Parquet, vetores ficam em Vortex, e objetos brutos ficam em armazenamento de objetos, como o sistema ainda os trata como uma única coleção?
Loon resolve isso com alinhamento por ID de linha.
O ID de linha é o sistema de coordenadas da camada de armazenamento
Cada ColumnGroupFile físico registra o caminho do arquivo e o intervalo de IDs de linha que cobre:
path
start_index
end_index
Diferentes ColumnGroups podem cobrir o mesmo espaço de IDs de linha, mesmo que fiquem em arquivos e formatos diferentes.
Para o ID de linha 12345, os metadados escalares podem estar em um ColumnGroup Parquet, o embedding pode estar em um ColumnGroup Vortex, e o vídeo bruto pode ser representado por uma referência de armazenamento de objetos. Logicamente, eles ainda são uma única linha. Isso dá à camada de armazenamento um sistema de coordenadas estável.
O ID de linha não é a chave primária de negócio. Ele é o sistema de coordenadas da camada de armazenamento que permite ao Loon dividir uma coleção fisicamente sem perder a capacidade de reconstruí-la logicamente.
Novas colunas não precisam reescrever colunas antigas
Adicionar embedding_v2 não exige reescrever os ColumnGroups originais de legenda, metadados ou embedding_v1. O Loon pode gravar um novo ColumnGroup de vetores, registrar o intervalo de IDs de linha que ele cobre e confirmar essa alteração por meio do Manifest.
O mesmo se aplica a vetores esparsos, recursos de rerank ou outros campos derivados que chegam mais tarde.
Desde que o novo ColumnGroup cubra o intervalo correto de IDs de linha, ele pode se juntar à mesma coleção lógica sem forçar dados não relacionados a se moverem.
Exclusões e compactação podem ser mais direcionadas
O alinhamento por ID de linha também ajuda com exclusões.
Uma exclusão pode primeiro ser expressa por meio de um log de exclusão. A linha se torna invisível no nível lógico, enquanto a limpeza física é adiada até a compactação. Quando a compactação finalmente é executada, ela nem sempre precisa reescrever todos os ColumnGroups vinculados às linhas afetadas. Ela pode se concentrar nos ColumnGroups que precisam de limpeza.
Isso importa porque nem toda coluna tem o mesmo perfil de custo. Reescrever um ColumnGroup escalar curto é muito diferente de reescrever centenas de gigabytes de vetores densos.
A busca híbrida pode buscar apenas as colunas de que precisa
O alinhamento por ID de linha também é o que torna a busca híbrida prática sobre formatos de arquivo híbridos.
Depois que a busca ANN retorna IDs de linha candidatos, o sistema pode buscar apenas os campos necessários para o resultado final: legendas, metadados, vetores, recursos de rerank ou referências de objetos.
Por exemplo, uma consulta pode precisar de:
caption
embedding
video_uri
Esses campos podem ficar em ColumnGroups diferentes. O Loon pode localizar os arquivos relevantes pelo intervalo de IDs de linha, ler os intervalos de bytes necessários e montar o resultado.
Sem alinhamento por ID de linha, formatos híbridos seriam apenas arquivos separados lado a lado. Com alinhamento por ID de linha, eles se comportam como uma única coleção lógica.
O Packed Reader oculta a divisão da camada superior
O componente de runtime que torna isso utilizável é o Packed Reader.
A camada superior vê um fluxo unificado de Arrow RecordBatch. Por baixo, os dados podem vir de múltiplos ColumnGroups em diferentes formatos de arquivo. O Packed Reader oculta essas diferenças, alinha os dados por intervalos de ID de linha e agenda I/O de múltiplos arquivos com uso de memória controlado.
Ele também oferece suporte a take direto por ID de linha. Dado um conjunto de IDs de linha, ele localiza os ColumnGroupFiles relevantes, emite leituras de intervalo e retorna os campos solicitados.
Para o fluxo de trabalho de vídeo, uma consulta ANN pode precisar de caption, embedding e video_uri. O Packed Reader pode buscar o ColumnGroup escalar e o ColumnGroup vetorial sem tocar em colunas não relacionadas.
Essa é a diferença entre “arquivos separados” e “uma tabela com múltiplos layouts físicos.”
Design 3: tornar o Manifest a fonte da verdade
Formatos de arquivo híbridos definem como os dados são armazenados fisicamente. O alinhamento de IDs de linha determina como ColumnGroups separados ainda formam uma única tabela lógica. Mas o sistema ainda precisa responder a uma pergunta maior: quais arquivos, logs, estatísticas, índices e referências a objetos pertencem à versão atual do conjunto de dados? Esse é o trabalho do Manifest.
Diretórios de armazenamento de objetos não são suficientes
Armazenamento de objetos não é um catálogo de banco de dados. Um diretório pode conter arquivos antigos, arquivos novos, saídas de jobs com falha, arquivos temporários, logs de exclusão, arquivos ainda referenciados por snapshots antigos e arquivos aguardando limpeza. O fato de um arquivo existir não significa que ele pertença à versão atual do conjunto de dados.
Um conjunto de dados Loon pode ser organizado em diretórios como:
_metadata/
_data/
_delta/
_stats/
_index/
Mas a estrutura de diretórios não é a fonte da verdade. O Manifest é. Leitores não devem listar diretórios e inferir o estado a partir de quaisquer arquivos que por acaso existam. Eles devem ler o Manifest atual e seguir a visão versionada que ele declara.
O Manifest define uma visão versionada do conjunto de dados
O Manifest define o conjunto de dados em uma determinada versão. Ele registra:
- quais ColumnGroups existem
- quais intervalos de IDs de linha eles cobrem
- qual formato físico cada ColumnGroup usa
- onde os arquivos ficam
- quais logs de exclusão estão ativos
- quais estatísticas estão disponíveis
- quais índices existem
- quais blobs externos são referenciados
- quais colunas e intervalos de linhas essas estatísticas ou índices cobrem
Cada atualização escreve uma nova versão do Manifest. Um leitor que abre a versão N vê uma visão estável do conjunto de dados na versão N. Um escritor pode preparar a versão N+1 sem interromper leitores que ainda estão usando a versão N.
O Manifest rastreia mais do que arquivos de tabela
No Loon, o corpo do Manifest é codificado com Apache Avro e organizado em torno de quatro seções principais.
- ColumnGroups descrevem as colunas, formatos, arquivos e intervalos de IDs de linha.
- DeltaLogs descrevem exclusões. Diferentes tipos de exclusão cobrem diferentes fontes de mudança, como exclusões por chave primária vindas de clientes, exclusões posicionais de compactação interna ou exclusões por igualdade vindas de mecanismos externos.
- Stats incluem metadados de planejamento, como filtros de Bloom, estatísticas BM25 e valores mínimo/máximo.
- Indexes descrevem tipo de índice, parâmetros, colunas cobertas e intervalos de IDs de linha. Isso pode incluir índices vetoriais como HNSW ou IVF, índices de texto, índices invertidos, índices bitmap e estruturas relacionadas.
É aqui que o Loon difere de um manifest de tabela tradicional.
Um conjunto de dados vetoriais precisa rastrear não apenas arquivos de dados e partições. Ele também precisa rastrear índices vetoriais, índices de texto, atributos esparsos, logs de exclusão, estatísticas, referências a objetos externos e os intervalos de IDs de linha que os conectam.
O Manifest deve poder ser escrito por mais do que o banco de dados
A parte mais importante não é apenas o que o Manifest contém. É quem pode escrevê-lo.
- Se apenas o banco de dados puder escrever o Manifest, ele permanece como metadados internos. Metadados mais limpos, mas ainda privados de um único mecanismo.
- Se mecanismos externos puderem gerar novos ColumnGroups, estatísticas e entradas do Manifest, o Manifest se torna uma interface de coordenação.
- Um job Spark, por exemplo, pode preencher retroativamente uma coluna vetorial esparsa. Ele escreve um novo ColumnGroup, registra a cobertura de linhas e as estatísticas, e faz commit de um novo Manifest. Consultas online podem continuar lendo a versão antiga durante o job. Depois que o commit é bem-sucedido, a nova versão se torna visível.
Isso é semelhante em espírito ao Iceberg e ao Delta Lake, mas o modelo de objetos é mais amplo. Um conjunto de dados vetoriais precisa rastrear índices vetoriais, índices de texto, atributos esparsos, logs de exclusão, estatísticas, referências a blobs e intervalos de IDs de linha, não apenas arquivos de tabela e partições.
Commits otimistas mantêm as atualizações de versão simples
Cada commit grava uma nova versão do Manifest. Um gravador pode criar novo conteúdo com base na versão N e, então, tentar gravar manifest-{N+1}.avro. A gravação condicional do armazenamento de objetos ou a semântica de correspondência de geração podem fazer o commit falhar se essa versão já existir. O gravador pode então tentar novamente contra a versão mais recente.
Isso dá ao Loon concorrência otimista sem forçar cada atualização a passar por um caminho de coordenação pesado e fortemente consistente. Sem um Manifest, o armazenamento multiformato e multimotor acaba se transformando em convenções de nomenclatura e reconciliação manual. Isso pode funcionar para pequenos conjuntos de dados. Não funciona para dados vetoriais em escala de TB.
O Manifest é o que transforma arquivos heterogêneos em um conjunto de dados que múltiplos sistemas podem ler e atualizar com segurança.
O que muda para os usuários quando o armazenamento se torna versionado
Para desenvolvedores de aplicações, o Loon não deve se tornar uma nova carga de API.
Os usuários ainda devem trabalhar com conceitos familiares do Milvus: coleções, inserções, busca e busca híbrida. Eles não devem precisar pensar em arquivos Manifest, ColumnGroups, intervalos de IDs de linha ou layout de arquivos durante o desenvolvimento normal de aplicações.
A mudança está por baixo. O armazenamento se torna mais consciente de como os conjuntos de dados de IA realmente evoluem.
Adicionar um novo embedding não deve mover os dados antigos
Anteriormente, adicionar embedding_v2 a uma coleção existente frequentemente exigia exportar dados, treinar um novo modelo, gerar vetores e então reimportar ou atualizar em massa a coleção via SDK. Esse caminho cria muito trabalho operacional: rastreamento de versões, novas tentativas de jobs com falha, reconstruções de índice, impacto no serviço e verificações de consistência.
Com o Loon, isso pode se tornar uma evolução de esquema mais um novo commit de ColumnGroup. A nova coluna de embedding pode ser gravada como seu próprio ColumnGroup físico, alinhado por ID de linha, e tornada visível por meio do Manifest. A coluna de legenda antiga, a coluna de metadados escalares e a coluna de embedding original não precisam ser movidas.
Backfills não devem exigir um loop de atualização no lado do cliente
Muitas atualizações de dados de IA são backfills. Uma equipe pode adicionar vetores esparsos depois que a busca híbrida se torna importante. Ela pode adicionar recursos de rerank depois que um novo modelo é treinado. Ela pode corrigir legendas após revisão humana. Ela pode adicionar tags de governança após uma atualização de política.
Em um layout tradicional, essas alterações frequentemente ocorrem por meio de atualizações via SDK do cliente ou caminhos de gravação apenas no banco de dados, mesmo quando os dados são produzidos pelo Spark, Ray ou outro motor externo.
Com o Loon, sistemas de computação externos podem produzir novos ColumnGroups e fazer commit deles por meio do Manifest. O banco de dados não precisa mais ser o único ponto de entrada para cada regravação.
A análise offline não deve exigir outra cópia da verdade
Anteriormente, equipes frequentemente despejavam uma coleção online em Parquet para avaliação ou análise offline. Isso cria duas versões do mesmo conjunto de dados: a coleção online e a cópia de análise. Depois que legendas são corrigidas, embeddings são regenerados, logs de exclusão são aplicados ou índices são reconstruídos, a equipe precisa perguntar qual cópia está atual.
Com um modelo de armazenamento baseado em Manifest, motores de análise podem ler a mesma visão versionada do conjunto de dados que o sistema de serviço. Eles podem projetar apenas as colunas de que precisam, varrer apenas os intervalos de linhas relevantes e trabalhar contra uma versão declarada do conjunto de dados em vez de um snapshot exportado manualmente.
Exclusões e correções devem tocar apenas o que mudou
Exclusões, correções de legendas, ajustes de rótulos e atualizações de governança são rotineiros em conjuntos de dados de IA. Eles não devem forçar cada coluna vetorial longa a passar pelo mesmo caminho de regravação.
Com o Loon, logs de exclusão podem primeiro ser tratados como exclusão lógica. Mais tarde, a compactação pode limpar os ColumnGroups afetados sem regravar dados não relacionados. Se um campo de texto curto muda, a camada de armazenamento não deve ter que regravar centenas de gigabytes de vetores densos apenas porque eles compartilham a mesma linha lógica.
Motores externos se tornam parte do fluxo de trabalho, não uma saída de emergência
A mudança maior é que mecanismos externos não são mais tratados como sistemas fora do banco de dados vetorial.
Spark, Ray, tarefas de avaliação, sistemas de rotulagem e pipelines de governança já produzem e modificam grande parte dos dados. A camada de armazenamento deve permitir que eles colaborem em torno de uma única fonte da verdade, em vez de exportar, copiar e reimportar constantemente.
É isso que uma versão do Manifest torna possível. Ele oferece ao serving online, à análise offline, às tarefas de backfill e à compactação uma visão compartilhada do dataset.
Isso pode soar como detalhes internos de armazenamento, mas afeta a rapidez com que as equipes conseguem iterar em datasets de IA. Cada mudança de modelo, backfill de feature, correção de legenda, filtro de qualidade e reconstrução de índice depende da mesma pergunta: "O sistema consegue atualizar o dataset sem mover dados que não precisa mover? "
Esse é o valor prático do modelo de armazenamento.
Loon está disponível no Milvus 3.0 beta e no Zilliz Vector Lakebase
Loon está disponível no Milvus 3.0 beta e também faz parte da camada de armazenamento no Zilliz Vector Lakebase, a próxima evolução do Zilliz Cloud. E esta versão se concentra em três áreas principais:
- O Manifest. O objetivo é que gravações, backfills, exclusões, estatísticas e atualizações de índice produzam visões versionadas do dataset que os leitores possam abrir de forma consistente. Para leitores, isso significa que uma consulta pode abrir uma versão específica do Manifest e ver uma visão estável do dataset. Para escritores, isso significa que novos arquivos de dados, logs de exclusão, estatísticas ou arquivos de índice podem ser preparados primeiro e depois tornados visíveis por meio de um commit versionado.
- O ColumnGroup e o suporte a formatos. Parquet oferece suporte a colunas escalares e amigáveis ao ecossistema. Vortex oferece suporte a padrões de acesso com uso intensivo de vetores. Lance pode ser integrado em modo somente leitura para compatibilidade com datasets Lance existentes.
- O Index on Lake. Estatísticas escalares, índices de filtragem e índices invertidos de texto podem participar do planejamento baseado em Manifest por intervalo de linhas. Índices vetoriais nativos de lake são mais complexos. HNSW e IVF têm comportamentos diferentes em armazenamento de objetos, e HNSW em particular é sensível a acesso aleatório e localidade de cache. Ele não pode simplesmente reutilizar um layout projetado para um SSD local e esperar o mesmo resultado.
Ainda há trabalho pela frente
- Caminhos de gravação externos importam porque Spark e Ray devem ser capazes de produzir ColumnGroups e commits do Manifest sem forçar cada backfill a passar por um loop de SDK de cliente.
- Interoperabilidade com lakehouse importa porque muitas equipes já usam catálogos e mecanismos de consulta como Iceberg, Delta Lake, Trino, DuckDB e Athena. Dados vetoriais devem poder participar desse ecossistema sem perder desempenho de busca vetorial.
- Layout de índice importa porque índices de grafo e estruturas invertidas têm padrões de acesso diferentes em armazenamento de objetos.
- Semântica de objetos grandes importa porque vídeos brutos, PDFs, imagens e arquivos de áudio exigem gerenciamento de referências, versionamento e comportamento de exclusão alinhados com o dataset vetorial derivado.
O comportamento exato da versão, as configurações padrão e o caminho de migração devem seguir as notas de versão relevantes do Milvus e do Zilliz Cloud. A direção do armazenamento, no entanto, é clara: bancos de dados vetoriais precisam de uma base versionada e nativa de lake sob a camada de serving.
Experimente o Loon no Zilliz Vector Lakebase
Se sua stack atual separa serving online, análise offline, backfills e fluxos de trabalho externos de data lake em sistemas diferentes, vale a pena conhecer o Zilliz Vector Lakebase. Você pode experimentá-lo no Zilliz Cloud. Novos cadastros com e-mail corporativo recebem US$ 100 em créditos gratuitos. Você também pode falar conosco sobre seu caso de uso.
Você também pode acompanhar o lançamento do Milvus 3.0 para ver como o Loon evolui no mecanismo open-source.
O Zilliz Vector Lakebase reúne:
- Atendimento em camadas para diferentes compromissos entre desempenho em tempo real e custo
- Busca sob demanda para cargas de trabalho em larga escala ou exploratórias sem computação sempre ativa
- Busca em data lakes externos, para que você possa indexar e pesquisar diretamente sobre dados existentes no lake
- Busca de espectro completo em vetores, texto, JSON e dados geoespaciais, com recuperação híbrida e reclassificação
- Armazenamento unificado nativo de lake criado com o Vortex, um formato aberto projetado para leituras aleatórias mais rápidas e de menor custo em dados com uso intensivo de vetores
Continue lendo

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.
Milvus/Zilliz + Surveillance: How Vector Databases Transform Multi-Camera Tracking
See how Milvus vector database enhances multi-camera tracking with similarity-based matching for better surveillance in retail, warehouses and transport hubs.

Vector Databases vs. Graph Databases
Use a vector database for AI-powered similarity search; use a graph database for complex relationship-based queries and network analysis.


