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.

Intermediário 40 min 30 pontos Leitura 0%

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:

  1. Escreve em Cassandra (fonte da verdade).
  2. Emite evento (CDC, Change Data Capture, outbox, ou mensagem na escrita).
  3. Indexer consome e upserta no OpenSearch.
  4. 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.