Do sequential scan ao índice vetorial
Uma consulta por vizinhos mais próximos sem índice é trabalho honesto. O Postgres calcula a distância do seu vetor de consulta até cada linha, ordena e devolve as dez primeiras. É exato e, com dez mil linhas, leva mais ou menos o tempo de um piscar de olhos.
Com dois milhões de linhas leva segundos, e o seu endpoint está quebrado.
O curso anterior, Arquitetura RAG de IA, tratou o vector store como uma caixa com um botão de recall. Este curso abre a caixa. No fim você vai conseguir escolher um tipo de índice, definir os parâmetros dele a partir do formato dos seus dados, medir quanto essa escolha custou em recall e explicar por que uma consulta vetorial filtrada se comportou de forma estranha.
Meça antes de ajustar
Toda decisão de tuning aqui não vale nada sem um número de comparação. O Postgres te dá isso de graça:
EXPLAIN ANALYZE
SELECT id FROM rag_chunks
ORDER BY embedding <=> '[0.1, 0.2, ...]'
LIMIT 10;
Limit (cost=..) (actual time=412.883..412.891 rows=10 loops=1)
-> Sort (actual time=412.881..412.885 rows=10 loops=1)
Sort Method: top-N heapsort Memory: 27kB
-> Seq Scan on rag_chunks (actual time=0.019..385.402
rows=2000000 loops=1)
Planning Time: 0.104 ms
Execution Time: 412.951 ms
Três coisas nessa saída importam, e você vai lê-las de novo depois de cada mudança neste curso. Seq Scan significa que nenhum índice foi usado. rows=2000000 é quantos cálculos de distância você pagou. Execution Time é o número que os seus usuários sentem.
Repare também no que um sequential scan faz bem: ele é exato. Ele é a verdade de referência contra a qual você vai medir todo índice aproximado na aula 7, e esse é um papel útil muito depois de você parar de servir tráfego com ele.
O que um índice muda, e o que custa
Um índice sobre valores escalares (um B-tree em created_at) é um atalho para uma resposta que continua exata. Índices vetoriais não são assim.
Não existe jeito de ordenar pontos de alta dimensão numa linha de forma que pontos próximos continuem próximos. Isso vem da geometria do espaço, e nenhuma implementação contorna. Então índices vetoriais abrem mão da exatidão e organizam os dados de forma que a maioria dos vizinhos próximos possa ser encontrada sem visitar tudo. O resultado é busca por vizinhos aproximados, e aquilo de que você abriu mão tem nome e número:
recall@10 = (quantos dos 10 verdadeiros o índice devolveu) / 10
Um recall@10 de 0,95 significa que, em média, nove e meia das dez linhas que voltaram estão entre as dez mais próximas, e o resto são quase-acertos: normalmente a décima primeira e a décima segunda mais próximas, não linhas aleatórias.
flowchart LR
S["Sequential scan"] --> S1["recall 1,00"]
S --> S2["O(n) — segundos com 2M de linhas"]
A["Índice ANN"] --> A1["recall 0,90-0,99, ajustável"]
A --> A2["sublinear — milissegundos"]
Recall é um botão, não uma propriedade fixa do índice. Todo tipo de índice deste curso expõe um parâmetro de tempo de consulta que troca latência por recall. Construir o índice é a decisão; girar esse botão é o trabalho do dia a dia.
Três custos, não um
Times avaliam índices vetoriais pela velocidade de consulta e se surpreendem com os outros dois.
Latência de consulta é o que você foi comprar.
Tempo de construção é o que você paga quando o índice é criado e, de forma mais dolorosa, quando ele é reconstruído. Um índice HNSW sobre alguns milhões de linhas pode levar horas. Se o seu processo de deploy reconstrói índices, isso agora é um problema de deploy.
Memória decide se qualquer coisa acima se sustenta. O desempenho do índice despenca quando a estrutura não cabe mais na memória, porque uma travessia de grafo que seguia ponteiros na RAM passa a emitir leituras aleatórias em disco. Um índice vetorial costuma ser maior que a tabela que ele indexa.
2.000.000 linhas × 1024 dimensões × 4 bytes = 8,2 GB de vetores crus
Índice HNSW em cima disso = mais ou menos 2-4 GB
Faça essas multiplicações antes de escolher dimensões, não depois. Essa é a aritmética por trás do trabalho de quantização da aula 6.
O que o pgvector te dá
O pgvector (0.8.6 na data em que isto foi escrito) traz dois tipos de índice e quatro tipos de vetor. O mapa para o resto do curso:
| IVFFlat | HNSW | |
|---|---|---|
| Estrutura | Vetores particionados em listas | Grafo de proximidade em camadas |
| Tempo de construção | Rápido | Lento |
| Velocidade / recall | Bom | Melhor |
| Memória | Menor | Maior |
| Precisa de dados existentes | Sim: ele agrupa o que está lá | Não |
| Botão de consulta | probes |
ef_search |
A linha "Precisa de dados existentes" é uma armadilha operacional: um índice IVFFlat construído sobre uma tabela vazia ou minúscula produz clusters ruins, e ele não se conserta conforme as linhas chegam. A aula 3 cobre o que fazer a respeito.
O curso
- Do sequential scan ao índice vetorial: você está aqui.
- Métricas de distância e classes de operadores: acertando o par.
- IVFFlat: listas, probes e quando agrupar já basta.
- HNSW:
m,ef_construction,ef_searche o que o grafo custa. - Filtragem: onde a busca aproximada fica difícil.
- Quantização:
halfvec, vetores binários e rescoring. - Medir recall e colocar em produção.
A seguir: a escolha que desativa o seu índice em silêncio se você errar.
Continue com a aula 2
Métricas de distância e classes de operadores
As diferenças entre cosseno, L2 e produto interno, e por que uma classe de operadores errada faz o índice ser ignorado.
Com uma conta grátis você também tem os quizzes, seu progresso e suas anotações, e a discussão.