Sobre nós Guias Projetos Contactos
Админка
please wait

Introdução

O Elasticsearch disponibiliza capacidades poderosas de pesquisa de texto integral que vão muito além de simples consultas LIKE em bases de dados. Construído sobre o Apache Lucene, oferece pontuação de relevância, correspondência difusa (fuzzy matching), agregações e pesquisa quase em tempo real. Este guia aborda uma implementação prática de Elasticsearch para requisitos comuns de pesquisa.

Configuração

Instalação com Docker

# docker-compose.yml
version: '3.8'
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ports:
- "9200:9200"
volumes:
- esdata:/usr/share/elasticsearch/data
kibana:
image: docker.elastic.co/kibana/kibana:8.11.0
ports:
- "5601:5601"
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
volumes:
esdata:

Verificar a Instalação

curl http://localhost:9200

Gestão de Índices

Criar Índice com Mapping

curl -X PUT "localhost:9200/products" -H 'Content-Type: application/json' -d'
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0,
"analysis": {
"analyzer": {
"custom_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "asciifolding", "snowball"]
}
}
}
},
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "custom_analyzer",
"fields": {
"keyword": { "type": "keyword" }
}
},
"description": {
"type": "text",
"analyzer": "custom_analyzer"
},
"category": {
"type": "keyword"
},
"price": {
"type": "float"
},
"in_stock": {
"type": "boolean"
},
"created_at": {
"type": "date"
},
"tags": {
"type": "keyword"
}
}
}
}'

Tipos de Campos

TipoCaso de Uso
textPesquisa de texto integral (analisado)
keywordCorrespondência exata, ordenação, agregações
integer/long/floatNúmeros
dateDatas
booleanVerdadeiro/falso
nestedArrays de objetos

Operações sobre Documentos

Indexar Documentos

# Documento único
curl -X POST "localhost:9200/products/_doc/1" -H 'Content-Type: application/json' -d'
{
"name": "Wireless Bluetooth Headphones",
"description": "Premium noise-canceling headphones with 30-hour battery",
"category": "electronics",
"price": 199.99,
"in_stock": true,
"tags": ["audio", "wireless", "premium"],
"created_at": "2024-01-15"
}'
# Indexação em massa
curl -X POST "localhost:9200/_bulk" -H 'Content-Type: application/json' -d'
{"index": {"_index": "products", "_id": "2"}}
{"name": "USB-C Charging Cable", "category": "accessories", "price": 19.99}
{"index": {"_index": "products", "_id": "3"}}
{"name": "Laptop Stand", "category": "accessories", "price": 49.99}
'

Atualizar Documentos

# Atualização parcial
curl -X POST "localhost:9200/products/_update/1" -H 'Content-Type: application/json' -d'
{
"doc": {
"price": 179.99,
"in_stock": false
}
}'
# Atualização com script
curl -X POST "localhost:9200/products/_update/1" -H 'Content-Type: application/json' -d'
{
"script": {
"source": "ctx._source.price -= params.discount",
"params": { "discount": 20 }
}
}'

Consultas de Pesquisa

Pesquisa Básica

# Consulta match (analisada)
curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d'
{
"query": {
"match": {
"name": "wireless headphones"
}
}
}'
# Multi-match (pesquisar vários campos)
curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d'
{
"query": {
"multi_match": {
"query": "wireless audio",
"fields": ["name^2", "description", "tags"],
"type": "best_fields"
}
}
}'

Consultas Booleanas

curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must": [
{ "match": { "name": "headphones" } }
],
"filter": [
{ "term": { "category": "electronics" } },
{ "range": { "price": { "lte": 300 } } },
{ "term": { "in_stock": true } }
],
"should": [
{ "term": { "tags": "premium" } }
],
"must_not": [
{ "term": { "tags": "refurbished" } }
]
}
}
}'

Pesquisa Difusa

# Lida com gralhas
curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d'
{
"query": {
"fuzzy": {
"name": {
"value": "headpohnes",
"fuzziness": "AUTO"
}
}
}
}'
# Match com fuzziness
curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d'
{
"query": {
"match": {
"name": {
"query": "wireles headpohnes",
"fuzziness": "AUTO"
}
}
}
}'

Autocomplete com Prefixo

curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d'
{
"query": {
"prefix": {
"name.keyword": "Wire"
}
}
}'
# Melhor: utilizar o completion suggester para autocomplete

Agregações

Agregação de Termos

curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d'
{
"size": 0,
"aggs": {
"categories": {
"terms": {
"field": "category",
"size": 10
}
}
}
}'

Agregação por Intervalos

curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d'
{
"size": 0,
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "to": 50 },
{ "from": 50, "to": 100 },
{ "from": 100, "to": 200 },
{ "from": 200 }
]
}
}
}
}'

Agregações Nested

curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d'
{
"size": 0,
"aggs": {
"categories": {
"terms": { "field": "category" },
"aggs": {
"avg_price": { "avg": { "field": "price" } },
"price_stats": { "stats": { "field": "price" } }
}
}
}
}'

Integração com PHP

composer require elasticsearch/elasticsearch
<?php
use Elasticsearch\ClientBuilder;
$client = ClientBuilder::create()
->setHosts(['localhost:9200'])
->build();
// Indexar documento
$params = [
'index' => 'products',
'id' => '1',
'body' => [
'name' => 'Wireless Headphones',
'price' => 199.99,
'category' => 'electronics',
],
];
$client->index($params);
// Pesquisar
$params = [
'index' => 'products',
'body' => [
'query' => [
'bool' => [
'must' => [
['match' => ['name' => $searchQuery]],
],
'filter' => [
['term' => ['category' => $category]],
['range' => ['price' => ['lte' => $maxPrice]]],
],
],
],
'from' => $offset,
'size' => $limit,
'sort' => [
['_score' => 'desc'],
['created_at' => 'desc'],
],
],
];
$response = $client->search($params);
foreach ($response['hits']['hits'] as $hit) {
$product = $hit['_source'];
$score = $hit['_score'];
echo "{$product['name']} (score: {$score})\n";
}
// Total de resultados
$total = $response['hits']['total']['value'];

Integração com Node.js

const { Client } = require('@elastic/elasticsearch');
const client = new Client({ node: 'http://localhost:9200' });
// Indexar documento
await client.index({
index: 'products',
id: '1',
document: {
name: 'Wireless Headphones',
price: 199.99,
category: 'electronics',
},
});
// Pesquisar
const result = await client.search({
index: 'products',
query: {
bool: {
must: [
{ match: { name: searchQuery } },
],
filter: [
{ term: { category: 'electronics' } },
{ range: { price: { lte: 300 } } },
],
},
},
from: 0,
size: 10,
sort: [
{ _score: 'desc' },
{ created_at: 'desc' },
],
});
result.hits.hits.forEach((hit) => {
console.log(hit._source.name, hit._score);
});

Sincronização com a Base de Dados

Sincronização Orientada a Eventos

// Após inserção/atualização na base de dados
class ProductObserver
{
private $elasticsearch;
public function saved(Product $product)
{
$this->elasticsearch->index([
'index' => 'products',
'id' => $product->id,
'body' => [
'name' => $product->name,
'description' => $product->description,
'price' => $product->price,
'category' => $product->category->name,
'updated_at' => $product->updated_at->toIso8601String(),
],
]);
}
public function deleted(Product $product)
{
$this->elasticsearch->delete([
'index' => 'products',
'id' => $product->id,
]);
}
}

Comando de Reindexação em Massa

// Comando Artisan
class ReindexProducts extends Command
{
protected $signature = 'search:reindex';
public function handle()
{
// Eliminar e recriar o índice
$this->elasticsearch->indices()->delete(['index' => 'products']);
$this->createIndex();
// Indexação em massa
$products = Product::with('category')->cursor();
$batch = [];
foreach ($products as $product) {
$batch[] = ['index' => ['_index' => 'products', '_id' => $product->id]];
$batch[] = $this->formatProduct($product);
if (count($batch) >= 1000) {
$this->elasticsearch->bulk(['body' => $batch]);
$batch = [];
}
}
if (!empty($batch)) {
$this->elasticsearch->bulk(['body' => $batch]);
}
}
}

Dicas de Desempenho

Definições do Índice

{
"settings": {
"index": {
"refresh_interval": "30s",
"number_of_replicas": 1
}
}
}

Operações em Massa

// Utilizar a Bulk API para vários documentos
$params = ['body' => []];
foreach ($products as $product) {
$params['body'][] = ['index' => ['_index' => 'products', '_id' => $product->id]];
$params['body'][] = $product->toSearchArray();
if (count($params['body']) >= 2000) {
$client->bulk($params);
$params['body'] = [];
}
}
if (!empty($params['body'])) {
$client->bulk($params);
}

Otimização de Consultas

  1. Utilize filtros para correspondências exatas (com cache)
  2. Limite os campos devolvidos com _source
  3. Utilize paginação com from/size
  4. Evite paginação profunda — utilize search_after em alternativa

Boas Práticas

  1. Desenhe os mappings cuidadosamente — alterar mais tarde é dispendioso
  2. Utilize analyzers apropriados para o seu idioma
  3. Mantenha os dados sincronizados com a sua base de dados principal
  4. Monitorize a saúde do cluster e o espaço em disco
  5. Utilize aliases para reindexação sem downtime
  6. Teste as consultas com volumes de dados realistas

Conclusão

O Elasticsearch disponibiliza capacidades de pesquisa poderosas que melhoram a experiência do utilizador. Conceba os seus mappings de forma ponderada, utilize analyzers apropriados e mantenha os dados sincronizados com a sua base de dados principal. Os padrões deste guia ajudam-no a implementar uma pesquisa de texto integral eficaz nas suas aplicações.

 
 
 
Языки
Темы
Copyright © 1999 — 2026
ZK Interactive