---
title: "Go e Elasticsearch: Busca Full-Text Avançada"
url: "https://golang.com.br/tutoriais/go-elasticsearch/"
markdown_url: "https://golang.com.br/tutoriais/go-elasticsearch.MD"
description: "Aprenda a implementar busca full-text poderosa com Go e Elasticsearch. Guia completo de indexação, queries, agregações e casos de uso reais."
date: "2026-02-11"
author: "Hugo"
---

# Go e Elasticsearch: Busca Full-Text Avançada

Aprenda a implementar busca full-text poderosa com Go e Elasticsearch. Guia completo de indexação, queries, agregações e casos de uso reais.


# 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?](#o-que-é-elasticsearch)
2. [Configuração do Cliente Go](#configuração-do-cliente-go)
3. [Indexando Documentos](#indexando-documentos)
4. [Realizando Buscas](#realizando-buscas)
5. [Queries Avançadas](#queries-avançadas)
6. [Agregações](#agregações)
7. [Mapeamentos e Análise](#mapeamentos-e-análise)
8. [Exemplo Prático: Catálogo de Produtos](#exemplo-prático-catálogo-de-produtos)
9. [Performance e Otimização](#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 Uso | Exemplo |
|-------------|---------|
| Busca de produtos | Amazon, Mercado Livre |
| Log aggregation | ELK Stack (Elasticsearch, Logstash, Kibana) |
| Análise de dados | Dashboards em tempo real |
| Autocomplete | Sugestões de pesquisa |
| Geolocalização | Restaurantes próximos |

## Configuração do Cliente Go

### Instalação

```bash
# 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

```go
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

```go
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

```go
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

```go
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)

```go
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

```go
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)

```go
// 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)

```go
// 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

```go
// 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)

```go
// 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)

```go
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

```go
// 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

```go
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)

```go
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

```go
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

```go
// 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

```go
// 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

```go
// 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

```go
// 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

{{< next-steps >}}
- [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/)
{{< /next-steps >}}

## 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.
