04 ‐ Entendendo Índice, Shards e Segments no Elasticsearch - BktechBrazil/elastic-eck GitHub Wiki

4.1 - Fundamentos: índices, shards e segments

Índice            (coleção lógica de documentos: "pedidos")
  └─ Shard        (uma fatia do índice = um índice Lucene completo)
       └─ Segment (arquivos imutáveis dentro do shard; nascem no refresh e são fundidos no merge)

Índice

Um índice no Elasticsearch é uma coleção de documentos com características semelhantes. Cada índice é identificado por um nome único e é a unidade principal de armazenamento e consulta. Em um cluster, é possível ter quantos índices forem necessários — por exemplo, um para logs de aplicação, outro para dados de clientes, outro para eventos de segurança.

Shards

Para distribuir os dados horizontalmente entre os nodes do cluster, o Elasticsearch divide cada índice em partes menores chamadas shards. Cada shard é, na prática, uma instância independente do Lucene — com sua própria estrutura de dados e capacidade de executar buscas de forma autônoma.

Essa divisão permite que o Elasticsearch processe operações em paralelo e distribua a carga entre múltiplos nodes, o que melhora tanto o desempenho quanto a disponibilidade.

Existem dois tipos de shard:

  • Primary shards: o número de primary shards é definido no momento da criação do índice e não pode ser alterado depois. Escolher esse número com cuidado é importante — shards demais geram overhead de gerenciamento, shards de menos limitam a capacidade de escalar.
  • Replica shards: cópias dos primary shards, armazenadas em nodes diferentes. Servem para alta disponibilidade (se um node falhar, as réplicas garantem que os dados continuem acessíveis) e para aumentar o throughput de leitura, já que as buscas podem ser distribuídas entre primários e réplicas.

Regra de ouro: uma réplica nunca é alocada no mesmo node do seu primário — senão ela não protegeria contra a queda daquele node. Por isso, em um cluster de um único node, as réplicas ficam unassigned e o índice fica yellow.

Primários × réplicas depois da criação: number_of_replicas é dinâmico e pode ser alterado a qualquer momento. number_of_shards é fixo: para mudar o número de primários é preciso criar um novo índice (por exemplo, com as APIs _split, _shrink ou _reindex).

Segments

Dentro de cada shard, os dados são organizados em estruturas chamadas segments. Um segment é um índice invertido imutável — a estrutura central usada pelo Lucene para armazenar e recuperar documentos.

Quando novos documentos são indexados, eles passam por três etapas:

  1. Buffer de memória: o documento é escrito primeiro em um buffer em memória (o translog garante durabilidade em caso de falha antes da escrita em disco).
  2. Novo segment: periodicamente, o buffer é descarregado para um novo segment em disco. Esse segment é imutável — para atualizar ou deletar um documento, o Elasticsearch marca o documento antigo como deletado e escreve uma nova versão em um segment diferente.
  3. Merge: com o tempo, o Elasticsearch mescla segments pequenos em segmentos maiores. Durante esse processo, documentos marcados como deletados são descartados definitivamente, liberando espaço em disco.

Elasticsearch Indexing Process

Fonte: Medium — Luis Sena

Entender esse ciclo é essencial para ajustar o comportamento de indexação, controlar o uso de memória e disco, e otimizar o desempenho de buscas em diferentes cargas de trabalho. É ele que explica por que "otimizar" um índice que não recebe mais escrita é fazer um force merge: juntar os segments em poucos segments maiores e eliminar de vez os documentos deletados.

Distribuição de documentos nos Shards

Quando um documento é indexado, o Elasticsearch precisa decidir em qual shard primário ele será armazenado. Essa decisão é feita por uma fórmula de roteamento baseada no _id do documento:

shard = hash(_id) % number_of_primary_shards

O Elasticsearch calcula o hash do _id e divide pelo número de primary shards — o resto da divisão determina o shard destino. Isso garante uma distribuição uniforme dos documentos entre os shards sem necessidade de coordenação central.

Consequências práticas:

  • O número de primary shards não pode ser alterado depois da criação do índice, porque mudar esse número alteraria o resultado da fórmula — os documentos já indexados nunca seriam encontrados
  • É possível definir um roteamento customizado usando um campo específico no lugar do _id — útil para garantir que documentos relacionados fiquem no mesmo shard e otimizar buscas que sempre filtram por aquele campo

Tamanho e quantidade de Shards — orientações práticas

Escolher o número correto de shards é uma das decisões mais importantes ao criar um índice. Tanto poucos quanto muitos shards causam problemas:

Problemas com poucos shards:

  • Shards muito grandes ficam lentos para buscar e demoram muito para se recuperar após uma falha de node
  • Limita a capacidade de paralelizar operações de busca

Problemas com muitos shards:

  • Cada shard consome memória do cluster (heap do master node e metadados)
  • Um número excessivo de shards pequenos gera mais overhead de coordenação do que ganho de performance

Referências práticas recomendadas pela Elastic:

Parâmetro Referência
Tamanho ideal por shard Entre 10 GB e 50 GB para a maioria dos casos de uso
Tamanho máximo recomendado por shard 50 GB — acima disso, recovery e merge ficam lentos
Número máximo de documentos por shard Até 200 milhões de documentos
Heap dos master-eligible nodes Pelo menos 1 GB de heap para cada 3.000 índices do cluster
Limite de shards por node Por padrão, o cluster não permite mais de 1.000 shards (não congelados) por node (cluster.max_shards_per_node)

Exemplo prático: se você espera ter um índice de 100 GB, o ideal é criar entre 2 e 10 shards primários. Se o índice vai crescer ao longo do tempo via rollover (como em logs), mantenha cada índice gerado pelo rollover entre 10 GB e 50 GB ajustando as condições de rollover no ILM.


4.2 - Nodes, papéis e a tradução para o ECK

Um node pode acumular papéis (node.roles). Em clusters pequenos, um node faz tudo; em clusters grandes, separar os papéis traz estabilidade:

Papel Função
master Coordena o cluster (estado, alocação de shards). Poucos e dedicados em produção
data (data_hot, data_warm, data_cold, data_frozen, data_content) Guardam os shards. data genérico atende a todos os tiers
ingest Executam ingest pipelines (pré-processamento)
ml Rodam jobs de Machine Learning
coordinating Um node sem nenhum papel (node.roles: []) só recebe requisições, distribui e consolida os resultados

No ECK, cada conceito de arquitetura do Elasticsearch vira um objeto do Kubernetes:

Conceito Elasticsearch Objeto no ECK
Um node Um pod (gerenciado por um StatefulSet)
Um grupo de nodes com a mesma configuração Um nodeSet (com config.node.roles)
Escalar o número de nodes Mudar count no nodeSet
Disco de um node Um PVC (via volumeClaimTemplates)
Topologia com papéis ou tiers separados Vários nodeSets com papéis e recursos diferentes

Ou seja: desenhar a arquitetura de um cluster no ECK é desenhar a lista de nodeSets. Um cluster com 3 masters dedicados e 3 nodes de dados e ingestão são dois nodeSets:

nodeSets:
  # 3 masters dedicados
  - name: master
    count: 3
    config:
      node.roles: ["master"]
  # 3 nodes de dados e ingestão
  - name: ingest-data
    count: 3
    config:
      node.roles: ["data", "ingest"]

O nome do pod segue o padrão <cluster>-es-<nodeSet>-<número>: o nodeSet ingest-data de um cluster quickstart gera os pods quickstart-es-ingest-data-0, -1 e -2. O nome do node no Elasticsearch é o mesmo nome do pod.

Quando o count aumenta, o operator cria os novos pods. Quando diminui, o operator primeiro migra os dados dos nodes que vão sair e só depois remove os pods e os seus PVCs. A migração só acontece se o número de réplicas dos índices couber nos nodes que sobram: um índice com 1 réplica não pode ficar em um único node de dados, e nesse caso a redução fica parada até você diminuir as réplicas.


4.3 - Alocação e roteamento de shards

A alocação de shards define como o Elasticsearch distribui os shards entre os nodes do cluster. O Elasticsearch faz isso automaticamente, mas oferece controles granulares para situações específicas.

Alocação em nível de cluster

Controla o comportamento geral de alocação de shards em todo o cluster. É útil durante janelas de manutenção para evitar movimentação desnecessária de shards.

E no ECK? Nos rolling upgrades e reinícios, o operator desliga cada node usando a API de desligamento de nodes (node shutdown) com um allocation_delay: o Elasticsearch espera um pouco antes de realocar os shards de um node que vai voltar logo, evitando movimentação desnecessária. Esse tempo pode ser ajustado com a anotação eck.k8s.elastic.co/restart-allocation-delay no recurso Elasticsearch. Você não precisa alterar a alocação à mão para upgrades.

Alocação baseada em disco

O Elasticsearch monitora o uso de disco de cada node e toma decisões de alocação com base em três marcas d'água (watermarks):

  • Low watermark: quando atingida, o node para de receber novos shards por alocação. Não afeta shards já existentes.
  • High watermark: quando atingida, o Elasticsearch começa a mover shards existentes para outros nodes com mais espaço disponível.
  • Flood stage: situação crítica — o Elasticsearch bloqueia a indexação nos índices alocados nesse node para evitar corrupção de dados.

E no ECK? O disco de cada node é o PVC criado a partir do volumeClaimTemplates. Com a StorageClass local-path do laboratório, o volume é um diretório do próprio host e o tamanho pedido no PVC não é imposto: o Elasticsearch enxerga o espaço livre do disco da VM.

Alocação por atributos — Shard Allocation Awareness

Permite que o Elasticsearch considere atributos personalizados dos nodes na hora de distribuir shards. O caso de uso mais comum é rack awareness — garantir que primary e replica shards de um mesmo índice fiquem em racks físicos diferentes, de forma que uma falha de rack não derrube todos os dados ao mesmo tempo.

E no ECK? Os atributos são definidos no config do nodeSet (por exemplo, node.attr.rack_id). O ECK cria automaticamente em cada pod o atributo k8s_node_name, com o nome do node do Kubernetes em que o pod está rodando, e o usa na alocação para evitar que primário e réplica fiquem no mesmo host. A partir do ECK 3.4, a forma recomendada de espalhar o cluster por zonas de disponibilidade é o campo zoneAwareness do nodeSet: o operator distribui os pods entre as zonas e configura sozinho o node.attr.zone e o cluster.routing.allocation.awareness.attributes:

nodeSets:
  - name: default
    count: 6
    zoneAwareness: {}

Alocação baseada em filtros

Permite incluir ou excluir nodes específicos da alocação usando atributos como nome do node, endereço IP ou atributos personalizados. Útil para retirar um node de produção gradualmente ou direcionar índices específicos para hardware determinado.

Arquitetura Hot-Warm-Cold-Frozen

Uma estratégia de gerenciamento de dados que categoriza nodes e índices em "temperaturas" com base na frequência de acesso:

Tier Hardware típico Para que serve
Hot SSDs de alto desempenho Dados ativos: indexação constante e buscas frequentes
Warm HDDs ou SSDs de médio desempenho Dados recentes mas menos acessados, principalmente leitura
Cold Storage mais barato Dados raramente consultados, recuperação mais lenta aceitável
Frozen Object storage (ex: S3, GCS) Dados históricos via searchable snapshots, acesso muito esporádico

Essa arquitetura é a base do ILM (Index Lifecycle Management), que automatiza a transição dos dados entre os tiers ao longo do tempo.

Hot-Warm-Cold Architecture

Fonte: Opster — ILM: Manage the index lifecycle

E no ECK? Cada tier é um nodeSet com o papel correspondente (data_hot, data_warm, data_cold), seus próprios recursos e sua própria StorageClass. Em produção, os nodeSets de cada tier costumam ter também uma nodeAffinity para rodar em hosts do Kubernetes com o hardware adequado (SSD para hot, discos maiores para warm).

4.4 - APIs _cat para diagnóstico

As APIs _cat retornam informações do cluster em formato de tabela, fácil de ler no terminal ou no Console. O parâmetro ?v adiciona o cabeçalho das colunas e ?help lista as colunas disponíveis.

Endpoint O que mostra
GET _cat/health?v Saúde do cluster (green, yellow, red)
GET _cat/nodes?v Nodes, papéis (node.role), heap, CPU e qual é o master (*)
GET _cat/nodeattrs?v Atributos personalizados de cada node
GET _cat/indices?v Índices, saúde, número de primários e réplicas, documentos e tamanho
GET _cat/shards/<indice>?v Onde cada shard está (prirep: p = primário, r = réplica) e o estado (STARTED, INITIALIZING, UNASSIGNED)
GET _cat/segments/<indice>?v Segments de cada shard: quantidade, documentos, deletados e tamanho
GET _cat/allocation?v Shards e uso de disco por node
GET _cluster/health/<indice>?pretty Saúde de um índice específico
GET _cluster/allocation/explain Por que um shard não está alocado

Na coluna node.role do _cat/nodes, cada letra é um papel: m = master, d = data, i = ingest, l = ml, h = data_hot, w = data_warm, c = data_cold, s = data_content, e assim por diante.


4.5 - Laboratório 1: primários, réplicas e o status yellow

Os laboratórios deste capítulo usam o cluster lab-es e o Console do Kibana. Os comandos de terminal (kubectl) rodam na VM.

Acessando o Kibana

No terminal da VM, obtenha a senha do usuário elastic e abra o túnel até o Kibana:

PASSWORD=$(kubectl -n elastic get secret lab-es-es-elastic-user \
  -o go-template='{{.data.elastic | base64decode}}')
echo "Senha do elastic: $PASSWORD"

pkill -f "port-forward.*5601" || true
nohup kubectl -n elastic port-forward --address 0.0.0.0 service/lab-kb-kb-http 5601 > /tmp/pf-kibana.log 2>&1 &
sleep 2
cat /tmp/pf-kibana.log

O túnel fica ligado ao pod do Kibana que estava rodando quando ele foi aberto. Se esse pod for reiniciado ou recriado, o túnel cai e o Kibana deixa de responder no navegador: execute este bloco novamente.

No navegador, abra https://<IP_DA_VM>:5601, faça login com o usuário elastic e a senha exibida e abra o Console (Dev Tools no menu de navegação ou Console na busca global).

(OPCIONAL) Instalação feita via laboratório do repositório:

Caso a instalação tenha feito seguindo o Laboratório do repositório (00-preparacao-ambiente), seguir os passos abaixo para que os laboratórios da wiki não ocorra algum erro:

# 1) Remover o quickstart — libera ~5Gi de memória reservada
kubectl -n elastic delete kibana quickstart --ignore-not-found
kubectl -n elastic delete elasticsearch quickstart --ignore-not-found
kubectl -n elastic get elasticsearch,kibana     # deve restar só lab-es e lab-kb
kubectl -n elastic get pvc                      # deve restar só o do lab-es

# 2) Criar a pasta que a wiki usa, com os manifestos que estão rodando
mkdir -p ~/eck-lab/manifests
cp /opt/elastic-eck/02-deploy-elastic-stack-eck/manifests/elasticsearch.yaml \
   /opt/elastic-eck/02-deploy-elastic-stack-eck/manifests/kibana.yaml ~/eck-lab/manifests/
cd ~/eck-lab
kubectl diff -n elastic -f manifests/elasticsearch.yaml   # sem saída = idêntico ao cluster
kubectl diff -n elastic -f manifests/kibana.yaml

# 3) Conferir a versão do operator
helm list -n elastic-system                     # CHART eck-operator-3.4.1

Limpamos o ambiente quickstart da máquina para liberação de recursos, então copiamos os manifestos para o caminho usado por essa wiki e então comparamos com o manifesto vigente atualmente.

Passo 1 — Nodes e papéis

GET _cat/nodes?v&h=name,node.role,heap.percent,cpu,disk.used_percent,master

Passo 2 — Ver os shards de um índice existente

GET _cat/shards/kibana_sample_data_ecommerce?v

Passo 3 — Criar um índice com 3 primários e 1 réplica

PUT loja
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1
  }
}
GET _cat/shards/loja?v

Confira a saúde do índice:

GET _cluster/health/loja

Para ver o motivo de a réplica não ser alocada:

GET _cluster/allocation/explain
{
  "index": "loja",
  "shard": 0,
  "primary": false
}

Não corrija o índice agora: no próximo laboratório, as réplicas serão alocadas quando o cluster ganhar novos nodes.


4.6 - Laboratório 2: ampliando o lab-es para 3 nodes

No ECK, aumentar o cluster é acrescentar nodeSets ao manifesto e reaplicá-lo. Vamos transformar o lab-es em um cluster de 3 nodes, com o atributo datacenter em cada um, na mesma distribuição de um ambiente com dois datacenters:

Pod (node) nodeSet Papéis Atributo datacenter Memória / heap
lab-es-es-default-0 default todos (master, data, ingest...) datacenter1 4Gi / 2 GB
lab-es-es-node2-0 node2 data, ingest datacenter1 2Gi / 1 GB
lab-es-es-node3-0 node3 data, ingest datacenter2 2Gi / 1 GB

O lab-es, os dados, a senha do usuário elastic e o Kibana continuam os mesmos. O lab-es-es-default-0 segue como o único master.

Passo 1 — Conferir a memória disponível

Os dois nodes novos pedem 2Gi de memória cada. Confira quanto já está reservado no node do Kubernetes:

kubectl describe node eck-lab | grep -A 8 "Allocated resources"

Caso o hostname não tenha sido configurado na instalação:

kubectl describe node $(kubectl get nodes -o name) | grep -A 8 "Allocated resources"
# ou
kubectl describe node vmi3605242 | grep -A 8 "Allocated resources"

Resultado esperado: a linha memory mostra os requests já reservados (cerca de 5 a 6 GiB com o lab-es e o Kibana). Os 4Gi dos nodes novos precisam caber no que falta para a memória total da VM.

Passo 2 — Criar o manifesto do lab-es com 3 nodes

mkdir -p ~/eck-lab/manifests
cd ~/eck-lab

cat <<'EOF' > manifests/elasticsearch-3-nodes.yaml
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: lab-es
spec:
  version: 9.4.2
  nodeSets:
    - name: default
      count: 1
      config:
        node.store.allow_mmap: true
        node.attr.datacenter: datacenter1
      podTemplate:
        spec:
          containers:
            - name: elasticsearch
              env:
                - name: ES_JAVA_OPTS
                  value: -Xms2g -Xmx2g
              resources:
                requests:
                  memory: 4Gi
                  cpu: "1"
                limits:
                  memory: 4Gi
      volumeClaimTemplates:
        - metadata:
            name: elasticsearch-data
          spec:
            accessModes: [ReadWriteOnce]
            resources:
              requests:
                storage: 20Gi
            storageClassName: local-path
    - name: node2
      count: 1
      config:
        node.roles: ["data", "ingest"]
        node.store.allow_mmap: true
        node.attr.datacenter: datacenter1
      podTemplate:
        spec:
          containers:
            - name: elasticsearch
              env:
                - name: ES_JAVA_OPTS
                  value: -Xms1g -Xmx1g
              resources:
                requests:
                  memory: 2Gi
                  cpu: 500m
                limits:
                  memory: 2Gi
      volumeClaimTemplates:
        - metadata:
            name: elasticsearch-data
          spec:
            accessModes: [ReadWriteOnce]
            resources:
              requests:
                storage: 10Gi
            storageClassName: local-path
    - name: node3
      count: 1
      config:
        node.roles: ["data", "ingest"]
        node.store.allow_mmap: true
        node.attr.datacenter: datacenter2
      podTemplate:
        spec:
          containers:
            - name: elasticsearch
              env:
                - name: ES_JAVA_OPTS
                  value: -Xms1g -Xmx1g
              resources:
                requests:
                  memory: 2Gi
                  cpu: 500m
                limits:
                  memory: 2Gi
      volumeClaimTemplates:
        - metadata:
            name: elasticsearch-data
          spec:
            accessModes: [ReadWriteOnce]
            resources:
              requests:
                storage: 10Gi
            storageClassName: local-path
EOF

O que mudou em relação ao manifesto original do lab-es:

Trecho Mudança
nodeSets[default].config Ganhou node.attr.datacenter: datacenter1. Como a configuração do node mudou, o operator reinicia o pod lab-es-es-default-0
nodeSets[node2] Novo grupo com 1 node de dados e ingestão no datacenter1
nodeSets[node3] Novo grupo com 1 node de dados e ingestão no datacenter2

O node.attr.<nome> cria um atributo personalizado no node. É o equivalente a acrescentar a mesma linha no elasticsearch.yml de cada servidor em uma instalação tradicional.

Passo 3 — Aplicar o manifesto

kubectl apply -n elastic -f manifests/elasticsearch-3-nodes.yaml
kubectl -n elastic get pods -w

Resultado esperado: os pods lab-es-es-node2-0 e lab-es-es-node3-0 são criados e o lab-es-es-default-0 é reiniciado. Aguarde os três ficarem 1/1 Running e pressione Ctrl+C.

Enquanto o lab-es-es-default-0 reinicia, o cluster fica sem master e o Kibana mostra erros por um ou dois minutos. Isso é esperado: em produção, o cluster teria 3 masters e continuaria no ar.

Confira o recurso:

kubectl -n elastic get elasticsearch lab-es
kubectl -n elastic get statefulset,pvc | grep lab-es

Resultado esperado: HEALTH green e NODES 3; três StatefulSets (lab-es-es-default, lab-es-es-node2, lab-es-es-node3) e um PVC para cada pod.

NAME     HEALTH   NODES   VERSION   PHASE   AGE
lab-es   green    3       9.4.2     Ready   2d

Passo 4 — Ver os nodes e os atributos

No Console:

GET _cat/nodes?v&h=name,node.role,master
GET _cat/nodeattrs?v&h=node,attr,value

Passo 5 — Ver as réplicas do loja alocadas

GET _cat/shards/loja?v
GET _cluster/health/loja

Observe também que o Elasticsearch passou a distribuir os shards dos outros índices entre os três nodes:

GET _cat/allocation?v

4.7 - Laboratório 3: configurações de alocação de shards

Todos os comandos desta seção devem ser executados no Console do Kibana.

1. Alocação de shards em nível de cluster

Desativar a alocação de shards (útil durante manutenção):

PUT /_cluster/settings
{
  "persistent": {
    "cluster.routing.allocation.enable": "none"
  }
}

Com a alocação desativada, nenhum shard novo é alocado. Crie um índice e observe:

PUT teste-alocacao
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 1
  }
}
GET _cat/shards/teste-alocacao?v

Reativar a alocação de shards:

PUT /_cluster/settings
{
  "persistent": {
    "cluster.routing.allocation.enable": "all"
  }
}
GET _cat/shards/teste-alocacao?v

Sempre use persistent em vez de transient ao alterar configurações de cluster. Settings transient são fortemente desaconselhados a partir do Elasticsearch 8.x — se o cluster se tornar instável, eles podem ser perdidos, deixando o cluster em um estado inesperado.

2. Alocação baseada em disco

PUT /_cluster/settings
{
  "persistent": {
    "cluster.routing.allocation.disk.watermark.low": "85%",
    "cluster.routing.allocation.disk.watermark.high": "90%",
    "cluster.routing.allocation.disk.watermark.flood_stage": "95%"
  }
}

Significado de cada watermark:

  • low (85%): o node para de receber novos shards por realocação automática quando o uso de disco atinge esse valor.
  • high (90%): o Elasticsearch começa a mover shards existentes para nodes com mais espaço.
  • flood_stage (95%): a indexação é bloqueada nos índices alocados nesse node. O índice fica em modo read-only automaticamente.

Confira o uso de disco de cada node:

GET _cat/allocation?v

Para restaurar os valores padrão:

PUT /_cluster/settings
{
  "persistent": {
    "cluster.routing.allocation.disk.watermark.low": null,
    "cluster.routing.allocation.disk.watermark.high": null,
    "cluster.routing.allocation.disk.watermark.flood_stage": null
  }
}

3. Shard Allocation Awareness

Passo 1 — Verificar os atributos aplicados:

O atributo datacenter já foi definido no manifesto (Laboratório 2), no config de cada nodeSet:

GET /_cat/nodeattrs?v

Passo 2 — Ativar a consciência de alocação via API:

PUT /_cluster/settings
{
  "persistent": {
    "cluster.routing.allocation.awareness.attributes": "datacenter"
  }
}

O Elasticsearch passa a considerar o atributo datacenter na distribuição de shards — tentando garantir que primary e replica de um mesmo índice fiquem em datacenters diferentes.

Passo 3 — Ver a awareness funcionando:

GET _cat/shards/loja?v&h=index,shard,prirep,state,node

Passo 4 — Forçar a consciência de alocação:

Na awareness comum, se um datacenter inteiro cair, o Elasticsearch recria as cópias perdidas nos datacenters que sobraram. A awareness forçada reserva lugar para os datacenters informados: quando não é possível equilibrar, o Elasticsearch prefere deixar a réplica unassigned a colocá-la no mesmo datacenter que o primário. Isso evita que o datacenter que sobrou fique sobrecarregado com todas as cópias.

Para ver a diferença, crie um índice com 2 réplicas (3 cópias de cada shard):

PUT loja-force
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 2
  }
}
GET _cat/shards/loja-force?v&h=index,shard,prirep,state,node

Agora force a awareness informando três datacenters, sendo que o datacenter3 não existe (é como se ele tivesse caído):

PUT /_cluster/settings
{
  "persistent": {
    "cluster.routing.allocation.awareness.force.datacenter.values": "datacenter1,datacenter2,datacenter3"
  }
}
GET _cat/shards/loja-force?v&h=index,shard,prirep,state,node
GET _cluster/health/loja-force

Passo 5 — Desfazer as configurações de awareness:

PUT /_cluster/settings
{
  "persistent": {
    "cluster.routing.allocation.awareness.attributes": null,
    "cluster.routing.allocation.awareness.force.datacenter.values": null
  }
}
DELETE loja-force

E em produção? Com zonas de disponibilidade reais, a forma recomendada no ECK é o campo zoneAwareness do nodeSet, que distribui os pods entre as zonas e configura a awareness automaticamente.

4. Filtragem de alocação

Vamos retirar os shards do índice loja do node lab-es-es-node3-0:

PUT loja/_settings
{
  "index.routing.allocation.exclude._name": "lab-es-es-node3-0"
}
GET _cat/shards/loja?v&h=index,shard,prirep,state,node

Aqui usamos a filtragem no nível do índice (index.routing.allocation.exclude._name). A mesma configuração existe no nível do cluster (cluster.routing.allocation.exclude._name), mas no ECK é o operator que a usa para migrar os dados antes de remover nodes; alterá-la à mão pode conflitar com o operator.

Verificar a distribuição atual de shards:

GET /_cat/allocation?v

Remover a restrição após o exercício:

PUT loja/_settings
{
  "index.routing.allocation.exclude._name": null
}

4.8 - Laboratório 4: segments e force merge

Passo 1 — Criar um índice e indexar documentos

PUT segments-lab
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 1
  }
}

Indexe 5 documentos com refresh=true, para forçar um refresh a cada documento. No Console, selecione as cinco linhas e execute todas de uma vez:

PUT segments-lab/_doc/1?refresh=true
{ "n": 1 }
PUT segments-lab/_doc/2?refresh=true
{ "n": 2 }
PUT segments-lab/_doc/3?refresh=true
{ "n": 3 }
PUT segments-lab/_doc/4?refresh=true
{ "n": 4 }
PUT segments-lab/_doc/5?refresh=true
{ "n": 5 }
GET _cat/segments/segments-lab?v&h=shard,prirep,segment,docs.count,docs.deleted,size

Passo 2 — Deletar um documento

DELETE segments-lab/_doc/1?refresh=true
GET _cat/segments/segments-lab?v&h=shard,prirep,segment,docs.count,docs.deleted,size

Passo 3 — Force merge

POST segments-lab/_forcemerge?max_num_segments=1
GET _cat/segments/segments-lab?v&h=shard,prirep,segment,docs.count,docs.deleted,size

Cuidado: force merge é caro em I/O. Em produção, faça só em índices que não recebem mais escrita, como índices antigos que já passaram para o tier warm ou cold.


4.9 - Laboratório 5: arquitetura Hot-Warm-Cold

Neste laboratório vamos configurar cada node do cluster com um tier diferente e verificar como o Elasticsearch direciona a alocação de shards com base nessa configuração. É a arquitetura usada pelo ILM:

Pod (node) nodeSet Tier Papéis
lab-es-es-default-0 default Hot master, data_hot, data_content, ingest, ml, remote_cluster_client, transform
lab-es-es-node2-0 node2 Warm data_warm
lab-es-es-node3-0 node3 Cold data_cold

O node hot precisa deixar de ter o papel genérico data. Esse papel vale para todos os tiers: com ele, o Elasticsearch poderia manter no hot um índice que deveria estar no warm ou no cold.

Passo 1 — Remover os índices de teste

Com um único node hot, índices com réplica não conseguiriam alocar a réplica em outro node do mesmo tier e ficariam yellow. Remova os índices de teste deste capítulo:

DELETE loja,teste-alocacao,segments-lab

Passo 2 — Criar o manifesto com os tiers

cd ~/eck-lab

cat <<'EOF' > manifests/elasticsearch-tiers.yaml
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: lab-es
spec:
  version: 9.4.2
  nodeSets:
    - name: default
      count: 1
      config:
        node.roles: ["master", "data_hot", "data_content", "ingest", "ml", "remote_cluster_client", "transform"]
        node.store.allow_mmap: true
        node.attr.datacenter: datacenter1
      podTemplate:
        spec:
          containers:
            - name: elasticsearch
              env:
                - name: ES_JAVA_OPTS
                  value: -Xms2g -Xmx2g
              resources:
                requests:
                  memory: 4Gi
                  cpu: "1"
                limits:
                  memory: 4Gi
      volumeClaimTemplates:
        - metadata:
            name: elasticsearch-data
          spec:
            accessModes: [ReadWriteOnce]
            resources:
              requests:
                storage: 20Gi
            storageClassName: local-path
    - name: node2
      count: 1
      config:
        node.roles: ["data_warm"]
        node.store.allow_mmap: true
        node.attr.datacenter: datacenter1
      podTemplate:
        spec:
          containers:
            - name: elasticsearch
              env:
                - name: ES_JAVA_OPTS
                  value: -Xms1g -Xmx1g
              resources:
                requests:
                  memory: 2Gi
                  cpu: 500m
                limits:
                  memory: 2Gi
      volumeClaimTemplates:
        - metadata:
            name: elasticsearch-data
          spec:
            accessModes: [ReadWriteOnce]
            resources:
              requests:
                storage: 10Gi
            storageClassName: local-path
    - name: node3
      count: 1
      config:
        node.roles: ["data_cold"]
        node.store.allow_mmap: true
        node.attr.datacenter: datacenter2
      podTemplate:
        spec:
          containers:
            - name: elasticsearch
              env:
                - name: ES_JAVA_OPTS
                  value: -Xms1g -Xmx1g
              resources:
                requests:
                  memory: 2Gi
                  cpu: 500m
                limits:
                  memory: 2Gi
      volumeClaimTemplates:
        - metadata:
            name: elasticsearch-data
          spec:
            accessModes: [ReadWriteOnce]
            resources:
              requests:
                storage: 10Gi
            storageClassName: local-path
EOF

A única diferença em relação ao elasticsearch-3-nodes.yaml é a linha node.roles de cada nodeSet. Em uma instalação tradicional, isso seria editar o node.roles no elasticsearch.yml de cada servidor e reiniciar os nodes um a um; no ECK, o operator faz o rolling restart sozinho.

Passo 3 — Aplicar o manifesto

kubectl apply -n elastic -f manifests/elasticsearch-tiers.yaml
kubectl -n elastic get pods -w

Resultado esperado: o operator reinicia os três pods, um de cada vez. Aguarde os três ficarem 1/1 Running e pressione Ctrl+C. Depois confira:

kubectl -n elastic get elasticsearch lab-es

Resultado esperado: HEALTH green e NODES 3.

Passo 4 — Verificar os papéis de cada node

GET /_cat/nodes?v&h=name,node.role,ip

Os índices que já existiam (dados de exemplo e índices dos capítulos anteriores) usam por padrão o tier data_content e passam a ficar todos no lab-es-es-default-0:

GET _cat/allocation?v

Passo 5 — Criação de índices e movimentação entre tiers

Criar um índice alocado no tier hot:

PUT /hot_index
{
  "settings": {
    "index.routing.allocation.include._tier_preference": "data_hot",
    "number_of_replicas": 0
  }
}

O índice é criado sem réplica porque cada tier do laboratório tem um único node.

Verificar onde os shards foram alocados:

GET /_cat/shards/hot_index?v

Mover o índice para o tier warm:

PUT /hot_index/_settings
{
  "index.routing.allocation.include._tier_preference": "data_warm"
}
GET /_cat/shards/hot_index?v

Mover o índice para o tier cold:

PUT /hot_index/_settings
{
  "index.routing.allocation.include._tier_preference": "data_cold"
}
GET /_cat/shards/hot_index?v

O lab-es fica com essa arquitetura de três tiers, que é a base do ILM. Se você não for continuar com o ILM, volte o cluster para um node seguindo a seção 4.10.


4.10 - (Opcional) Voltando o lab-es para um node

Faça esta seção só se precisar liberar memória na VM. Voltar para um node é reaplicar o manifesto original do lab-es: o operator migra os dados dos nodes node2 e node3 para o lab-es-es-default-0 e só depois remove os pods e os PVCs deles.

A migração só acontece se todos os shards couberem no node que fica. Por isso, antes de reaplicar o manifesto, os índices não podem ter réplicas nem estar presos aos tiers warm ou cold.

Passo 1 — Preparar os índices

Remova o índice de teste do tier cold e confira se sobrou algum índice com réplica:

DELETE hot_index
GET _cat/indices?v&h=index,health,pri,rep&s=rep:desc

Se algum índice seu tiver rep maior que 0, zere as réplicas dele (os índices de sistema, que começam com ., ajustam as réplicas sozinhos):

PUT <nome-do-indice>/_settings
{
  "index": { "number_of_replicas": 0 }
}

Confira também que não ficou nenhuma configuração de awareness ativa:

GET _cluster/settings?filter_path=persistent.cluster.routing

Não pode aparecer nenhuma configuração de awareness nem de exclude. O "allocation": { "enable": "all" } é o valor padrão e pode aparecer.

Passo 2 — Reaplicar o manifesto original

cd ~/eck-lab
kubectl apply -n elastic -f manifests/elasticsearch.yaml
kubectl -n elastic get pods -w

Resultado esperado: o operator reinicia o lab-es-es-default-0 (que volta a ter todos os papéis) e, depois de migrar os dados, remove os pods lab-es-es-node2-0 e lab-es-es-node3-0. Pressione Ctrl+C quando só restar o lab-es-es-default-0 com 1/1 Running.

Passo 3 — Verificar

kubectl -n elastic get elasticsearch lab-es
kubectl -n elastic get pvc | grep lab-es

Resultado esperado: NODES 1 e HEALTH green, e só o PVC elasticsearch-data-lab-es-es-default-0.

Se a redução não avançar (os pods node2 e node3 continuam Running depois de vários minutos), algum shard não consegue ir para o node que fica. Veja os eventos e o log do operator e confira o Passo 1:

kubectl -n elastic describe elasticsearch lab-es | sed -n '/Events/,$p'
kubectl -n elastic-system logs statefulset/elastic-operator | tail -20

Resumo do capítulo

  • Um índice é dividido em shards (cada um é um índice Lucene), e cada shard guarda os dados em segments imutáveis que são fundidos pelo merge.
  • O número de primários é fixo na criação do índice; o número de réplicas pode mudar a qualquer momento. Uma réplica nunca fica no mesmo node do seu primário, por isso um cluster de um node fica yellow com réplicas.
  • No ECK, node = pod, grupo de nodes = nodeSet, escalar = mudar count ou acrescentar nodeSets e disco = PVC. Atributos e papéis dos nodes ficam no config de cada nodeSet, e o operator aplica as mudanças com rolling restart.
  • A alocação de shards é controlada por configurações de cluster e de índice: habilitação, watermarks de disco, awareness por atributos (comum e forçada) e filtros.
  • O force merge reduz o número de segments e elimina documentos deletados, mas deve ser usado só em índices que não recebem mais escrita.
  • A arquitetura hot-warm-cold no ECK é um nodeSet por tier, e o _tier_preference do índice decide em qual tier os shards ficam.

Origem do conteúdo

4.1 - Índices, shards e segments

4.2 - Nodes, papéis e a tradução para o ECK

4.3 - Alocação e roteamento de shards

4.4 - APIs _cat

4.5 a 4.10 - Laboratórios

⚠️ **GitHub.com Fallback** ⚠️