Como implementar search full-text com Elasticsearch

1.1. Visão geral do Elasticsearch

O Elasticsearch é um motor de busca distribuído baseado no Apache Lucene. Sua arquitetura é composta por:
- Índices: coleções de documentos com características similares
- Shards: divisões dos índices que permitem paralelismo e distribuição
- Réplicas: cópias dos shards para alta disponibilidade
- Cluster: conjunto de nós que trabalham juntos

Um cluster típico para full-text search pode ser configurado assim:

PUT /_cluster/settings
{
  "persistent": {
    "cluster.max_shards_per_node": 100
  }
}

1.2. Diferença entre busca exata e full-text

A busca exata (term query) localiza documentos que contêm exatamente o termo especificado:

GET /produtos/_search
{
  "query": {
    "term": {
      "codigo": "ABC-123"
    }
  }
}

Já a busca full-text (match query) analisa o texto antes de buscar:

GET /produtos/_search
{
  "query": {
    "match": {
      "descricao": "laptop gaming高性能"
    }
  }
}

1.3. O processo de análise

A análise transforma o texto bruto em tokens pesquisáveis. O processo inclui:

  • Tokenizer: divide o texto em tokens (ex: standard, whitespace, ngram)
  • Filtros: modificam os tokens (ex: lowercase, stop, stemmer)
  • Analyzers customizados: combinam tokenizers e filtros
PUT /meu_indice
{
  "settings": {
    "analysis": {
      "analyzer": {
        "meu_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase", "stop", "porter_stem"]
        }
      }
    }
  }
}

2. Modelagem de Dados e Mapeamento de Campos

2.1. Definição de mapping

O mapping define como os campos são indexados:

PUT /artigos
{
  "mappings": {
    "properties": {
      "titulo": {
        "type": "text",
        "analyzer": "meu_analyzer"
      },
      "categoria": {
        "type": "keyword"
      },
      "comentarios": {
        "type": "nested",
        "properties": {
          "autor": {"type": "keyword"},
          "texto": {"type": "text"}
        }
      }
    }
  }
}

2.2. Configuração de analyzers customizados

Para conteúdo em português, configure stemming e stop words:

PUT /artigos/_settings
{
  "analysis": {
    "analyzer": {
      "portugues_analyzer": {
        "type": "custom",
        "tokenizer": "standard",
        "filter": [
          "lowercase",
          "brazilian_stop",
          "brazilian_stemmer"
        ]
      }
    },
    "filter": {
      "brazilian_stop": {
        "type": "stop",
        "stopwords": "_brazilian_"
      },
      "brazilian_stemmer": {
        "type": "stemmer",
        "language": "brazilian"
      }
    }
  }
}

2.3. Campos multifields

Combine busca textual com ordenação:

PUT /produtos/_mapping
{
  "properties": {
    "nome": {
      "type": "text",
      "fields": {
        "keyword": {
          "type": "keyword"
        },
        "completo": {
          "type": "text",
          "analyzer": "portugues_analyzer"
        }
      }
    }
  }
}

3. Indexação e Preparação de Documentos

3.1. Estrutura otimizada

PUT /produtos/_doc/1
{
  "nome": "Notebook Dell Inspiron",
  "descricao": "Notebook com processador Intel Core i7, 16GB RAM, SSD 512GB",
  "preco": 4999.99,
  "data_cadastro": "2024-01-15",
  "tags": ["informatica", "notebook", "dell"]
}

3.2. Bulk indexing

POST /produtos/_bulk
{ "index": { "_id": "1" } }
{ "nome": "Notebook Dell", "preco": 4999.99 }
{ "index": { "_id": "2" } }
{ "nome": "Mouse Logitech", "preco": 149.90 }
{ "index": { "_id": "3" } }
{ "nome": "Teclado Mecânico", "preco": 299.90 }

3.3. Alias management para reindexação

POST /_aliases
{
  "actions": [
    { "remove": { "index": "produtos_v1", "alias": "produtos" } },
    { "add": { "index": "produtos_v2", "alias": "produtos" } }
  ]
}

4. Consultas Full-Text

4.1. Match, match_phrase e match_phrase_prefix

GET /produtos/_search
{
  "query": {
    "match_phrase_prefix": {
      "descricao": {
        "query": "notebook dell",
        "max_expansions": 50
      }
    }
  }
}

4.2. Multi-match com boosting

GET /produtos/_search
{
  "query": {
    "multi_match": {
      "query": "notebook gamer",
      "fields": ["nome^3", "descricao^2", "tags"],
      "type": "best_fields",
      "tie_breaker": 0.3
    }
  }
}

4.3. Query string avançada

GET /produtos/_search
{
  "query": {
    "query_string": {
      "query": "nome:(notebook AND dell) OR descricao:(gamer AND i7)",
      "default_field": "nome",
      "allow_leading_wildcard": false
    }
  }
}

5. Relevância e Scoring

5.1. BM25 no Elasticsearch

O Elasticsearch usa BM25 por padrão. Para ajustar parâmetros:

PUT /produtos/_settings
{
  "index": {
    "similarity": {
      "default": {
        "type": "BM25",
        "k1": 1.2,
        "b": 0.75
      }
    }
  }
}

5.2. Function score para boosting

GET /produtos/_search
{
  "query": {
    "function_score": {
      "query": {
        "match": { "descricao": "notebook" }
      },
      "functions": [
        {
          "field_value_factor": {
            "field": "avaliacao_media",
            "factor": 1.5,
            "modifier": "log1p"
          }
        },
        {
          "gauss": {
            "data_cadastro": {
              "origin": "2024-01-01",
              "scale": "30d",
              "decay": 0.5
            }
          }
        }
      ],
      "score_mode": "multiply"
    }
  }
}

5.3. Filtros e rescore

GET /produtos/_search
{
  "query": {
    "bool": {
      "must": {
        "match": { "descricao": "notebook" }
      },
      "filter": [
        { "range": { "preco": { "gte": 2000, "lte": 8000 } } },
        { "term": { "disponivel": true } }
      ]
    }
  },
  "rescore": {
    "window_size": 100,
    "query": {
      "rescore_query": {
        "match_phrase": {
          "descricao": "processador i7"
        }
      },
      "query_weight": 0.7,
      "rescore_query_weight": 1.2
    }
  }
}

6. Otimização de Performance

6.1. Índices por período

PUT /logs-2024.01.15
{
  "settings": {
    "index.number_of_shards": 3,
    "index.number_of_replicas": 1
  }
}

6.2. Cache de consultas

PUT /produtos/_settings
{
  "index.requests.cache.enable": true
}

6.3. Estratégias de sharding

Para índices de leitura intensa:

PUT /produtos
{
  "settings": {
    "index.number_of_shards": 5,
    "index.number_of_replicas": 2,
    "index.routing.allocation.total_shards_per_node": 3
  }
}

7. Manutenção e Monitoramento

7.1. ILM - Index Lifecycle Management

PUT _ilm/policy/meu_policy
{
  "policy": {
    "phases": {
      "hot": {
        "actions": {
          "rollover": {
            "max_size": "50GB",
            "max_age": "30d"
          }
        }
      },
      "delete": {
        "min_age": "90d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}

7.2. Monitoramento de latência

GET /produtos/_stats/search?pretty

7.3. Backup e snapshots

PUT /_snapshot/meu_repositorio
{
  "type": "fs",
  "settings": {
    "location": "/mnt/backups/elasticsearch"
  }
}

PUT /_snapshot/meu_repositorio/snapshot_1
{
  "indices": "produtos,artigos",
  "ignore_unavailable": true
}

Referências