Inteligência artificial, sem ruído.
Infraestrutura9 min

Como otimizar o Milvus com Force Merge para dobrar a velocidade de busca vetorial

Aprenda a usar o Force Merge no Milvus para eliminar a fragmentação de segmentos e duplicar a velocidade das suas buscas vetoriais em escala.

Como otimizar o Milvus com Force Merge para dobrar a velocidade de busca vetorial

À medida que os sistemas de busca semântica, recomendação e sistemas de Retrieval-Augmented Generation (RAG) escalam para centenas de milhões de vetores, a latência das consultas torna-se o principal gargalo de infraestrutura. No coração do Milvus, um dos bancos de dados vetoriais distribuídos mais populares do mundo, reside uma arquitetura baseada em log-structured merge-tree (LSM) adaptada para vetores. Embora essa arquitetura ofereça excelente desempenho de gravação, ela introduz um desafio crítico: a fragmentação de segmentos.

Quando dados são ingeridos continuamente ou em lotes frequentes, o Milvus cria múltiplos pequenos segmentos de dados. Cada segmento funciona como um subbanco de dados independente, com seu próprio índice de busca (como HNSW ou IVF-FLAT). Durante uma busca vetorial (ANNS – Approximate Nearest Neighbor Search), o Milvus é obrigado a consultar cada um desses segmentos individualmente e, em seguida, mesclar os resultados. Esse processo gera um overhead massivo de CPU, latência de rede interna e contenção de cache. Este artigo técnico de nível avançado demonstra como utilizar a API de compactação (compact()) com o parâmetro target_size para consolidar segmentos fragmentados, reduzindo drasticamente a latência das queries e dobrando a velocidade de busca.

A Arquitetura de Segmentos do Milvus e o Custo da Fragmentação

Para compreender por que a compactação é tão eficaz, é necessário entender a jornada de um vetor dentro do Milvus. O ciclo de vida dos dados é dividido em duas categorias principais de segmentos:

  • Growing Segments (Segmentos em Crescimento): Segmentos temporários alocados na memória (QueryNode/DataNode) que recebem os dados inseridos em tempo real. Eles utilizam busca por força bruta (brute-force) ou índices temporários simples enquanto novos vetores são adicionados.
  • Sealed Segments (Segmentos Selados): Quando um segmento em crescimento atinge o limite de tamanho configurado (geralmente 512 MB por padrão, controlado pela variável dataNode.segment.maxSize no arquivo de configuração do Milvus) ou quando o método flush() é explicitamente chamado, o segmento é fechado, persistido no Object Storage (como S3 ou MinIO) e torna-se imutável. É neste momento que o IndexNode entra em ação para construir o índice vetorial definitivo.

O Impacto Matemático e Computacional de Múltiplos Segmentos

Seja um conjunto de dados de 10 milhões de vetores de 1536 dimensões. Se esse conjunto estiver distribuído em 50 pequenos segmentos de 200.000 vetores cada, em vez de 5 segmentos consolidados de 2 milhões de vetores, o impacto na performance de busca é devastador. A complexidade de busca em um grafo HNSW (Hierarchical Navigable Small World) é aproximadamente logarítmica em relação ao número de nós, representada por $O(log N)$. No entanto, ao buscar em múltiplos segmentos, a complexidade total torna-se:

Complexidade Total = K * O(log (N / K)) + O(K * topK * log(K * topK))

Onde K é o número de segmentos e N é o número total de vetores. O termo adicional à direita representa o overhead computacional necessário para mesclar os resultados locais de cada segmento (merge sorting) para extrair o top-K global. Além disso, a execução de buscas paralelas em múltiplos pequenos grafos HNSW destrói a localidade de cache L1/L2/L3 da CPU, resultando em constantes cache misses e desperdício de ciclos de clock.

A Solução: Compactação e Force Merge com target_size

A compactação no Milvus é o processo de mesclar vários segmentos selados pequenos em segmentos maiores e remover dados marcados para exclusão (tombstones). O Milvus executa uma compactação automática em segundo plano, mas essa rotina padrão é conservadora e otimizada para evitar picos de consumo de recursos durante a ingestão ativa. Para cenários de produção de alta performance, a compactação manual via API de Force Merge é indispensável.

Como funciona o parâmetro target_size

Introduzido para fornecer controle granular sobre o layout físico dos dados, o parâmetro target_size (definido em megabytes) instrui o gerenciador de dados do Milvus (DataCoord) a consolidar segmentos menores até que o segmento resultante atinja o tamanho ideal especificado. O tamanho ideal recomendado para segmentos selados varia entre 512 MB e 2048 MB (2 GB), dependendo do hardware disponível e do tipo de índice utilizado.

Ao consolidar os dados em segmentos de tamanho ideal, reduzimos drasticamente o valor de K na nossa equação de complexidade, permitindo que o algoritmo de busca explore caminhos mais profundos e otimizados dentro de um único grafo de alta densidade, em vez de saltar entre dezenas de grafos isolados.

Implementação Prática: Executando Compaction e Force Merge via PyMilvus

Abaixo, apresentamos um guia passo a passo e um script em Python utilizando a biblioteca pymilvus para demonstrar como acionar a compactação de forma programática e monitorar seu progresso até a consolidação total.

Passo 1: Conectando e Analisando o Estado Atual dos Segmentos

Antes de iniciar a compactação, é fundamental inspecionar a coleção para identificar o nível de fragmentação. O trecho de código abaixo conecta-se ao cluster Milvus, carrega a coleção e exibe a quantidade e o tamanho dos segmentos existentes.

from pymilvus import connections, utility, Collection

# Conectar ao cluster Milvus
connections.connect("default", host="localhost", port="19530")

collection_name = "artigos_cientificos_v4"
collection = Collection(collection_name)

# Forçar o flush para garantir que todos os dados em memória sejam selados
collection.flush()

# Obter informações detalhadas sobre os segmentos
segment_details = utility.get_query_segment_info(collection_name)
print(f"Total de segmentos ativos: {len(segment_details)}")
for seg in segment_details:
    print(f"Segmento ID: {seg.segmentID}, State: {seg.state}, Rows: {seg.num_rows}")

Passo 2: Disparando a API de Compactação com target_size

Após validar a fragmentação, invocamos a API de compactação. Definiremos o target_size para 1024 MB (1 GB), que é o ponto ideal para equilibrar o uso de memória RAM do QueryNode e a velocidade de busca vetorial.

# Disparar o processo de compactação manual
# Nota: O Milvus consolidará segmentos menores em segmentos de aproximadamente 1GB
compaction_job_id = collection.compact(target_size=1024 * 1024 * 1024) # valor em bytes
print(f"Compaction iniciada com sucesso. Job ID: {compaction_job_id}")

Passo 3: Monitorando o Status da Compactação

A compactação é uma operação assíncrona e pesada, que envolve intensa leitura e gravação em disco/object storage. Devemos monitorar o progresso até que o estado seja alterado para concluído.

import time

while True:
    state = collection.get_compaction_state()
    print(f"Status da compactação: {state.state.name} | Progresso: {state.completed_plans}/{state.total_plans} planos concluídos")
    if state.state.name in ["Completed", "Failed"]:
        break
    time.sleep(5)

if state.state.name == "Completed":
    print("Processo de Force Merge concluído com sucesso!")
else:
    print("Falha ao executar a compactação dos segmentos.")

Estratégia Avançada: Reconstrução de Índices Pós-Compactação

Um erro comum cometido por administradores de bancos de dados vetoriais é assumir que a compactação otimiza a busca imediatamente. Quando segmentos são mesclados, o Milvus descarta os índices antigos desses segmentos fragmentados, pois a estrutura topológica dos grafos antigos não corresponde mais ao novo segmento consolidado. Se você realizar buscas logo após a compactação, o Milvus executará buscas por força bruta nos novos segmentos, resultando em uma degradação temporária severa de performance.

Para evitar esse cenário em produção, siga este fluxo de trabalho rigoroso:

  1. Executar a Compactação: Mesclar os segmentos conforme demonstrado no código anterior.
  2. Monitorar a Conclusão: Aguardar até que todos os planos de compactação estejam finalizados.
  3. Reconstruir o Índice Vetorial: Invocar a criação de índice explicitamente. O Milvus detectará o novo segmento consolidado e criará um único grafo HNSW robusto.
  4. Carregar a Coleção na Memória (Load): Liberar a versão antiga da coleção da memória RAM e carregar a nova versão indexada.

Veja como implementar essa sequência de forma segura:

# Reconstruir o índice HNSW no segmento consolidado
index_params = {
    "metric_type": "COSINE",
    "index_type": "HNSW",
    "params": {"M": 16, "efConstruction": 200}
}

print("Iniciando a reconstrução do índice pós-compaction...")
collection.create_index(field_name="vector", index_params=index_params, overwrite=True)

# Liberar e recarregar a coleção para garantir que os QueryNodes apontem para os novos segmentos indexados
collection.release()
collection.load()
print("Coleção atualizada e carregada com sucesso na memória RAM!")

Análise de Casos de Borda e Mitigação de Riscos

Embora a compactação manual traga ganhos expressivos, ela consome recursos computacionais significativos e exige planejamento operacional. Abaixo, detalhamos os principais casos de borda e como mitigá-los.

1. Overhead de Memória RAM nos QueryNodes

Durante o processo de compactação, o Milvus mantém uma arquitetura de controle de concorrência baseada em MVCC (Multi-Version Concurrency Control). Isso significa que os segmentos antigos (fragmentados) continuam servindo consultas em tempo real na memória RAM enquanto o novo segmento consolidado está sendo escrito e indexado. Como resultado, o consumo de memória RAM dos QueryNodes pode dobrar temporariamente durante a operação. Certifique-se de que seus nós tenham pelo menos 60% de margem de memória livre antes de iniciar o processo.

2. O Dilema dos Vetores Deletados (Tombstones)

Em sistemas dinâmicos onde atualizações e exclusões de vetores são frequentes, o Milvus apenas marca os vetores como deletados na memória (soft delete) usando filtros de Bloom. Esses vetores deletados continuam ocupando espaço físico no disco e degradando a performance de busca, pois o algoritmo ANNS ainda precisa percorrê-los para depois descartá-los do resultado final. A execução do compact() limpa fisicamente esses registros (hard delete). Se a sua taxa de atualização de dados for superior a 15%, agendar compatações semanais é vital para manter as SLAs de latência sob controle.

O Futuro do Gerenciamento de Segmentos no Milvus

A evolução do Milvus aponta para uma automação cada vez maior do ciclo de vida dos dados. Nas próximas versões, espera-se que algoritmos de aprendizado de máquina analisem em tempo real o padrão de queries e tomem decisões autônomas de compactação baseadas não apenas no tamanho físico do segmento (target_size), mas também na entropia dos grafos vetoriais e na frequência de acesso de determinadas partições (hot/cold data tiering).

Além disso, o desenvolvimento de técnicas de indexação dinâmica permitirá que segmentos sejam mesclados e incrementalmente atualizados sem a necessidade de descartar e reconstruir o índice do zero, reduzindo o custo computacional de manutenção em até 70%. Até que essas tecnologias estejam maduras, o controle explícito sobre a compactação física dos dados continua sendo a ferramenta mais poderosa no arsenal de engenheiros de dados e especialistas em IA para extrair a máxima performance de sistemas de busca vetorial em larga escala.


Descubra mais sobre noticiAI

Assine para receber nossas notícias mais recentes por e-mail.

R
Sobre o autorRedação noticiAI

Equipe editorial do Noticiai, dedicada a explicar inteligência artificial com clareza e contexto.