Full-Text em outros bancos e ecossistema
Cassandra, Solr e OpenSearch
Por que Cassandra não tem FTS nativo rico e como integrações (DataStax Search/Solr, OpenSearch) resolvem.
Nesta aula você vai
- Explicar por que índices secundários no Cassandra não substituem FTS
- Descrever o padrão de integração com Solr (DataStax Search) ou OpenSearch
- Desenhar sincronização, consistência eventual e responsabilidade entre OLTP e busca
Cassandra, Solr e OpenSearch
Objetivos
Nesta aula você vai:
- Explicar por que índices secundários no Cassandra não substituem FTS
- Descrever o padrão de integração com Solr (DataStax Search) ou OpenSearch
- Desenhar sincronização, consistência eventual e responsabilidade entre OLTP e busca
Introdução
Apache Cassandra brilha em escritas distribuídas, alta disponibilidade e modelo de partição por chave. Full-text search rico não faz parte do núcleo. Índices secundários e SAI resolvem predicados pontuais — não ranking BM25, análise linguística ou facetas de catálogo.
A solução de mercado é separar responsabilidades: Cassandra como fonte da verdade (system of record); Solr ou OpenSearch como motor de busca.
Conteúdo
Limitações dos índices secundários
Índices secundários (e mesmo SAI em versões recentes) permitem filtrar por colunas que não são a partition key, com restrições:
- Cardinalidade e distribuição ruins geram fan-out caro entre nós.
- Não há tokenizer/stemming/relevância lexical de motor de busca.
LIKE/ matching parcial não equivalem a um índice invertido FTS.- Queries multi-coluna ad hoc fogem do modelo de acesso por partition key.
Regra prática: se a pergunta do usuário é “quais documentos falam sobre X com relevância Y?”, você está fora da faixa ideal do Cassandra sozinho.
DataStax Search (Solr)
Em distribuições DataStax, Search integra nós Solr ao cluster Cassandra:
- Documentos indexados a partir das tabelas CQL.
- Consultas Solr (Lucene) para texto, facetas e ranking.
- Co-localização operacional reduz latência de sync, mas aumenta complexidade do cluster.
Padrão mental:
App → CQL (escrita/leitura por chave)
App → Solr query (busca textual / facetas)
↑ indexação a partir das SSTables/tabelas
Avalie custo operacional: Search acopla ciclo de vida Cassandra + Solr.
OpenSearch / Elasticsearch ao lado
Padrão mais portátil em cloud:
- Escreve em Cassandra (fonte da verdade).
- Emite evento (CDC, Change Data Capture, outbox, ou mensagem na escrita).
- Indexer consome e upserta no OpenSearch.
- API de busca lê OpenSearch; API de detalhe pode voltar ao Cassandra pela chave.
[Writer] → Cassandra
→ Outbox/CDC → Indexer → OpenSearch
[Search API] → OpenSearch (ids + snippets)
[Get by id] → Cassandra
Vantagens: escala de busca independente, analyzers maduros, highlights, aggregations. Custo: consistência eventual entre escrita e busca.
Consistência e responsabilidade
| Decisão | Recomendação |
|---|---|
| Fonte da verdade | Cassandra (ou outro OLTP) |
| SLA de atualidade | Segundos a minutos; documente |
| Idempotência do indexer | Upsert por id + version/timestamp |
| Deletes | Tombstone/evento de delete explícito |
| Reindex | Job full rebuild + alias swap |
Nunca trate o índice de busca como fonte autoritativa para atualizações de negócio.
Quando “só Cassandra” ainda basta
- Acesso exclusivamente por partition/clustering key.
- Filtros secundários simples e baixa cardinalidade bem modelados.
- Busca textual não é requisito de produto (só exact match / prefixo controlado na app).
Se o roadmap inclui autocomplete preditivo, relevância e facetas, prever orçamento para Solr/OpenSearch cedo evita remodelar partições só para “forçar” busca.
Exemplos práticos
-- Modelagem OLTP (não é FTS)
CREATE TABLE articles (
tenant_id uuid,
article_id timeuuid,
title text,
body text,
status text,
PRIMARY KEY ((tenant_id), article_id)
) WITH CLUSTERING ORDER BY (article_id DESC);
// Indexer simplificado (pseudo)
async function onArticleUpsert(event) {
const { tenant_id, article_id, title, body, status, version } = event;
await openSearch.client.index({
index: 'articles',
id: `${tenant_id}:${article_id}`,
version,
version_type: 'external_gte',
document: { tenant_id, article_id, title, body, status },
});
}
// Query de busca
async function search(tenantId, q) {
const res = await openSearch.client.search({
index: 'articles',
query: {
bool: {
must: [{ multi_match: { query: q, fields: ['title^3', 'body'] } }],
filter: [
{ term: { tenant_id: tenantId } },
{ term: { status: 'published' } },
],
},
},
highlight: { fields: { body: {} } },
size: 20,
});
return res.hits.hits;
}
Problemas e como resolver
| Problema | Causa | Mitigação |
|---|---|---|
| Busca “no Cassandra” lenta | Secondary index / ALLOW FILTERING | Motor de busca dedicado |
| Resultado desatualizado | Lag do indexer | Monitorar lag; SLA; rebuild |
| Documento fantasma | Delete não propagado | Evento de delete + TTL |
| Hot partitions no index | Chave de doc ruim | id composto estável tenant+entity |
Resumo
Cassandra não substitui FTS: use-o para dados particionados e emparelhe Solr (DataStax Search) ou OpenSearch para texto, ranking e facetas. Desenhe sync idempotente e aceite consistência eventual. Próxima aula: matriz de quem mais se beneficia de cada stack de busca.