Go e Elasticsearch: Busca Full-Text Avançada

Elasticsearch é o motor de busca mais popular do mundo, usado desde pequenas aplicações até sistemas como Netflix e Uber. Neste guia, você aprenderá a integrar Elasticsearch com Go para criar buscas rápidas, relevantes e escaláveis.

Índice

  1. O que é Elasticsearch?
  2. Configuração do Cliente Go
  3. Indexando Documentos
  4. Realizando Buscas
  5. Queries Avançadas
  6. Agregações
  7. Mapeamentos e Análise
  8. Exemplo Prático: Catálogo de Produtos
  9. Performance e Otimização

O que é Elasticsearch?

Elasticsearch é um motor de busca e análise distribuído baseado no Apache Lucene. Ele proporciona:

  • Busca Full-Text: Busca em texto natural com relevância
  • Performance: Milissegundos em bilhões de documentos
  • Escalabilidade: Distribuição automática entre nós
  • Agregações: Analytics em tempo real
  • REST API: Interface HTTP JSON simples

Casos de Uso

Caso de UsoExemplo
Busca de produtosAmazon, Mercado Livre
Log aggregationELK Stack (Elasticsearch, Logstash, Kibana)
Análise de dadosDashboards em tempo real
AutocompleteSugestões de pesquisa
GeolocalizaçãoRestaurantes próximos

Configuração do Cliente Go

Instalação

# Cliente oficial Elasticsearch
go get github.com/elastic/go-elasticsearch/v8

# Ou para versões mais antigas
go get github.com/elastic/go-elasticsearch/v7

Cliente Básico

package main

import (
    "log"
    "github.com/elastic/go-elasticsearch/v8"
)

func main() {
    // Configuração básica
    cfg := elasticsearch.Config{
        Addresses: []string{
            "http://localhost:9200",
        },
        Username: "elastic",
        Password: "your-password",
    }

    client, err := elasticsearch.NewClient(cfg)
    if err != nil {
        log.Fatalf("Erro criando cliente: %s", err)
    }

    // Verificar conexão
    res, err := client.Info()
    if err != nil {
        log.Fatalf("Erro no ping: %s", err)
    }
    defer res.Body.Close()

    log.Printf("Conectado: %s", res)
}

Configuração Avançada

import (
    "crypto/tls"
    "net/http"
    "time"
)

cfg := elasticsearch.Config{
    // Múltiplos nós para alta disponibilidade
    Addresses: []string{
        "https://es1.example.com:9200",
        "https://es2.example.com:9200",
        "https://es3.example.com:9200",
    },
    
    // Autenticação
    Username: "elastic",
    Password: "senha-segura",
    
    // API Key (alternativa à senha)
    // APIKey: "base64-api-key",
    
    // Configurações de retry
    MaxRetries: 3,
    RetryBackoff: func(i int) time.Duration {
        return time.Duration(i) * 100 * time.Millisecond
    },
    
    // Timeout
    RequestTimeout: 10 * time.Second,
    
    // Transport personalizado (TLS, etc.)
    Transport: &http.Transport{
        TLSClientConfig: &tls.Config{
            InsecureSkipVerify: true, // ⚠️ Apenas para desenvolvimento
        },
        MaxIdleConns:        100,
        MaxIdleConnsPerHost: 10,
    },
    
    // Logger para debug
    Logger: &estransport.ColorLogger{
        Output:             os.Stdout,
        EnableRequestBody:  true,
        EnableResponseBody: true,
    },
}

client, err := elasticsearch.NewClient(cfg)

Indexando Documentos

Criando um Índice

package main

import (
    "bytes"
    "context"
    "encoding/json"
    "log"
    "strings"

    "github.com/elastic/go-elasticsearch/v8"
    "github.com/elastic/go-elasticsearch/v8/esapi"
)

func criarIndice(client *elasticsearch.Client, nome string) error {
    // Verificar se índice existe
    exists, err := client.Indices.Exists([]string{nome})
    if err != nil {
        return err
    }
    
    if exists.StatusCode == 200 {
        log.Printf("Índice %s já existe", nome)
        return nil
    }

    // Criar índice com mapeamento
    mapping := `{
        "mappings": {
            "properties": {
                "titulo": {
                    "type": "text",
                    "analyzer": "portuguese"
                },
                "descricao": {
                    "type": "text",
                    "analyzer": "portuguese"
                },
                "preco": {
                    "type": "float"
                },
                "categoria": {
                    "type": "keyword"
                },
                "data_criacao": {
                    "type": "date"
                }
            }
        },
        "settings": {
            "number_of_shards": 1,
            "number_of_replicas": 0
        }
    }`

    req := esapi.IndicesCreateRequest{
        Index: nome,
        Body:  strings.NewReader(mapping),
    }

    res, err := req.Do(context.Background(), client)
    if err != nil {
        return err
    }
    defer res.Body.Close()

    if res.IsError() {
        log.Printf("Erro criando índice: %s", res.String())
    } else {
        log.Printf("Índice %s criado com sucesso", nome)
    }

    return nil
}

Indexando Documentos

type Produto struct {
    ID          string    `json:"id"`
    Titulo      string    `json:"titulo"`
    Descricao   string    `json:"descricao"`
    Preco       float64   `json:"preco"`
    Categoria   string    `json:"categoria"`
    Tags        []string  `json:"tags"`
    DataCriacao time.Time `json:"data_criacao"`
}

func indexarProduto(client *elasticsearch.Client, produto Produto) error {
    // Serializar para JSON
    data, err := json.Marshal(produto)
    if err != nil {
        return err
    }

    // Indexar documento
    req := esapi.IndexRequest{
        Index:      "produtos",
        DocumentID: produto.ID,
        Body:       bytes.NewReader(data),
        Refresh:    "true", // Torna o documento imediatamente disponível
    }

    res, err := req.Do(context.Background(), client)
    if err != nil {
        return err
    }
    defer res.Body.Close()

    if res.IsError() {
        return fmt.Errorf("erro indexando: %s", res.String())
    }

    return nil
}

// Indexar em bulk (mais eficiente para muitos documentos)
func indexarEmBulk(client *elasticsearch.Client, produtos []Produto) error {
    var buf bytes.Buffer

    for _, p := range produtos {
        // Ação de index
        meta := []byte(fmt.Sprintf(`{"index":{"_index":"produtos","_id":"%s"}}%s`, p.ID, "\n"))
        data, _ := json.Marshal(p)
        data = append(data, "\n"...)
        
        buf.Write(meta)
        buf.Write(data)
    }

    req := esapi.BulkRequest{
        Body:    &buf,
        Refresh: "true",
    }

    res, err := req.Do(context.Background(), client)
    if err != nil {
        return err
    }
    defer res.Body.Close()

    if res.IsError() {
        return fmt.Errorf("erro bulk: %s", res.String())
    }

    return nil
}

Realizando Buscas

Busca Simples (Match Query)

func buscarProdutos(client *elasticsearch.Client, termo string) ([]Produto, error) {
    // Construir query
    query := map[string]interface{}{
        "query": map[string]interface{}{
            "multi_match": map[string]interface{}{
                "query":  termo,
                "fields": []string{"titulo^3", "descricao", "tags"},
            },
        },
    }

    var buf bytes.Buffer
    if err := json.NewEncoder(&buf).Encode(query); err != nil {
        return nil, err
    }

    // Executar busca
    res, err := client.Search(
        client.Search.WithContext(context.Background()),
        client.Search.WithIndex("produtos"),
        client.Search.WithBody(&buf),
        client.Search.WithTrackTotalHits(true),
        client.Search.WithPretty(),
    )
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()

    if res.IsError() {
        return nil, fmt.Errorf("erro na busca: %s", res.String())
    }

    // Decodificar resultado
    var r map[string]interface{}
    if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
        return nil, err
    }

    // Extrair produtos
    var produtos []Produto
    hits := r["hits"].(map[string]interface{})["hits"].([]interface{})
    
    for _, hit := range hits {
        source := hit.(map[string]interface{})["_source"]
        sourceJSON, _ := json.Marshal(source)
        
        var p Produto
        json.Unmarshal(sourceJSON, &p)
        produtos = append(produtos, p)
    }

    return produtos, nil
}

Busca com Filtros

func buscarComFiltros(client *elasticsearch.Client, params BuscaParams) ([]Produto, error) {
    // Bool query com must, filter, should, must_not
    query := map[string]interface{}{
        "query": map[string]interface{}{
            "bool": map[string]interface{}{
                "must": []map[string]interface{}{
                    {"multi_match": map[string]interface{}{
                        "query":  params.Termo,
                        "fields": []string{"titulo^3", "descricao"},
                    }},
                },
                "filter": []map[string]interface{}{
                    // Categoria exata
                    {"term": map[string]interface{}{"categoria": params.Categoria}},
                    // Range de preço
                    {"range": map[string]interface{}{
                        "preco": map[string]interface{}{
                            "gte": params.PrecoMin,
                            "lte": params.PrecoMax,
                        },
                    }},
                    // Data
                    {"range": map[string]interface{}{
                        "data_criacao": map[string]interface{}{
                            "gte": params.DataInicio.Format(time.RFC3339),
                            "lte": params.DataFim.Format(time.RFC3339),
                        },
                    }},
                },
            },
        },
        "sort": []map[string]interface{}{
            {params.Ordenacao: map[string]interface{}{"order": params.Ordem}},
        },
        "from": params.Offset,
        "size": params.Limite,
    }

    // ... executar busca
}

Queries Avançadas

Match Phrase (Busca de Frase)

// Busca exata da frase
query := map[string]interface{}{
    "query": map[string]interface{}{
        "match_phrase": map[string]interface{}{
            "descricao": "notebook gamer",
        },
    },
}

Fuzzy Search (Tolerância a Erros)

// Tolerância a erros de digitação
query := map[string]interface{}{
    "query": map[string]interface{}{
        "fuzzy": map[string]interface{}{
            "titulo": map[string]interface{}{
                "value":     "iphone",  // Busca "iphon", "iphonne", etc.
                "fuzziness": "AUTO",    // AUTO determina baseado no tamanho
            },
        },
    },
}

Wildcard e Regexp

// Wildcard
query := map[string]interface{}{
    "query": map[string]interface{}{
        "wildcard": map[string]interface{}{
            "titulo": "*iphone*",  // Contém "iphone"
        },
    },
}

// Regexp
query := map[string]interface{}{
    "query": map[string]interface{}{
        "regexp": map[string]interface{}{
            "titulo": "noteboo.*",  // Começa com "noteboo"
        },
    },
}

Autocomplete (Prefix + Edge N-gram)

// Mapeamento para autocomplete
mappingAutoComplete := `{
    "mappings": {
        "properties": {
            "titulo": {
                "type": "text",
                "analyzer": "autocomplete",
                "search_analyzer": "standard"
            }
        }
    },
    "settings": {
        "analysis": {
            "analyzer": {
                "autocomplete": {
                    "tokenizer": "autocomplete_tokenizer",
                    "filter": ["lowercase"]
                }
            },
            "tokenizer": {
                "autocomplete_tokenizer": {
                    "type": "edge_ngram",
                    "min_gram": 2,
                    "max_gram": 20,
                    "token_chars": ["letter", "digit"]
                }
            }
        }
    }
}`

// Query de autocomplete
query := map[string]interface{}{
    "query": map[string]interface{}{
        "match": map[string]interface{}{
            "titulo": {
                "query": termo,
                "operator": "and",
            },
        },
    },
    "size": 10,
}

Busca por Proximidade (Geo)

type Localizacao struct {
    Lat float64 `json:"lat"`
    Lon float64 `json:"lon"`
}

// Mapeamento geopoint
geoMapping := `{
    "mappings": {
        "properties": {
            "localizacao": {
                "type": "geo_point"
            }
        }
    }
}`

// Buscar próximos
query := map[string]interface{}{
    "query": map[string]interface{}{
        "geo_distance": map[string]interface{}{
            "distance": "10km",
            "localizacao": map[string]interface{}{
                "lat": -23.5505,   // São Paulo
                "lon": -46.6333,
            },
        },
    },
    "sort": []map[string]interface{}{
        {
            "_geo_distance": map[string]interface{}{
                "localizacao": map[string]interface{}{
                    "lat": -23.5505,
                    "lon": -46.6333,
                },
                "order":         "asc",
                "unit":          "km",
                "distance_type": "plane",
            },
        },
    },
}

Agregações

Agregações Básicas

// Contagem por categoria
agg := map[string]interface{}{
    "aggs": map[string]interface{}{
        "por_categoria": map[string]interface{}{
            "terms": map[string]interface{}{
                "field": "categoria",
                "size":  20,
            },
        },
        "preco_stats": map[string]interface{}{
            "stats": map[string]interface{}{
                "field": "preco",
            },
        },
    },
    "size": 0, // Não retornar documentos, só agregações
}

Histograma de Preços

agg := map[string]interface{}{
    "aggs": map[string]interface{}{
        "histograma_precos": map[string]interface{}{
            "histogram": map[string]interface{}{
                "field":    "preco",
                "interval": 100,
            },
        },
    },
}

Agregações Aninhadas (Sub-aggs)

agg := map[string]interface{}{
    "aggs": map[string]interface{}{
        "por_categoria": map[string]interface{}{
            "terms": map[string]interface{}{
                "field": "categoria",
            },
            "aggs": map[string]interface{}{
                "preco_medio": map[string]interface{}{
                    "avg": map[string]interface{}{
                        "field": "preco",
                    },
                },
                "preco_max": map[string]interface{}{
                    "max": map[string]interface{}{
                        "field": "preco",
                    },
                },
            },
        },
    },
}

Exemplo Prático: Catálogo de Produtos

Modelo Completo

package main

import (
    "bytes"
    "context"
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "time"

    "github.com/elastic/go-elasticsearch/v8"
    "github.com/go-chi/chi/v5"
)

type Produto struct {
    ID          string    `json:"id"`
    Titulo      string    `json:"titulo"`
    Descricao   string    `json:"descricao"`
    Preco       float64   `json:"preco"`
    PrecoPromo  float64   `json:"preco_promo,omitempty"`
    Categoria   string    `json:"categoria"`
    SubCategoria string   `json:"sub_categoria"`
    Marca       string    `json:"marca"`
    Tags        []string  `json:"tags"`
    Estoque     int       `json:"estoque"`
    Rating      float64   `json:"rating"`
    Reviews     int       `json:"reviews"`
    Imagens     []string  `json:"imagens"`
    DataCriacao time.Time `json:"data_criacao"`
}

type CatalogoService struct {
    es *elasticsearch.Client
}

func NewCatalogoService(es *elasticsearch.Client) *CatalogoService {
    return &CatalogoService{es: es}
}

// Busca com sugestões de autocomplete
func (s *CatalogoService) Buscar(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    
    // Parâmetros da query
    query := r.URL.Query().Get("q")
    categoria := r.URL.Query().Get("categoria")
    precoMin := parseFloat(r.URL.Query().Get("preco_min"), 0)
    precoMax := parseFloat(r.URL.Query().Get("preco_max"), 100000)
    ordenar := r.URL.Query().Get("ordenar") // preco, rating, relevancia
    page := parseInt(r.URL.Query().Get("page"), 1)
    size := parseInt(r.URL.Query().Get("size"), 20)
    
    // Construir query
    var esQuery map[string]interface{}
    
    if query == "" {
        // Match all com filtros
        esQuery = map[string]interface{}{
            "query": map[string]interface{}{
                "bool": map[string]interface{}{
                    "must": []map[string]interface{}{{
                        "match_all": map[string]interface{}{},
                    }},
                },
            },
        }
    } else {
        // Multi-match com boost
        esQuery = map[string]interface{}{
            "query": map[string]interface{}{
                "bool": map[string]interface{}{
                    "must": []map[string]interface{}{{
                        "multi_match": map[string]interface{}{
                            "query": query,
                            "fields": []string{
                                "titulo^4",      // Titulo tem maior peso
                                "marca^3",       // Marca também é importante
                                "tags^2",        // Tags médio
                                "descricao",     // Descrição padrão
                            },
                            "type": "best_fields",
                            "tie_breaker": 0.3,
                        },
                    }},
                },
            },
        }
    }
    
    // Adicionar filtros
    filters := []map[string]interface{}{}
    
    if categoria != "" {
        filters = append(filters, map[string]interface{}{
            "term": map[string]interface{}{"categoria": categoria},
        })
    }
    
    filters = append(filters, map[string]interface{}{
        "range": map[string]interface{}{
            "preco": map[string]interface{}{
                "gte": precoMin,
                "lte": precoMax,
            },
        },
    })
    
    // Estoque disponível
    filters = append(filters, map[string]interface{}{
        "range": map[string]interface{}{
            "estoque": map[string]interface{}{
                "gt": 0,
            },
        },
    })
    
    if len(filters) > 0 {
        esQuery["query"].(map[string]interface{})["bool"].(map[string]interface{})["filter"] = filters
    }
    
    // Ordenação
    switch ordenar {
    case "preco_asc":
        esQuery["sort"] = []map[string]interface{}{
            {"preco": "asc"},
        }
    case "preco_desc":
        esQuery["sort"] = []map[string]interface{}{
            {"preco": "desc"},
        }
    case "rating":
        esQuery["sort"] = []map[string]interface{}{
            {"rating": "desc"},
        }
    default:
        // Relevância (score) é padrão
    }
    
    // Paginação
    esQuery["from"] = (page - 1) * size
    esQuery["size"] = size
    
    // Agregações (facets)
    esQuery["aggs"] = map[string]interface{}{
        "categorias": map[string]interface{}{
            "terms": map[string]interface{}{
                "field": "categoria",
                "size":  10,
            },
        },
        "marcas": map[string]interface{}{
            "terms": map[string]interface{}{
                "field": "marca",
                "size":  20,
            },
        },
        "faixas_preco": map[string]interface{}{
            "histogram": map[string]interface{}{
                "field":    "preco",
                "interval": 500,
            },
        },
    }
    
    // Highlight (destacar termos encontrados)
    if query != "" {
        esQuery["highlight"] = map[string]interface{}{
            "fields": map[string]interface{}{
                "titulo": map[string]interface{}{},
                "descricao": map[string]interface{}{
                    "fragment_size": 150,
                },
            },
        }
    }
    
    // Executar busca
    var buf bytes.Buffer
    json.NewEncoder(&buf).Encode(esQuery)
    
    res, err := s.es.Search(
        s.es.Search.WithContext(ctx),
        s.es.Search.WithIndex("produtos"),
        s.es.Search.WithBody(&buf),
        s.es.Search.WithTrackTotalHits(true),
    )
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer res.Body.Close()
    
    if res.IsError() {
        http.Error(w, res.String(), http.StatusInternalServerError)
        return
    }
    
    // Decodificar resposta
    var result map[string]interface{}
    if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    // Formatar resposta
    response := s.formatarResposta(result, page, size)
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(response)
}

func (s *CatalogoService) formatarResposta(result map[string]interface{}, page, size int) map[string]interface{} {
    hits := result["hits"].(map[string]interface{})
    total := int(hits["total"].(map[string]interface{})["value"].(float64))
    docs := hits["hits"].([]interface{})
    
    produtos := []map[string]interface{}{}
    for _, doc := range docs {
        d := doc.(map[string]interface{})
        source := d["_source"]
        score := d["_score"]
        
        item := map[string]interface{}{
            "data":  source,
            "score": score,
        }
        
        // Adicionar highlights se existirem
        if highlight, ok := d["highlight"]; ok {
            item["highlights"] = highlight
        }
        
        produtos = append(produtos, item)
    }
    
    // Extrair agregações
    aggs := result["aggregations"].(map[string]interface{})
    
    return map[string]interface{}{
        "produtos": produtos,
        "paginacao": map[string]interface{}{
            "total":        total,
            "pagina":       page,
            "por_pagina":   size,
            "total_paginas": (total + size - 1) / size,
        },
        "facets": map[string]interface{}{
            "categorias":   extractBuckets(aggs, "categorias"),
            "marcas":       extractBuckets(aggs, "marcas"),
            "faixas_preco": extractBuckets(aggs, "faixas_preco"),
        },
    }
}

func extractBuckets(aggs map[string]interface{}, key string) []map[string]interface{} {
    if agg, ok := aggs[key]; ok {
        buckets := agg.(map[string]interface{})["buckets"].([]interface{})
        result := []map[string]interface{}{}
        for _, b := range buckets {
            result = append(result, b.(map[string]interface{}))
        }
        return result
    }
    return []map[string]interface{}{}
}

func main() {
    // Inicializar Elasticsearch
    es, err := elasticsearch.NewDefaultClient()
    if err != nil {
        log.Fatal(err)
    }
    
    catalogo := NewCatalogoService(es)
    
    // Setup rotas
    r := chi.NewRouter()
    r.Get("/api/buscar", catalogo.Buscar)
    
    log.Println("Servidor rodando em :8080")
    http.ListenAndServe(":8080", r)
}

Performance e Otimização

1. Bulk Indexing

// Indexar em lotes de 1000 documentos
const batchSize = 1000

for i := 0; i < len(produtos); i += batchSize {
    end := i + batchSize
    if end > len(produtos) {
        end = len(produtos)
    }
    
    if err := indexarEmBulk(client, produtos[i:end]); err != nil {
        log.Printf("Erro no batch %d: %v", i/batchSize, err)
    }
}

2. Connection Pooling

// Reutilizar conexões HTTP
cfg := elasticsearch.Config{
    Addresses: []string{"http://localhost:9200"},
    Transport: &http.Transport{
        MaxIdleConns:        100,
        MaxIdleConnsPerHost: 10,
        IdleConnTimeout:     30 * time.Second,
    },
}

3. Scroll para Grandes Volumes

// Buscar grandes volumes sem sobrecarregar memória
func scrollSearch(client *elasticsearch.Client, query map[string]interface{}) error {
    ctx := context.Background()
    
    // Iniciar scroll
    res, err := client.Search(
        client.Search.WithContext(ctx),
        client.Search.WithIndex("produtos"),
        client.Search.WithBody(encodeQuery(query)),
        client.Search.WithSize(1000),
        client.Search.WithScroll(5*time.Minute),
    )
    if err != nil {
        return err
    }
    defer res.Body.Close()
    
    var result map[string]interface{}
    json.NewDecoder(res.Body).Decode(&result)
    
    scrollID := result["_scroll_id"].(string)
    
    // Processar primeira página
    processHits(result["hits"].(map[string]interface{})["hits"].([]interface{}))
    
    // Continuar scroll
    for {
        scrollRes, err := client.Scroll(
            client.Scroll.WithContext(ctx),
            client.Scroll.WithScrollID(scrollID),
            client.Scroll.WithScroll(5*time.Minute),
        )
        if err != nil {
            return err
        }
        defer scrollRes.Body.Close()
        
        var scrollResult map[string]interface{}
        json.NewDecoder(scrollRes.Body).Decode(&scrollResult)
        
        hits := scrollResult["hits"].(map[string]interface{})["hits"].([]interface{})
        if len(hits) == 0 {
            break
        }
        
        processHits(hits)
        scrollID = scrollResult["_scroll_id"].(string)
    }
    
    // Limpar scroll
    client.ClearScroll(
        client.ClearScroll.WithContext(ctx),
        client.ClearScroll.WithScrollID(scrollID),
    )
    
    return nil
}

4. Index Templates

// Criar template para índices de logs
indexTemplate := `{
    "index_patterns": ["logs-*"],
    "template": {
        "settings": {
            "number_of_shards": 2,
            "number_of_replicas": 1,
            "index.lifecycle.rollover_alias": "logs"
        },
        "mappings": {
            "properties": {
                "@timestamp": {"type": "date"},
                "level": {"type": "keyword"},
                "message": {"type": "text"},
                "service": {"type": "keyword"}
            }
        }
    }
}`

req := esapi.IndicesPutIndexTemplateRequest{
    Name: "logs-template",
    Body: strings.NewReader(indexTemplate),
}

res, err := req.Do(context.Background(), client)

Próximos Passos

Próximos Passos

- [Go e MongoDB: CRUD e Agregações](/tutoriais/go-mongodb/) - [Go e Redis: Cache e Session Store](/tutoriais/go-redis-cache/) - [Go e PostgreSQL: CRUD Completo](/tutoriais/go-postgresql-crud/) - [Go para APIs REST: Guia Completo](/tutoriais/go-api-rest/)

Conclusão

Elasticsearch é uma ferramenta poderosa para busca e analytics em Go. Este guia cobriu:

  • Indexação: Bulk, templates, mapeamentos
  • Busca: Full-text, filtros, fuzzy, autocomplete
  • Agregações: Analytics em tempo real
  • Performance: Otimizações e boas práticas

Para aplicações que precisam de busca robusta, Elasticsearch + Go é uma combinação excelente.


FAQ

Q: Quando usar Elasticsearch vs PostgreSQL Full-Text? R: Use PostgreSQL para busca simples em poucos campos. Use Elasticsearch para busca complexa, relevância, agregações e alta escala.

Q: É necessário usar Logstash? R: Não. O cliente Go pode indexar diretamente. Logstash é útil para processamento complexo de logs.

Q: Como faço backup dos dados? R: Use snapshots para S3 ou filesystem. Configure políticas de snapshot automático.

Q: Posso usar Elasticsearch como banco primário? R: Não recomendado. Use como índice secundário para busca, mantendo dados primários em SQL/NoSQL.

Q: Como escalo Elasticsearch? R: Adicione nós ao cluster. Shards são distribuídos automaticamente. Monitore com Kibana.