Anatomia de um Sistema de Gerenciamento de Banco de Dados Vetorial Nativo da Nuvem
Sinto-me honrado que nosso artigo mais recente, "Manu: A Cloud Native Vector Database Management System" tenha sido aceito pela VLDB'22, uma conferência internacional de destaque em pesquisa de bancos de dados. Neste artigo, discutirei a principal filosofia de design e os princípios por trás do Manu (nome do projeto para o Milvus 2.0), um banco de dados nativo da nuvem criado especificamente para o gerenciamento de dados vetoriais. Você pode consultar nossos artigos anteriores e nosso repositório do GitHub para obter mais informações.
Contexto
Quando projetamos o Milvus 1.0, nosso principal objetivo era simplesmente dar suporte ao gerenciamento de vetores e otimizar o desempenho da recuperação de vetores. Mas, à medida que interagimos com mais usuários ao longo dos anos, descobrimos alguns requisitos de negócios comuns para bancos de dados vetoriais que eram difíceis de abordar sob a estrutura inicial.
Essas necessidades podem ser agrupadas nas seguintes categorias: requisitos em constante mudança, uma política de consistência mais flexível, elasticidade em nível de componente e um modelo de processamento de transações mais simples e eficiente.
Requisitos em constante mudança
Os requisitos de negócios ainda não estão totalmente definidos quando se trata de processamento de dados vetoriais.
Nos primeiros dias, a busca pelos K vizinhos mais próximos era a mais necessária. Mas então, cada vez mais requisitos surgiram, incluindo busca por intervalo, suporte a várias métricas de distância personalizadas, busca híbrida, consulta multimodal e outras semânticas de consulta cada vez mais diversas.
Isso exige que a arquitetura de um banco de dados vetorial seja flexível o suficiente para oferecer suporte a novos requisitos de forma rápida e ágil.
Uma política de consistência mais flexível
Tome a recomendação de conteúdo como exemplo: esse cenário tem altos requisitos de pontualidade. Novo conteúdo precisa ser recomendado aos usuários em poucos minutos ou até segundos, portanto o sistema não pode levar um dia ou mais para atualizar sua recomendação. Nesses cenários, é difícil garantir resultados de negócios fornecendo apenas consistência eventual, ao passo que haverá uma grande sobrecarga do sistema se insistirmos em consistência forte.
Para resolver esse problema, propomos a seguinte solução: de acordo com os requisitos de negócios, os usuários podem especificar o atraso máximo que pode ser tolerado antes que os dados inseridos possam ser consultados. O sistema, por sua vez, ajusta determinados mecanismos de processamento de dados para garantir o resultado final para o negócio.
Elasticidade em nível de componente
Os requisitos de recursos e a intensidade de carga variam muito para cada componente de um banco de dados vetorial em diferentes aplicações. Por exemplo, componentes de recuperação e consulta de vetores exigem grandes recursos computacionais e de memória para garantir seu desempenho, enquanto arquivamento de dados e gerenciamento de metadados precisam apenas de poucos recursos para funcionar.
Em termos de aplicações, o requisito mais crítico para sistemas de recomendação é a capacidade de fazer consultas simultâneas em larga escala e, portanto, nesses sistemas, apenas o componente de consulta assume uma carga maior. Aplicações de analytics, por outro lado, muitas vezes precisam importar uma grande quantidade de dados offline, então a pressão de carga recai sobre a inserção de dados e a construção de índices, dois componentes inter-relacionados.
Para melhorar a utilização dos recursos, é necessário que cada módulo funcional tenha escalabilidade independente e elástica, para que a alocação de recursos do sistema possa ser mais estreitamente compatível com as necessidades reais de uma aplicação.
Um modelo de processamento de transações mais simples e eficiente
Estritamente falando, o modelo de transações é um espaço para otimização que pode ser explorado no design do sistema, em vez de um requisito de negócios.
À medida que o aprendizado de máquina evolui em seu poder descritivo, as empresas tendem a fundir dados de múltiplas dimensões de uma única entidade para representá-la como um vetor único e unificado. Por exemplo, na criação de perfis de usuários, informações como perfis pessoais, preferências e relações sociais são fundidas. Como resultado, bancos de dados vetoriais podem ser mantidos com apenas uma única tabela, sem a necessidade de implementar operações semelhantes a JOIN, comuns em bancos de dados tradicionais. Dessa forma, o sistema precisa oferecer suporte apenas a ACID em nível de linha em uma única tabela e pode dispensar transações complexas que envolvem múltiplas tabelas, deixando uma ampla margem para desacoplamento de componentes e otimização de desempenho no sistema.
Objetivos
Como o segundo grande lançamento do Milvus, Manu é posicionado como um sistema de banco de dados vetorial distribuído e nativo da nuvem.
Quando começamos a projetar o Manu, consideramos os diversos requisitos de negócios mencionados acima e os combinamos com os requisitos comuns de um sistema distribuído. Os resultados são cinco objetivos amplos para o Manu: evolutibilidade de longo prazo, consistência ajustável, boa elasticidade, alta disponibilidade e alto desempenho.
Evolutibilidade de longo prazo
Para controlar a complexidade do sistema em um nível gerenciável enquanto as funcionalidades evoluem, precisamos desacoplar bem o sistema para garantir que componentes individuais possam evoluir, ser adicionados ou substituídos de forma independente, com interferência mínima em outros componentes.
Consistência ajustável
O sistema precisa oferecer suporte à consistência delta para que os usuários possam especificar o atraso de visibilidade da consulta para dados recém-inseridos. A consistência delta exige que todas as consultas possam retornar todos os dados relevantes pelo menos até a unidade de tempo delta, que pode ser especificada pela aplicação do usuário com base nos requisitos de negócios.
Boa elasticidade
Para melhorar a eficiência da utilização de recursos, o sistema precisa alcançar elasticidade granular no nível dos componentes, bem como uma política de alocação de recursos que leve em conta várias dependências de hardware dos componentes.
Alta disponibilidade e desempenho
Alta disponibilidade é o requisito básico de todos os bancos de dados em nuvem, o que exige que, caso alguns nós de serviço ou componentes falhem, outros serviços não sejam afetados, e que o sistema seja capaz de uma recuperação eficaz de falhas.
Alto desempenho é um clichê para bancos de dados vetoriais. No processo de design, precisamos controlar rigorosamente a sobrecarga gerada no nível da estrutura do sistema para garantir bom desempenho.
Arquitetura do Manu
Manu adota uma arquitetura de quatro camadas que permite o desacoplamento da leitura da escrita, do sem estado do com estado e do armazenamento da computação.
Como mostrado na figura abaixo, de cima para baixo, Manu possui quatro camadas, ou seja, camada de acesso, camada de coordenadores, camada de workers e camada de armazenamento. Manu também usa um sistema de logs como sua espinha dorsal, que conecta os componentes desacoplados.
Arquitetura do Manu.
Camada de acesso
A camada de acesso consiste em proxies sem estado que atuam como endpoints dos usuários.
Esses proxies recebem solicitações dos clientes, distribuem as solicitações aos componentes correspondentes e coletam os resultados antes de retorná-los aos clientes. Além disso, os proxies armazenam em cache uma cópia dos metadados para verificar a legitimidade das solicitações de busca (por exemplo, se a coleção a ser pesquisada existe).
Camada de coordenadores
A camada de coordenadores gerencia o status do sistema, mantém metadados e coordena os componentes do sistema para o processamento de tarefas.
Há quatro tipos de coordenadores, cada um projetado de forma independente para uma funcionalidade diferente. Dessa forma, falhas do sistema podem ser isoladas e os componentes podem evoluir separadamente. Por questões de confiabilidade, cada coordenador pode ter múltiplas instâncias (por exemplo, uma principal e duas de backup).
Coordenador raiz
O coordenador raiz lida com solicitações de definição de dados, como criar/excluir coleções, e mantém as meta-informações das coleções (por exemplo, as propriedades das coleções, o tipo de dados de cada propriedade).
Coordenador de dados
O coordenador de dados lida com a persistência dos dados. Ele coordena os nós de dados para transformar solicitações de atualização de dados em binlogs e registra as informações detalhadas das coleções (por exemplo, a lista dos segmentos de cada coleção, o caminho de armazenamento de cada segmento).
Coordenador de índices
O coordenador de índices gerencia a indexação de dados. Ele coordena os nós de índice para tarefas de indexação e registra as informações de índice de cada coleção (por exemplo, tipo de índice, parâmetros relacionados, caminho de armazenamento, etc.).
Coordenador de consultas
O coordenador de consultas monitora o status dos nós de consulta e ajusta a atribuição de segmentos (junto com os índices relacionados) aos nós de consulta para balanceamento de carga.
Camada de workers
A camada de workers executa as múltiplas tarefas no sistema.
Todos os nós de worker são sem estado - eles buscam cópias somente leitura dos dados para realizar tarefas e não precisam coordenar entre si. Portanto, o número de nós de worker pode ser ajustado de forma flexível de acordo com a carga. Além disso, Manu usa diferentes nós de worker para diferentes tarefas, de modo que cada tipo de nó possa ser dimensionado independentemente de acordo com a carga real e os requisitos de QoS.
Camada de armazenamento
A camada de armazenamento armazena persistentemente informações de status do sistema, metadados, coleções e índices associados.
Manu usa armazenamentos KV (key-value) distribuídos de alta disponibilidade, como etcd, para armazenar informações de status do sistema e metadados. Quando os metadados são atualizados, os dados serão primeiro gravados no armazenamento KV e depois sincronizados com os coordenadores relevantes. Dados de grande volume, como os de coleções e índices, são gerenciados com serviços de armazenamento de objetos como AWS S3. A alta latência que vem com o armazenamento de objetos não é um gargalo de desempenho porque os nós de worker obtêm cópias somente leitura dos dados do armazenamento de objetos e as armazenam em cache localmente antes de processar os dados, de modo que a maior parte do processamento de dados é feita localmente.
Backbone de logs
Backbone de logs.
Para desacoplar melhor os componentes do sistema (por exemplo, WAL, binlog, nós de dados, nós de índice e nós de consulta), para que cada um possa ser dimensionado e evoluído independentemente, Manu segue o paradigma "log as data" e usa um sistema de logs como seu backbone, que conecta os componentes desacoplados do sistema. No Manu, os logs podem ser assinados persistentemente por diferentes componentes do sistema, que, portanto, são chamados de assinantes dos logs.
Os logs no Manu podem ser divididos em write-ahead log (WAL) e binlog. O WAL é a parte incremental do log do sistema, enquanto o binlog é a parte base. Eles se complementam em atraso, capacidade e custo.
Como mostrado na figura acima, os loggers são os pontos de entrada do sistema de logs, publicando dados no WAL. Os nós de dados assinam o WAL e convertem os WALs baseados em linhas em binlogs baseados em colunas. Todos os componentes somente leitura, como nós de índice e nós de consulta, são assinantes independentes do serviço de logs para se manterem atualizados.
O sistema de logs também serve para passar mensagens entre componentes. Em outras palavras, os componentes podem transmitir eventos do sistema por meio de logs. Por exemplo, os nós de dados podem informar a outros componentes quais segmentos foram gravados no armazenamento de objetos, e os nós de índice podem informar todos os coordenadores de consulta assim que novos índices forem construídos. Além disso, diferentes tipos de mensagens são organizados em diferentes canais. Cada componente só precisa assinar seu canal correspondente em vez de escutar todos os logs de transmissão.
Fluxo de trabalho de processamento de dados
Esta seção detalha o fluxo de trabalho de processamento de dados dentro do Manu e apresenta o processo de inserção de dados, construção de índices e execução de consultas.
Inserção de dados
Fluxo de trabalho de inserção de dados.
A figura acima ilustra o fluxo de trabalho de inserção de dados no Manu e os componentes relevantes envolvidos.
Depois de serem processadas pelo proxy, as solicitações de inserção de dados são distribuídas em vários buckets com base em algoritmos de hash. Geralmente, há vários loggers no sistema Manu lidando com as entidades em cada bucket de hash com base em hashing consistente. As entidades em cada bucket de hash são gravadas em um canal de log de gravação antecipada (WAL) que mapeia apenas para esse bucket. Quando um logger recebe uma solicitação de inserção de dados, ele atribui um número de sequência de log (LSN) globalmente exclusivo a essa solicitação e a grava no canal WAL correspondente. O LSN é gerado pelo oráculo de serviço de tempo central (TSO). Cada logger precisa receber um LSN do TSO e salvar o LSN localmente em intervalos regulares.
Para garantir que o pub/sub de logs tenha baixa latência e seja de granularidade fina, as entidades são armazenadas de forma baseada em linhas no WAL no Manu, e cada componente que assina o WAL lê dados dele de maneira streaming. Na maioria dos casos, o WAL pode ser implementado por meio de uma fila de mensagens baseada em nuvem, como Kafka ou Pulsar. Os nós de dados assinam os WALs e convertem os WALs baseados em linhas em binlogs baseados em colunas. A natureza baseada em colunas do binlog facilita a compactação e o acesso aos dados. Um exemplo dessa eficiência ocorre com os nós de índice. Os nós de índice leem apenas a coluna vetorial necessária do binlog para a construção do índice e, portanto, ficam livres de amplificações de leitura.
Construção de índice
Há dois cenários de construção de índice no Manu - indexação em lote e indexação em fluxo. A indexação em lote ocorre quando o usuário constrói um índice para uma coleção inteira (por exemplo, quando todos os vetores são atualizados com um novo modelo de embedding). Nesse caso, o coordenador de índice obtém os caminhos de todos os segmentos na coleção a partir do coordenador de dados e instrui os nós de índice a construir um índice para cada segmento. A indexação em fluxo acontece quando os usuários inserem continuamente novas entidades, e os índices são construídos de forma assíncrona em tempo real, sem interromper os serviços de busca.
Quando o nó de dados grava um novo segmento no binlog, o coordenador de dados notifica o coordenador de índice para criar uma tarefa para que um nó de índice construa um índice no novo segmento. Tanto nos cenários de indexação em lote quanto em fluxo, depois que o índice necessário é construído para um segmento, o nó de índice o persiste no armazenamento de objetos e envia o caminho de armazenamento ao coordenador de índice, notificando o coordenador de consulta para que os nós de consulta possam carregar o índice para processar consultas.
Execução de consulta
O Manu particiona uma coleção em segmentos e distribui os segmentos entre nós de consulta para executar solicitações de consulta em paralelo. Os proxies armazenam em cache uma cópia da distribuição de segmentos nos nós de consulta consultando o coordenador de consulta, e despacham solicitações de busca para os nós de consulta que contêm segmentos da coleção pesquisada. Os nós de consulta realizam consultas vetoriais em seus segmentos locais, mesclam os resultados e os retornam ao proxy. O proxy agrega ainda mais os resultados de cada nó de consulta e retorna os resultados finais ao cliente.
Os nós de consulta obtêm dados de três fontes - o WAL, os arquivos de índice e o binlog. Para dados históricos, os nós de consulta leem os binlogs ou arquivos de índice correspondentes do armazenamento de objetos. Já para dados incrementais, os nós de consulta leem diretamente do WAL de maneira streaming. Obter dados incrementais do binlog causará latência na visibilidade dos dados, o que é especialmente verdadeiro para solicitações de busca grandes. Em outras palavras, dados recém-inseridos só estarão disponíveis para consulta após um longo período de tempo, o que não atende à necessidade de alta consistência em alguns cenários.
Como mencionado anteriormente, o Manu adota um modelo de consistência delta para permitir que os usuários ajustem os níveis de consistência com mais flexibilidade. A consistência delta garante que os dados atualizados (incluindo dados inseridos e excluídos) possam ser consultados e pesquisados até delta unidades de tempo após a solicitação de atualização de dados ser recebida pelo Manu.
O Manu alcança a consistência delta adicionando LSNs que carregam timestamps a todas as solicitações de inserção e consulta de dados. Ao executar solicitações de consulta, o nó de consulta verifica o timestamp da solicitação (Lr) e o timestamp da solicitação de atualização de dados mais recente processada pelo nó de consulta (Ls). A solicitação de consulta é executada somente quando o intervalo entre Lr e Ls é menor que delta. Caso contrário, a solicitação de consulta espera para ser executada até que as atualizações de dados registradas no WAL sejam processadas. No entanto, se não houver atualização de dados por um longo período, o intervalo de tempo entre Ls e o tempo atual do sistema se tornará tão pequeno que as consultas serão bloqueadas. Para evitar esse problema, o Manu insere regularmente informações de controle no WAL, forçando o nó de consulta a atualizar seu timestamp.
Avaliação de desempenho
No artigo, também integramos o Manu a aplicações do mundo real e conduzimos uma avaliação geral do desempenho do sistema. A seguir estão parte dos resultados da avaliação.
Desempenho de consulta do Manu e de outros sistemas de busca vetorial.
A figura acima compara o Manu com outros quatro sistemas anônimos de busca vetorial open-source em termos de desempenho de consulta. Podemos ver que o Manu supera evidentemente outros sistemas de busca vetorial ao realizar consultas nos conjuntos de dados SIFT e DEEP.
Desempenho de consulta do Manu sob diferentes números de nós.
A figura acima mostra o desempenho de consulta do Manu quando o número de nós de consulta varia. Podemos ver que ao consultar diferentes conjuntos de dados com diferentes métricas de similaridade, o desempenho de consulta do Manu apresenta uma relação aproximadamente linear com o número de nós de consulta.
Desempenho de consulta do Manu sob diferentes níveis de consistência.
As figuras acima demonstram o desempenho de consulta do Manu sob diferentes níveis de consistência. As coordenadas horizontais representam os valores de delta como na consistência delta. Cada figura reflete a frequência das informações de controle enviadas ao WAL que forçam os nós de consulta a sincronizar o tempo. Podemos ver pela figura que a latência de consulta do Manu diminui drasticamente à medida que o valor de delta aumenta. Portanto, os usuários do Manu precisam escolher o valor delta apropriado de acordo com sua necessidade de desempenho e consistência.
Conclusão
Neste artigo, com base nos requisitos do mundo real para banco de dados vetorial, apresentamos os designs do Manu e os fluxos de trabalho de suas principais funcionalidades. Em suma, as duas principais características do Manu são as seguintes:
O Manu usa o backbone de logs para conectar os componentes do sistema, o que permite o escalonamento e a evolução independentes de cada componente e facilita a alocação de recursos e o isolamento de falhas.
Com o sistema de logs e o LSN, o Manu adota um modelo de consistência delta para permitir uma compensação flexível entre consistência, custo e desempenho.
Em resumo, a principal contribuição do nosso artigo VLDB está na introdução da demanda do mundo real por banco de dados vetorial e no design da arquitetura básica de um banco de dados vetorial cloud-native. No momento, a arquitetura ainda está longe de ser perfeita e algumas de nossas direções futuras incluem:
Como recuperar vetores extraídos de conteúdo multimodal;
Como aproveitar melhor os serviços de armazenamento em nuvem, incluindo discos locais, cloud drives e outros serviços de armazenamento, para tornar a recuperação de dados mais eficiente;
Como maximizar o desempenho de indexação e busca com a ajuda de novos hardwares de computação, armazenamento ou comunicação como FPGA、GPU、RDMA、NVM 、RDMA.
Nota final
Há um ano, participei da ACM SIGMOD 2021 em Xi'an com Charles Xie, CEO da Zilliz. A ideia de escrever este artigo me ocorreu enquanto estávamos voltando para Xangai para o lançamento GA do Milvus 2.0 (Manu). Tanto Charles quanto eu percebemos que bancos de dados cloud-native estavam se tornando o novo tema em alta na academia. Foi uma grande coincidência que Manu seja exatamente um sistema de banco de dados cloud-native e desenvolvido especificamente para vetores massivos. Como resultado, viemos a escrever este artigo sobre Manu e o sistema de gerenciamento de banco de dados cloud-native.
Esperamos que nosso artigo possa lançar alguma luz e atrair mais acadêmicos e colegas da indústria para se juntarem a nós na exploração e pesquisa de sistemas de gerenciamento de bancos de dados vetoriais cloud-native.
Também queremos expressar nossa gratidão ao Professor Assistente Bo Tang, ao Professor Assistente de Pesquisa Xiao Yan e a Long Xiang por sua contribuição. Este artigo foi escrito conjuntamente pela equipe da Zilliz e pelo Grupo de Bancos de Dados da Southern University of Science and Technology.
Continue lendo

Data Deduplication at Trillion Scale: How to Solve the Biggest Bottleneck of LLM Training
Explore how MinHash LSH and Milvus handle data deduplication at the trillion-scale level, solving key bottlenecks in LLM training for improved AI model performance.

Optimizing Embedding Model Selection with TDA Clustering: A Strategic Guide for Vector Databases
Discover how Topological Data Analysis (TDA) reveals hidden embedding model weaknesses and helps optimize vector database performance.

Bringing AI to Legal Tech: The Role of Vector Databases in Enhancing LLM Guardrails
Discover how vector databases enhance AI reliability in legal tech, ensuring accurate, compliant, and trustworthy AI-powered legal solutions.



