Dicas e Truques Práticos para Desenvolvedores Criando Aplicações RAG
A busca vetorial não é fácil!
A busca vetorial, também conhecida como busca por similaridade vetorial ou busca de vizinho mais próximo, é uma técnica usada na recuperação de dados para aplicações RAG e sistemas de recuperação de informações para encontrar itens ou pontos de dados que sejam semelhantes ou intimamente relacionados a um determinado vetor de consulta. Ela é frequentemente apresentada como simples ao lidar com grandes conjuntos de dados. A percepção geral é que você pode simplesmente alimentar dados em um modelo de embeddings para gerar embeddings vetoriais e, em seguida, transferir esses vetores para seu banco de dados vetorial para recuperar os resultados desejados.
como realizar uma busca vetorial
Muitos provedores de bancos de dados vetoriais promovem suas capacidades com descritores como "fácil", "amigável ao usuário" e "simples". Eles afirmam que você pode alcançar resultados significativos com apenas algumas linhas de código, contornando as complexidades de machine learning, IA, processos de ETL ou ajustes detalhados do sistema.
E eles estão certos; a busca vetorial é tão fácil quanto usar uma biblioteca numérica básica como NumPy. Para demonstrar esse conceito, escrevi uma breve demonstração em apenas dez linhas de código Python usando o algoritmo de k-vizinhos mais próximos (KNN). Essa abordagem direta é eficaz e precisa para aplicações de pequena escala com conjuntos de dados de até mil ou dez mil vetores.
import numpy as np
# Function to calculate Euclidean distance
def euclidean_distance(a, b):
return np.linalg.norm(a - b)
# Function to perform KNN
def knn(data, target, k):
# Calculate distances between target and all points in data
distances = [euclidean_distance(d, target) for d in data]
# Combine distances with data indices
distances = np.array(list(zip(distances, range(len(data)))))
# Sort by distance
sorted_distances = distances[distances[:, 0].argsort()]
# Get the top k closest indices
closest_k_indices = sorted_distances[:k, 1].astype(int)
# Return the top k closest vectors
return data[closest_k_indices]
No entanto, essa abordagem não funcionará quando seu conjunto de dados crescer para um nível modesto de mais de um milhão ou dez milhões de vetores. Isso ocorre simplesmente porque aplicações do mundo real precisam interagir com usuários, estar disponíveis e são sempre muito mais complicadas. Construir uma aplicação escalável do mundo real exige considerar cuidadosamente vários fatores além da codificação, incluindo qualidade da busca, escalabilidade, disponibilidade, multilocação, custo, segurança e muito mais!
Então, sejamos honestos. Lembra do ditado: "Funciona na minha máquina?" A busca vetorial não é diferente: seu protótipo sempre funciona; a busca vetorial em produção costuma ser complexa, então quais são as melhores práticas para criar uma aplicação baseada em busca vetorial em produção?
Para ajudar você a navegar por esses desafios, compartilharemos três dicas essenciais para implantar efetivamente seu banco de dados vetorial no ambiente de produção da sua aplicação RAG com Milvus:
Projete um schema eficaz: Considere cuidadosamente sua estrutura de dados e como ela será consultada para criar um schema que otimize o desempenho e a escalabilidade.
Planeje a escalabilidade: Antecipe o crescimento futuro e projete sua arquitetura para acomodar volumes crescentes de dados e tráfego de usuários.
Selecione o índice ideal e ajuste o desempenho: Escolha o método de indexação mais adequado para seu caso de uso e monitore e ajuste continuamente as configurações de desempenho.
Ao seguir essas melhores práticas, você estará no caminho certo para construir uma aplicação robusta e eficiente baseada em busca vetorial. Nos comentários abaixo, compartilhe suas experiências e quaisquer dicas adicionais que você tenha achado úteis!
Projetando uma estratégia de schema eficaz
Um esquema define a estrutura de um banco de dados, incluindo as tabelas, campos, relacionamentos e tipos de dados. Essa estrutura organizada garante que os dados sejam armazenados de forma consistente e previsível, simplificando o gerenciamento, as consultas e a manutenção. Selecionar um esquema apropriado é particularmente crítico para bancos de dados vetoriais como o Milvus, que lidam com vetores e vários tipos de dados estruturados, incluindo metadados e dados escalares. Esses dados podem aprimorar a busca filtrada e melhorar os resultados gerais da busca. Esta seção explorará os principais fatores a considerar ao escolher a estratégia de esquema mais eficaz.
Esquema Dinâmico vs. Fixo
Em sistemas de banco de dados, esquemas dinâmicos e fixos representam duas abordagens principais para estruturar dados. Esquemas dinâmicos oferecem flexibilidade, simplificando a inserção e a recuperação de dados sem a necessidade de amplo alinhamento de dados ou processos de ETL. Essa abordagem é perfeita para aplicações que exigem mudanças rápidas na estrutura de dados. Por outro lado, os desenvolvedores valorizam os esquemas fixos por sua eficiência de desempenho e economia de memória, graças aos seus formatos de armazenamento compactos.
Uma abordagem de esquema híbrido pode beneficiar desenvolvedores que trabalham em aplicações eficientes de bancos de dados vetoriais. Esse método combina a robustez dos esquemas fixos para caminhos de dados essenciais com a flexibilidade dos esquemas dinâmicos para acomodar diversos casos de uso. Por exemplo, em um sistema de recomendação, elementos como nomes de produtos e IDs de produtos podem variar em importância dependendo do contexto. Ao empregar um esquema híbrido, os desenvolvedores podem garantir desempenho ideal onde necessário, mantendo a capacidade de se adaptar a requisitos de dados em mudança.
Definindo Chaves Primárias e Chaves de Partição
Chaves primárias e de partição são dois conceitos importantes em bancos de dados vetoriais. Usando o banco de dados vetorial Milvus como exemplo, podemos nos aprofundar em como essas chaves funcionam dentro de bancos de dados vetoriais.
A arquitetura do Milvus segmenta os dados em vários componentes: há campos fixos e dinâmicos (coletivamente chamados de payload), um campo vetorial obrigatório e campos do sistema, como timestamps e identificadores universalmente únicos (UUIDs), que são semelhantes aos encontrados em bancos de dados relacionais convencionais.
Chaves Primárias: No Milvus, a chave primária frequentemente serve como um identificador único, que em um caso de uso de RAG pode ser aplicada a um ID de chunk. Essa chave é acessada com frequência e pode ser configurada para ser gerada automaticamente. Ela desempenha um papel na localização e recuperação rápidas de entradas de dados específicas dentro do banco de dados.
Chaves de Partição: Ao criar uma coleção no Milvus, você pode especificar uma chave de partição. Essa chave permite que o Milvus armazene entidades de dados em diferentes partições com base em seus valores de chave, organizando efetivamente os dados em segmentos gerenciáveis. Uma maneira simples de pensar sobre chaves de partição é considerar o uso de uma chave de partição se houver conjuntos de dados nos quais você deseja aplicar filtros. Por exemplo, isolamento de dados e distribuição eficiente são necessários em situações multi-tenant, portanto armazená-los em partições separadas pode ajudar a alcançar isso. Chaves de partição também são úteis para escalabilidade, pois particionar dados em shards por meio de hashing permite que o banco de dados gerencie bases de usuários em larga escala e multi-tenancy com mais eficiência.
Tanto as chaves primárias quanto as chaves de partição são fundamentais para manter a integridade estrutural e a eficiência operacional dos bancos de dados vetoriais, tornando-as indispensáveis para lidar com conjuntos de dados extensos e garantir acesso e recuperação rápidos de dados.
Escolhendo Tipos de Embeddings Vetoriais
Ao escolher embeddings vetoriais para aplicações de RAG, é imprescindível escolher o modelo de ML certo para a criação de vetores e entender os diferentes tipos de embeddings disponíveis: embeddings densos, esparsos e binários.
Três categorias populares de embeddings vetoriais
Embeddings densos são o tipo mais comumente usado em aplicações de bancos de dados vetoriais para busca por similaridade semântica. Eles são conhecidos por sua robustez e aplicabilidade geral em vários tipos de dados. Modelos populares de embeddings densos incluem OpenAI, BGE e Cohere.
Embeddings esparsos estão ganhando popularidade por sua eficiência na busca de dados fora do domínio. Avanços recentes em modelos como Splade e BGE M3 aprimoraram sua utilidade em buscas heterogêneas, tornando-os uma escolha versátil para aplicações diversas.
Embeddings binários, caracterizados por seu formato binário (zeros e uns), são projetados para serem eficientes em termos de memória, tornando-os ideais para casos de uso especiais, como sequenciamento de proteínas. Modelos como Meta ESM-2 são normalmente usados para gerar esses embeddings, fornecendo soluções direcionadas para necessidades específicas de busca.
Para garantir a precisão dos resultados de busca para aplicações RAG, precisamos usar mais do que apenas embeddings densos. Portanto, precisamos encontrar soluções que suportem diferentes algoritmos de indexação para buscar de forma eficiente e eficaz diferentes tipos de embeddings vetoriais. O Milvus suporta vários índices para gerenciar embeddings densos, esparsos, binários e até híbridos esparsos e densos, permitindo buscas eficientes em várias dimensões de dados e garantindo desempenho ideal em aplicações de bancos de dados vetoriais.
Projetando Seu Esquema: Um Exemplo Prático
Vamos reunir estes elementos que acabamos de revisar para nos ajudar a projetar de forma eficaz uma arquitetura de esquema que aumentará a precisão dos nossos resultados de busca:
| Nome do Campo | Tipo | Descrição | Valor de Exemplo |
|---|---|---|---|
| chunkID | Int64 | Chave primária, identifica exclusivamente diferentes partes de um documento | 123456789 |
| userID | Int64 | Chave de partição, o particionamento de dados é baseado em userID para garantir que as buscas ocorram dentro de um único userID | 987654321 |
| docID | Int64 | Identificador único de um documento, usado para associar diferentes chunks do mesmo documento | 555666777 |
| chunkData | varchar | Uma parte do documento, contendo várias centenas de bytes de texto | "Esta é uma parte do documento..." |
| dynamicParams | JSON | Armazena parâmetros dinâmicos do documento, como nome, URL de origem etc. | {"name": "Documento de Exemplo", "source": "example.com"} |
| sparseVector | Formato específico | Dados que representam um vetor esparso. O formato específico terá valores diferentes de zero apenas em certas posições para representar esparsidade. | [0.1, 0, 0, 0.8, 0.4] |
| denseVector | Formato específico | Dados que representam um vetor denso. O formato específico terá um número fixo de dimensões com valores em cada uma. | [0.2, 0.3, 0.4, 0.1] |
Uma demonstração de esquema para uma aplicação típica de Geração Aumentada por Recuperação (RAG)
Ao examinar este esquema, é crucial observar a presença de campos adicionais, que vão além da chave primária e do campo vetorial. Esses campos extras desempenham um papel na construção e utilização do seu banco de dados vetorial. Vamos nos aprofundar:
O básico: Isso inclui sua chave primária (chunkID) e seus embeddings (denseVector), que cada entrada no banco de dados precisa.
Suporte a multilocação: Adicionamos um campo para particionar dados pelos locatários da nossa solução com a adição de um userID. Essa adição ajuda a segregar e gerenciar o acesso aos dados por usuário, aumentando a segurança e a personalização.
Refinando seus resultados de busca: Adicionamos alguns outros campos para nos ajudar com esse refinamento, incluindo:
docID: Este campo indica a origem do chunk e pode ser usado para aproveitar o recurso de busca por agrupamento do Milvus. Em nosso exemplo, dividimos nosso documento em chunks e armazenamos o embedding vetorial representativo no campo denseVector, e neste campo (docID), armazenamos as informações do documento associado. Você pode incluir o argumentogroup_by_fieldna operação search() para agrupar resultados pelo ID do documento a fim de encontrar documentos relevantes em vez de passagens ou chunks semelhantes. Isso ajuda a retornar os documentos relevantes em vez de chunks separados do mesmo documento.dynamicParams: Este campo pode ser filtrado, permitindo que você gerencie e recupere dados que atendam às suas necessidades. É um verdadeiro tesouro de metadados, como o nome do documento, URL de origem, etc. Definimos o campo json para armazenar vários pares chave-valor em um único campo.sparseVector: Este campo contém o embedding esparso do chunk, o que nos permite realizar uma busca ANN e recuperar resultados com base em um valor escalar associado ao vetor esparso.
O diagrama mostra que podemos reunir resultados de consultas separadas e então reclassificá-los para refinar os resultados da busca.
Ao projetar um schema que inclua esses campos adicionais, você pode criar um banco de dados vetorial mais robusto e flexível que atenda aos requisitos da sua aplicação RAG. Isso permite que você aproveite os pontos fortes da busca vetorial enquanto incorpora técnicas tradicionais de gerenciamento e recuperação de dados.
Plano para escalabilidade
Depois de encontrar sucesso com sua aplicação RAG MVP, é hora de começar a se preparar para a implantação em produção. Isso envolve antecipar o crescimento futuro e projetar sua arquitetura para acomodar volumes crescentes de dados e tráfego de usuários.
Para garantir que sua aplicação possa escalar de forma eficaz, você precisa estar ciente de que a escalabilidade em bancos de dados vetoriais apresenta desafios únicos em comparação com bancos de dados relacionais tradicionais, porque os dados são armazenados em um índice grande e centralizado. Essa configuração pode levar a dois problemas principais: velocidades lentas de indexação e qualidade degradada do índice devido a atualizações frequentes, o que, por sua vez, pode reduzir a qualidade da busca.
A estratégia de sharding do Milvus
O Milvus pode lidar com esses desafios dividindo todo o conjunto de dados em segmentos gerenciáveis; podemos realizar atualizações atrasadas ou compactar segmentos quando eles se tornam instáveis, mantendo uma qualidade de busca consistente. Essa segmentação facilita o balanceamento de carga eficaz, permitindo distribuir consultas uniformemente por todos os núcleos de processamento.
Usar partições para multi-tenancy também pode ajudar na escalabilidade e no desempenho, já que as buscas ficam confinadas aos dados relevantes à partição. Essa abordagem organiza efetivamente os dados e aprimora a segurança e a privacidade ao restringir a visibilidade aos usuários adequados. Além disso, o Milvus pode gerenciar com eficiência até dez bilhões de pontos de dados em uma única coleção. É muita coisa!
Para aplicações multi-tenant com menos de 10.000 tenants, gerenciar dados por coleção oferece maior controle dos dados. No entanto, chaves de partição podem oferecer suporte efetivo a tenants ilimitados ao segmentar dinamicamente os dados para serviços com milhões de usuários.
O Milvus é um sistema distribuído projetado para lidar com grandes volumes de consultas com facilidade. E a melhor parte? Simplesmente adicionar mais nós pode aumentar significativamente o desempenho, abrindo um mundo de possibilidades para suas aplicações. Para conjuntos de dados menores que inicialmente não exigem recursos extensos, aumentar as reservas de memória — normalmente de duas a três vezes a alocação atual — pode efetivamente dobrar as consultas por segundo (QPS). Esse framework escalável garante que, à medida que seus dados crescem, as capacidades do seu banco de dados possam crescer junto com eles, assegurando desempenho eficiente e confiável em todos os aspectos.
Escolhendo, avaliando e ajustando seu índice
Durante a fase de protótipo, carregar todos os dados na memória é comum para um processamento mais rápido e um desenvolvimento mais fácil. No entanto, à medida que você passa para produção e seus dados crescem, torna-se inviável armazenar tudo na memória. Isso ocorre porque:
A memória é limitada e cara em comparação com o armazenamento em disco.
Grandes conjuntos de dados podem exceder a capacidade de memória disponível.
Carregar todos os dados na memória pode aumentar significativamente o tempo de inicialização e o consumo de recursos.
Para lidar com conjuntos de dados maiores de forma eficiente em produção, você precisa escolher uma estratégia de indexação apropriada. O índice certo pode otimizar o desempenho da sua aplicação RAG em termos de velocidade de consulta, requisitos de armazenamento e latência.
Índices compatíveis com Milvus
Este diagrama ajuda a visualizar as diferenças entre vários índices com base em três métricas principais:
Consultas por segundo (QPS): Isso mede quantas consultas de busca o índice consegue processar por segundo, refletindo seu throughput e eficiência.
Armazenamento: Isso representa a quantidade de espaço em disco necessária para armazenar o índice, o que pode impactar os custos de infraestrutura e a escalabilidade.
Latência refere-se ao tempo necessário para processar uma única consulta e retornar os resultados, o que afeta a responsividade da sua aplicação.
Ao comparar essas métricas entre diferentes índices, você pode decidir qual índice melhor atende ao seu caso de uso específico e aos requisitos de desempenho.
No Milvus, fornecemos uma estrutura flexível de seleção de índices adaptada a várias necessidades de armazenamento e desempenho:
GPU Index é nossa principal opção para ambientes de alto desempenho, que oferece suporte a processamento e recuperação rápidos de dados.
Memory Index é uma opção intermediária que equilibra desempenho com capacidade, oferecendo taxas sólidas de consultas por segundo (QPS) e a capacidade de escalar até terabytes de armazenamento com uma latência média de cerca de 10 milissegundos.
Disk Index pode gerenciar dezenas de terabytes com uma latência razoável de cerca de 100 milissegundos, adequado para conjuntos de dados maiores e menos sensíveis ao tempo. Milvus é o único banco de dados vetorial de código aberto que oferece suporte a índice em disco.
Swap Index facilita a troca de dados entre S3 ou outras soluções de armazenamento de objetos e a memória. Essa abordagem reduz significativamente os custos — em aproximadamente dez vezes — enquanto gerencia a latência de forma eficaz. Os tempos típicos de acesso ficam em torno de 100 milissegundos, mas podem se estender a alguns segundos para dados acessados com menor frequência ("mais frios"), tornando-a viável para casos de uso offline e aplicações sensíveis a custos.
Depois de selecionar um índice, você pode avaliar seu desempenho com base em seu tempo de construção, precisão, desempenho e consumo de recursos. Por exemplo, um índice não otimizado pode suportar apenas 20 consultas por segundo sem nenhuma construção necessária. Otimizar um índice poderia melhorar significativamente o QPS, potencialmente aumentando-o em dez vezes a cada iteração de ajuste, embora ao custo de maior tempo de visualização.
Para selecionar e ajustar seu índice de forma eficaz, você deve:
Escolher o tipo de índice apropriado com base em suas necessidades específicas.
Ajustar os parâmetros do índice para otimizar o desempenho.
Fazer benchmark dos seus casos de uso para garantir que o índice funcione conforme esperado.
Ajustar os parâmetros de busca para melhorar ainda mais o desempenho.
Se você não tiver certeza sobre o processo de otimização, aproveite o poder de ferramentas de benchmark como VectorDBBench. Esta ferramenta, desenvolvida e disponibilizada como código aberto pela Zilliz, pode avaliar todos os principais bancos de dados vetoriais. Ela permite que você conduza experimentos abrangentes e ajuste seu sistema para obter desempenho ideal.
Para uma referência rápida, preparamos uma folha de consulta prática que descreve o desempenho de cada índice em nosso catálogo de índices de GPU. Este recurso pode ajudar você a otimizar o desempenho e a eficiência de custos, orientando você para a melhor correspondência de índice para as necessidades da sua aplicação.
Uma folha de consulta de índices
Resumo
Neste guia abrangente, exploramos o mundo multifacetado dos bancos de dados vetoriais e as abordagens práticas necessárias para maximizar sua eficiência e escalabilidade. Desde os fundamentos de projetar um esquema até as complexidades de gerenciar grandes conjuntos de dados, cobrimos as estratégias essenciais e as melhores práticas que os desenvolvedores precisam conhecer ao trabalhar com bancos de dados vetoriais.
À medida que os bancos de dados vetoriais evoluem, manter-se informado sobre esses aspectos capacitará os desenvolvedores a criar aplicações mais robustas, eficientes e escaláveis. Seja você um profissional de banco de dados experiente ou um recém-chegado a esta área, os insights fornecidos aqui ajudarão você a navegar pelas complexidades dos bancos de dados vetoriais com maior confiança e competência.
Continue lendo
Stop Building AI Data Infra for the Wrong Stage
Learn how AI data infrastructure should evolve from prototype to enterprise scale, and when Vector Lakebase becomes the right architecture for AI apps.

From Vector Database to Vector Lakebase
Zilliz offers a fully managed Vector Lakebase powered by Milvus, unifying real-time vector search, lake-scale discovery, and Al data operations.

Selecting the Right ETL Tools for Unstructured Data to Prepare for AI
Learn the right ETL tools for unstructured data to power AI. Explore key challenges, tool comparisons, and integrations with Milvus for vector search.



