Performance, antipadrões e decisão em produção
Antipadrões e mitigações
LIKE leading wildcard, FTS sem índice, sync atrasado, queries muito amplas e como corrigir.
Nesta aula você vai
- Identificar antipadrões clássicos de busca textual em produção
- Aplicar mitigações concretas (índices, sanitize, sync, limites)
- Priorizar correções pelo impacto em P95 e custo operacional
Antipadrões e mitigações
Objetivos
Nesta aula você vai:
- Identificar antipadrões clássicos de busca textual em produção
- Aplicar mitigações concretas (índices, sanitize, sync, limites)
- Priorizar correções pelo impacto em P95 e custo operacional
Introdução
A maioria das crises de busca não vem de “falta de Elasticsearch”, e sim de padrões que escalam mal: LIKE '%x%', FTS declarado mas não usado, índice dessincronizado, ou queries que batem em meio corpus. Esta aula cataloga os vilões e o remédio.
Conteúdo
1. LIKE com leading wildcard
-- Antipadrão
SELECT * FROM articles WHERE body LIKE '%cloudflare%';
Isso impede uso eficiente de B-tree e vira scan. Mitigações:
- FTS5 / FULLTEXT /
tsvector/ text index. - Trigram (
pg_trgm, FTS5 trigram) quando substring é requisito real. - Normalizar e buscar por tokens prefix (
cloud*) quando UX permitir.
2. “Temos FTS” mas a app ainda usa LIKE
Índice existe; código legado não. Mitigação: sinalizador de funcionalidade (feature flag), buscar chamadas LIKE '%', migrar endpoints um a um, apagar o caminho lento.
3. External content sem triggers / sync atrasado
No D1/SQLite com content='articles', sem triggers o índice mente. Em Cassandra→OpenSearch, lag não monitorado entrega resultados fantasmas.
Mitigações:
- Triggers INSERT/UPDATE/DELETE (FTS5).
- CDC/outbox + métrica de lag.
- Job de reconciliação periódica (diff ids).
4. Queries excessivamente amplas
-- Antipadrão: OR de mil termos / prefixo de 1 letra
MATCH 'a*'
Mitigações:
- Mínimo de 2–3 caracteres no autocomplete.
- Limite de tokens (ex.: 8–10).
ANDdefault em vez deORamplo.LIMITbaixo; timeout no Worker.- Stopwords e rejeição de queries vazias.
5. Concatenar input cru na sintaxe FTS
Usuário digita " ou AND e quebra a query ou amplia demais. Mitigação: whitelist de tokens, aspas em cada termo, escape conforme o motor.
6. OFFSET profundo
LIMIT 20 OFFSET 100000;
Mitigações: cap de páginas, keyset, “mostrar mais” com top-K, cursor opaco.
7. Reindex em horário de pico / tokenizer mudado sem rebuild
Mudar tokenizer sem rebuild deixa índice inconsistente com a expectativa. Mitigação: janela de manutenção, índice novo + alias swap (OpenSearch), ou rebuild FTS controlado.
8. Um cluster de busca para tudo sem isolamento
Logs + busca de produto no mesmo cluster se atropelam. Mitigação: índices/clusters separados; quotas; filas de indexação.
9. Tratar busca vetorial como substituto direto do FTS
Embeddings sem filtro lexical geram “sopa semântica” irrelevante. Mitigação: híbrido (FTS top-K + re-rank vetorial) e avaliação offline de qualidade.
Priorização
| Sintoma | Antipadrão provável | Prioridade |
|---|---|---|
| CPU alta em SELECT | LIKE %x% / query ampla | P0 |
| Resultados errados | Sync/triggers | P0 |
| P95 ruim só página 20+ | OFFSET | P1 |
| Erros de sintaxe FTS | Input cru | P1 |
| Relevância fraca | Só OR / sem pesos | P2 |
Exemplos práticos
// Antes: inseguro e amplo
const bad = `* FROM articles_fts WHERE articles_fts MATCH '${userInput}'`;
// Depois: tokens seguros + AND + limite
function safeMatch(userInput) {
const tokens = String(userInput)
.toLowerCase()
.normalize('NFD')
.replace(/\p{M}/gu, '')
.replace(/[^\p{L}\p{N}\s]/gu, ' ')
.trim()
.split(/\s+/)
.filter((t) => t.length >= 2)
.slice(0, 8);
if (!tokens.length) return null;
return tokens.map((t) => `"${t}"`).join(' AND ');
}
-- Mitigação D1: garantir sync
CREATE TRIGGER articles_ai AFTER INSERT ON articles BEGIN
INSERT INTO articles_fts(rowid, title, body)
VALUES (new.id, new.title, new.body);
END;
-- (+ UPDATE/DELETE como na aula de D1)
-- Substituir LIKE leading wildcard
-- Antes: WHERE title LIKE '%d1%'
-- Depois:
SELECT rowid, title FROM articles_fts
WHERE articles_fts MATCH 'title: d1*'
ORDER BY bm25(articles_fts)
LIMIT 20;
Problemas e como resolver
| Problema | Causa | Mitigação |
|---|---|---|
| Timeout intermitente | Query ampla + carga | Limites + cache + P95 alert |
| “Não acha o doc novo” | Lag / sem trigger | Sync + lag metric |
| XSS no highlight | HTML sem escape | Escape + marcadores controlados |
| Custo de cloud explode | Reindex contínuo mal feito | Batch, partial update, aliases |
Resumo
Elimine LIKE '%…%', proteja a sintaxe FTS, sincronize o índice, limite amplitude e profundidade de página. Com antipadrões sob controle, a checklist de arquitetura (próxima aula) decide FTS no banco versus motor dedicado com critérios objetivos.