Performance, antipadrões e decisão em produção
Checklist de arquitetura de busca
Decisão: FTS no banco vs motor dedicado; critérios de volume, relevância, equipe e custo.
Nesta aula você vai
- Aplicar uma checklist objetiva FTS-no-banco versus motor dedicado
- Pesar volume, relevância, equipe, custo e requisitos de atualidade dos dados
- Registrar a decisão de arquitetura com critérios verificáveis
Checklist de arquitetura de busca
Objetivos
Nesta aula você vai:
- Aplicar uma checklist objetiva FTS-no-banco versus motor dedicado
- Pesar volume, relevância, equipe, custo e requisitos de atualidade dos dados
- Registrar a decisão de arquitetura com critérios verificáveis
Introdução
A pergunta certa não é “qual o melhor motor de busca?”, e sim “qual capacidade mínima resolve o produto agora, com a equipe e o orçamento que temos?”. Esta aula consolida o módulo em uma checklist reutilizável de arquitetura.
Conteúdo
Passo 1 — Clarificar o job da busca
Responda por escrito:
- A busca é auxiliar (achar um registro) ou produto (descoberta, facetas, personalização)?
- Precisa de typo-tolerance, sinônimos, multilíngue?
- Precisa de semântica (paráfrase) ou lexical basta?
- Qual o SLA de atualidade após uma publicação?
- Multi-tenant? Isolamento por tenant na query é obrigatório?
Sem essas respostas, qualquer diagrama é especulação.
Passo 2 — Dimensões de decisão
| Dimensão | FTS no banco (Postgres/MariaDB/SQL Server/SQLite D1/Mongo text) | Motor dedicado (OpenSearch, Meili, Typesense, Atlas Search) |
|---|---|---|
| Volume | Até dezenas de milhões com query seletiva, conforme motor | Centenas de milhões+, QPS alto de busca |
| Relevância | BM25/textScore “bom o bastante” | Analyzers, synonyms, ranking functions, A/B |
| Facetas | Limitadas / SQL aggregations | Nativas e baratas relativamente |
| Equipe | DBA/dev já no SGBD | Precisa de responsabilidade de search/platform |
| Custo | Marginal no DB existente | Cluster/RAM/managed + sync |
| Atualidade | Transacional ou triggers | Eventual (segundos+) típico |
| Edge | D1 FTS5 excelente encaixe | Motor central + Worker cliente |
Passo 3 — Checklist binária (FTS no banco)
Marque “sim” só com evidência:
- Conjunto de dados e QPS cabem no orçamento medido (P95 OK no teste de carga realista)
- Relevância lexical aprovada por product/UX em amostra de queries
- Não há requisito forte de facetas complexas ou fuzzy agressivo
- Sync resolvido (mesma transação, triggers ou text index nativo)
- Plano de escape documentado (quando sair do banco)
Se três ou mais itens falham, avalie motor dedicado.
Passo 4 — Checklist binária (motor dedicado)
- Há SoR claro (DB) e indexer idempotente
- Lag monitorado e alertado
- Estratégia de reindex (alias/zero-downtime)
- Segurança: ACL/tenant filters em toda query
- Custo de RAM/replicas projetado para 12–18 meses
- Roteiro de incidente (cluster vermelho, quebra de mapping)
Passo 5 — Híbridos comuns
1) D1/Postgres FTS → API
2) FTS top-K → re-rank vetorial (Vectorize)
3) OpenSearch lexical + vector field
4) Autocomplete no Meili/Typesense + detalhe no OLTP
Híbrido aumenta poder e complexidade: só adote se a checklist de relevância exigir.
Passo 6 — Registrar a ADR
Documente em uma ADR curta:
- Contexto e drivers (volume, UX, equipe)
- Decisão (ex.: “FTS5 no D1 com triggers; reavaliar aos 5 M docs”)
- Consequências (limites conhecidos, métricas de gatilho)
- Gatilhos de revisão (P95, tamanho do DB, novas funcionalidades de faceta)
## ADR: Busca lexical no D1
- Status: aceita
- Decisão: articles_fts (fts5) + triggers; autocomplete prefix; sem OpenSearch no MVP
- Gatilho de revisão: P95 > 300 ms por 7 dias OU facetas no roadmap
Fluxo mental final
Job da busca → métricas atuais → checklist FTS banco
↘ falhou → checklist motor dedicado
↘ semântica necessária → vetores ± lexical
→ ADR + gatilhos de revisão
Exemplos práticos
// Scorecard simples para workshop de arquitetura
function scorecard(answers) {
const weights = {
volumeHigh: 2,
facetsNeeded: 2,
typoSynonyms: 2,
teamHasSearchOncall: 1,
edgeFirst: -1, // favorece D1/FTS embutido
semanticNeeded: 2,
};
let score = 0; // >3 sugere motor dedicado
for (const [k, w] of Object.entries(weights)) {
if (answers[k]) score += w;
}
return {
score,
recommendation:
score >= 3 ? 'motor-dedicado-ou-hibrido' : 'fts-no-banco',
};
}
-- Evidência mínima antes de decidir "cabe no D1"
-- Rodar sob carga com EXPLAIN QUERY PLAN + latência no Worker
SELECT count(*) AS approx
FROM articles_fts
WHERE articles_fts MATCH 'produto*';
Problemas e como resolver
| Problema | Causa | Mitigação |
|---|---|---|
| Decisão por moda | “Todo mundo usa ES” | Lista de verificação + teste de carga |
| Lock-in sem escape | Sem ADR/gatilhos | Documentar revisão |
| Dois SoR | Índice editável na UI de busca | SoR no OLTP apenas |
| Custo surpresa | Sem projeção de RAM/QPS | Capacity plan 12–18 meses |
Resumo
Use a checklist: esclareça o job, meça, prefira FTS no banco quando couber, escale para motor dedicado quando relevância/volume/facetas exigirem, e registre ADR com gatilhos. Assim a busca permanece uma decisão de engenharia — não um salto de fé.