Arquitetura do Serviço de Busca: Aplicação Flask Independente com Valkey
Este artigo explica como nosso serviço de busca funciona como uma aplicação Flask independente em um servidor separado, usando Valkey (um fork do Redis) para busca vetorial de alto desempenho, cache e autocompletar.
O Problema: Desempenho e Escalabilidade da Busca
As operações de busca são computacionalmente caras:
-
Extração de filtros: Corresponder mais de 2.500 frases contra cada consulta
-
Buscas relacionadas: Calcular similaridade entre 65K consultas
-
Autocompletar: Correspondência por prefixo em 65K consultas
-
Filtragem de produtos: Filtrar mais de 5K produtos por múltiplos critérios
Executar essas operações no servidor web principal causa:
-
Carregamento lento de páginas: A busca bloqueia outras requisições
-
Pressão de memória: Os embeddings representam uma pegada de memória massiva
-
Contenção de CPU: O cálculo de similaridade é intensivo em CPU
-
Dificuldade de escalonamento: Não é possível escalonar a busca independentemente
Precisamos de um serviço de busca dedicado que possa escalar de forma independente.
A Solução: Serviço de Busca Independente
Executamos uma aplicação Flask separada em um servidor dedicado:
Servidor Web Principal
↓ Chamadas HTTP API
Serviço de Busca
↓ Consultas Valkey
Servidor Valkey
Esta arquitetura fornece:
-
Escalonamento independente: Escalonar o serviço de busca sem afetar o servidor web principal
-
Isolamento de recursos: Operações de busca não impactam o servidor web principal
-
Cache: O Valkey armazena resultados em cache para consultas repetidas rápidas
-
Alta disponibilidade: O serviço de busca pode reiniciar sem afetar o servidor web principal
Componentes do Serviço de Busca
1. API de Extração de Filtros
-
Endpoint:
/api/extract_filters -
Propósito: Extrair filtros estruturados de consultas em linguagem natural
-
Exemplo:
GET /api/extract_filters?q=mini+pc+16gb+ram
Resposta:
{
"Form Factor": "Mini PC",
"Main Memory": "16"
}
Implementação:
-
Carregar mapeamentos de frase para filtro do Valkey ou JSON
-
Corresponder frases usando regex de limite de palavra
-
Retornar filtros estruturados
Cache: Mapeamentos de frase armazenados em cache no Valkey (TTL de 30 dias)
2. API de Buscas Relacionadas
-
Endpoint:
/api/related -
Propósito: Encontrar consultas semanticamente similares usando busca vetorial
-
Exemplo:
POST /api/related
{
"query": "mini pc",
"limit": 10
}
Resposta:
{
"related": [
{"query": "small computer", "similarity": 0.92},
{"query": "compact desktop", "similarity": 0.89},
{"query": "mini pc 8gb", "similarity": 0.87}
]
}
Implementação:
-
Embedar a consulta usando all-mpnet-base-v2
-
Consultar o RediSearch do Valkey para vizinhos mais próximos
-
Retornar os N principais resultados ordenados por similaridade
Cache: Resultados armazenados em cache no Valkey (TTL de 7 dias)
3. API de Autocompletar
-
Endpoint:
/api/autocomplete -
Propósito: Sugerir consultas conforme o usuário digita
-
Exemplo:
GET /api/autocomplete?q=mini+p&limit=5
Resposta:
{
"suggestions": [
"mini pc",
"mini pc 16gb",
"mini pc 8gb ram",
"mini pc fanless",
"mini pc windows 11"
]
}
Implementação:
-
Consultar o RediSearch do Valkey com correspondência por prefixo
-
Classificar por popularidade (pontuação de impressão + clique)
-
Retornar as N principais sugestões
Cache: Índice de autocompletar no Valkey (atualizado diariamente)
4. API de Consultas Populares
-
Endpoint:
/api/popular -
Propósito: Obter as consultas mais populares
-
Exemplo:
GET /api/popular?limit=10
Resposta:
{
"queries": [
"mini pc",
"thin client",
"industrial pc",
"all in one pc"
]
}
Implementação:
-
Carregar consultas do Valkey ou JSON
-
Ordenar por pontuação de tráfego (impressões + cliques)
-
Retornar as N principais consultas
Cache: Consultas populares armazenadas em cache no Valkey (TTL de 30 dias)
Integração com Valkey
Valkey é um fork do Redis que fornece:
-
Busca vetorial: Módulo RediSearch para busca por similaridade
-
Cache: Armazenamento chave-valor rápido em memória
-
Autocompletar: Correspondência por prefixo com conjuntos ordenados
-
Persistência: AOF (Arquivo Somente-Acrescentar) para durabilidade
Busca Vetorial com RediSearch
Usamos o módulo RediSearch do Valkey para busca por similaridade vetorial:
Criação do Índice:
client.ft("queries_idx").create_index([
VectorField("embedding", "FLAT", {
"TYPE": "FLOAT32",
"DIM": 768,
"DISTANCE_METRIC": "COSINE"
}),
TextField("query"),
NumericField("score")
])
Busca Vetorial:
query_embedding = model.encode(query)
results = client.ft("queries_idx").search(
Query("*=>[KNN 10 @embedding $vec AS score]")
.sort_by("score")
.return_fields("query", "score")
.dialect(2),
query_params={"vec": query_embedding.tobytes()}
)
Isso retorna os 10 vizinhos mais próximos por similaridade de cosseno.
Estratégia de Cache
Armazenamos vários tipos de dados em cache no Valkey:
Mapeamentos de Frase (TTL de 30 dias):
client.setex(
"seo:phrase_mappings",
30 * 24 * 3600,
json.dumps(phrase_mappings)
)
Buscas Relacionadas (TTL de 7 dias):
cache_key = f"related:{query_hash}"
client.setex(cache_key, 7 * 24 * 3600, json.dumps(results))
Consultas Populares (TTL de 30 dias):
client.setex(
"seo:popular_queries",
30 * 24 * 3600,
json.dumps(popular_queries)
)
Índice de Autocompletar (atualizado diariamente):
for query, score in queries:
client.zadd("autocomplete:mini", {query: score})
Autocompletar com Conjuntos Ordenados
Usamos conjuntos ordenados do Valkey para autocompletar:
Estrutura do Índice:
autocomplete:m → ["mini pc": 5000, "mini computer": 3000]
autocomplete:mi → ["mini pc": 5000, "mini computer": 3000]
autocomplete:min → ["mini pc": 5000, "mini computer": 3000]
autocomplete:mini → ["mini pc": 5000, "mini computer": 3000]
Consulta por Prefixo:
prefix = "mini"
results = client.zrevrange(f"autocomplete:{prefix}", 0, 9, withscores=True)
Isso retorna as 10 principais consultas começando com "mini", ordenadas por pontuação.
Comunicação da API
O servidor web principal chama o serviço de busca via HTTP:
Extração de Filtros
from app.shared.filter_service import extract_filters_from_query
filters = extract_filters_from_query("mini pc 16gb ram")
# Internamente chama: GET SEARCH_SERVICE_URL/api/extract_filters?q=...
Buscas Relacionadas
import requests
response = requests.post(
"SEARCH_SERVICE_URL/api/related",
json={"query": "mini pc", "limit": 10},
timeout=2
)
related = response.json()["related"]
Autocompletar
response = requests.get(
"SEARCH_SERVICE_URL/api/autocomplete",
params={"q": "mini p", "limit": 5},
timeout=1
)
suggestions = response.json()["suggestions"]
Tratamento de Erros e Fallbacks
O servidor web principal lida com falhas do serviço de busca de forma elegante:
try:
filters = extract_filters_from_query(query)
except Exception as e:
logger.error(f"Search service failed: {e}")
filters = {} # Fallback para filtros vazios
Isso garante que o servidor web principal continue funcionando mesmo se o serviço de busca estiver inativo.
### Configuração de Rede
Todos os servidores estão em uma rede privada:
- **Servidor web principal**: Pode acessar o serviço de busca e o Valkey
- **Serviço de busca**: Pode acessar o Valkey
- **Valkey**: Acessível apenas pelo servidor web principal e pelo serviço de busca
Nenhum acesso externo ao serviço de busca ou ao Valkey.
## Integração com o Pipeline de SEO
O serviço de busca integra-se com o pipeline de SEO:
### Passo 11: Migrar para Valkey
O pipeline de SEO carrega dados no Valkey:
```python
# Carregar embeddings de consulta
for query, embedding in zip(queries, embeddings):
client.hset(f"query:{query_hash}", mapping={
"query": query,
"embedding": embedding.tobytes(),
"score": score
})
# Criar índice RediSearch
client.ft("queries_idx").create_index([...])
Veja Migração para Valkey para detalhes.
Registro de Consultas
O serviço de busca registra consultas para o pipeline de SEO:
log_entry = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"query": query,
"filters_extracted": filters,
"results_count": len(results)
}
with open(SEO_LIVE_QUERIES_LOG, "a") as f:
f.write(json.dumps(log_entry) + "\n")
Esses registros alimentam de volta o Passo 1d: Buscar Consultas em Tempo Real.
Referências
Conceitos Técnicos
-
Valkey - Site oficial
-
Redis - Site oficial (fork do Valkey)
-
RediSearch - Módulo de busca vetorial
-
Similaridade de Cosseno - Wikipedia
Artigos Relacionados
-
Extração de Filtros - Como os filtros são extraídos
-
Geração de Buscas Relacionadas - Como as buscas relacionadas são geradas
-
Estratégia de Embedding - Como os embeddings são gerados
-
Visão Geral do Pipeline de SEO - Arquitetura completa do pipeline
-
Migração para Valkey - Carregando dados no Valkey
Resumo
Nosso serviço de busca funciona como uma aplicação Flask independente em um servidor separado:
Arquitetura:
-
Aplicação Flask independente em servidor dedicado
-
Valkey (fork do Redis) para cache e busca vetorial
-
API HTTP para comunicação com o servidor web principal
APIs:
-
/api/extract_filters- Extrair filtros de consultas -
/api/related- Encontrar consultas similares (busca vetorial) -
/api/autocomplete- Sugerir consultas (correspondência por prefixo) -
/api/popular- Obter consultas populares
Recursos do Valkey:
-
Busca vetorial (módulo RediSearch)
-
Cache (TTL de 30 dias para mapeamentos de frase)
-
Autocompletar (conjuntos ordenados)
-
Persistência (AOF)
Benefícios:
-
Escalonamento independente
-
Isolamento de recursos
-
Alto desempenho (cache do Valkey)
-
Tolerância a falhas (degradação elegante)
Esta arquitetura permite busca rápida e escalável enquanto mantém o servidor web principal responsivo.