Atelier fundamental 1 — Elasticsearch: um índice, um documento, uma consulta GET

Prática guiada13 min
Duração
20 min
Módulo
1/7
Pré-requisitos
o laboratório roda (etat exibe (healthy) em todos os lugares), Kibana Dev Tools aberto
Você vai construir
um índice seu, pratique-mini, com dois documentos que você vai ler, completar, buscar e depois excluir
Entregável
a resposta de GET pratique-mini/_doc/1 após a etapa 5, com seus três campos

Como ler esta página. Oito etapas, uma consulta por vez. Para cada uma: a consulta a digitar, a resposta exata do laboratório, e o que é preciso olhar nela. Digite você mesmo cada consulta (sem copiar e colar): é escrevendo PUT, GET, _doc que as palavras entram. Os blocos "Para entender bem" são opcionais; abra-os se uma etapa lhe deixar uma dúvida. Se o laboratório não estiver iniciado, volte à prática guiada: a seção Em resumo dá os comandos, kit incluído (https://github.com/hrhouma2/aiopsatlas-recherche-graphes-labo-fr).

Objetivo

A prática guiada fez você carregar 504 cursos, 609 avaliações e 12.000 linhas de log de uma vez, com um script. Você viu os números, mas ainda não escreveu nada por conta própria. Aqui, você recomeça do zero: um índice vazio que você cria, uma primeira ficha que você guarda dentro, que você relê, que você completa, depois uma segunda, uma busca, uma exclusão. No fim, você sabe o que é um índice e um documento porque fabricou um, não porque lhe disseram.

O vocabulário em uma imagem

Um índice é um armário. Um documento é uma ficha guardada no armário: um pequeno texto JSON com campos. Cada ficha tem um número, seu _id, que permite reencontrá-la diretamente sem buscar. Você não precisa declarar as colunas de antemão: a primeira ficha guardada cria o mapping (a planta do armário) sozinha.

ElasticsearchBanco SQL clássicoNesta prática
índicetabelapratique-mini
documentolinha{"titre": "Mon premier document"}
campocolunatitre, auteur, note
_idchave primária1, 2
mappingesquema da tabela (CREATE TABLE …)criado automaticamente na etapa 3
_sourcea linha tal como você a escreveuo que GET _doc/1 lhe devolve

Onde digitar, e como ler uma consulta

Abra http://localhost:5601, menu ☰ → ManagementDev Tools. O painel da esquerda recebe as consultas, o da direita exibe a resposta. Você envia com Ctrl + Enter (Cmd + Enter no macOS) ou o botão ▶ à direita da linha.

Toda consulta tem a mesma forma: um verbo, um caminho, e às vezes um corpo JSON embaixo.

VerboO que fazEquivalente SQL
GETler, sem mudar nadaSELECT
PUTcriar, ou substituir inteiramenteCREATE TABLE, INSERT (ou substituir a linha)
POSTagir: atualizar, buscar com um corpoUPDATE
DELETEexcluirDROP TABLE, DELETE

O caminho diz sobre o que agimos: pratique-mini (o armário), pratique-mini/_doc/1 (a ficha número 1 do armário), pratique-mini/_search (buscar no armário). As palavras que começam por _ são comandos do Elasticsearch, não nomes seus.

Etapa 1 — Criar o armário, vazio

text
PUT pratique-mini

O que a consulta pede: crie um índice que se chama pratique-mini. Nada mais: sem colunas, sem conteúdo.

json
{
  "acknowledged": true,
  "shards_acknowledged": true,
  "index": "pratique-mini"
}

O que olhar: "acknowledged": true, "está feito", e o nome em eco. O selo no alto à direita da resposta diz 200 - OK.

Para entender bem
  • Por que pratique- na frente? Todos os índices que você cria neste curso carregam esse prefixo. Assim GET _cat/indices/pratique-*?v lista tudo o que é seu e nada mais, e cours, avis, acces permanecem intocados.
  • Um nome de índice é em minúsculas, sem espaço nem maiúscula nem /. Pratique-Mini seria recusado.
  • Uma segunda vez a mesma consulta responde 400 com resource_already_exists_exception: o armário já existe. Não é uma falha, é uma resposta.

Etapa 2 — Vê-lo, e corrigir sua cor

text
GET _cat/indices/pratique-mini?v

O que a consulta pede: uma linha de resumo sobre este índice, com a linha de cabeçalho (?v, verbose).

text
health status index         uuid                   pri rep docs.count docs.deleted store.size pri.store.size dataset.size
yellow open   pratique-mini YHjAfGXBTwSZcl7CLcc2jw   1   1          0            0       227b           227b         227b

O que olhar: docs.count 0, o armário está vazio. E health yellow com rep 1: o Elasticsearch previu uma cópia de segurança (uma réplica) do seu índice em uma segunda máquina, e o laboratório só tem uma. A cópia não pode ser colocada em nenhum lugar, daí o amarelo. Os três índices do kit são verdes porque seu mapping fixa number_of_replicas: 0. Faça o mesmo, em uma consulta:

text
PUT pratique-mini/_settings
{
  "index": { "number_of_replicas": 0 }
}
json
{
  "acknowledged": true
}

Redigite GET _cat/indices/pratique-mini?v:

text
health status index         uuid                   pri rep docs.count docs.deleted store.size pri.store.size dataset.size
green  open   pratique-mini YHjAfGXBTwSZcl7CLcc2jw   1   0          0            0       227b           227b         227b

O que olhar: green, rep 0. Seu uuid será diferente: é o identificador interno do índice, sorteado na criação.

Para entender bem
  • Amarelo não é quebrado. Um índice amarelo se lê e se escreve normalmente. É um aviso: "a cópia de segurança que você pediu não existe". Em uma única máquina, ela não pode existir.
  • Enquanto seu índice estava amarelo, GET _cluster/health dizia "status": "yellow" para todo o cluster: a cor do cluster é a pior cor dos seus índices. É a explicação da falha "yellow" do catálogo da lição 04.
  • GET pratique-mini (sem _cat) devolve a ficha completa do índice: "mappings": { } (vazio, nenhuma ficha guardada) e "settings" com number_of_replicas.

Etapa 3 — Guardar uma primeira ficha

text
PUT pratique-mini/_doc/1
{
  "titre": "Mon premier document"
}

O que a consulta pede: no armário pratique-mini, guarde uma ficha (_doc) número 1 que contém um campo titre.

json
{
  "_index": "pratique-mini",
  "_id": "1",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 1,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 0,
  "_primary_term": 1
}

O que olhar: "result": "created" e "_version": 1: primeira versão da ficha 1. Equivalente SQL: INSERT INTO pratique_mini (id, titre) VALUES (1, 'Mon premier document'), exceto que nenhum CREATE TABLE foi necessário.

Para entender bem
  • O mapping acabou de nascer. Digite GET pratique-mini/_mapping: o campo titre está agora declarado do tipo text (para buscar palavras dentro) com um subcampo titre.keyword (para ordenar ou filtrar pelo valor exato). O Elasticsearch o deduziu do valor "Mon premier document", uma string.
  • O 1 de _doc/1 foi você quem escolheu. Os documentos do kit fazem o mesmo (C0001, A00001…). Se você escrever POST pratique-mini/_doc sem número, o Elasticsearch inventa um _id de vinte caracteres; prático para logs, incômodo para uma ficha que você quer reencontrar à mão.
  • _shards, _seq_no, _primary_term são contabilidade interna (em quantos pedaços a escrita foi confirmada, qual número de ordem). Você não precisa deles neste curso.

Etapa 4 — Reler a ficha

text
GET pratique-mini/_doc/1

O que a consulta pede: dê-me a ficha número 1 de pratique-mini, diretamente, sem buscar.

json
{
  "_index": "pratique-mini",
  "_id": "1",
  "_version": 1,
  "_seq_no": 0,
  "_primary_term": 1,
  "found": true,
  "_source": {
    "titre": "Mon premier document"
  }
}

O que olhar: "found": true, e _source, sua ficha tal como você a escreveu, caractere por caractere. Equivalente SQL: SELECT * FROM pratique_mini WHERE id = 1.

Experimente uma ficha que não existe: GET pratique-mini/_doc/3.

json
{
  "_index": "pratique-mini",
  "_id": "3",
  "found": false
}

Sem erro, sem _source: "found": false, selo 404 - Not Found. O Elasticsearch entendeu a pergunta; a resposta é "não há nada nesse número".

Etapa 5 — Acrescentar valores à ficha

text
POST pratique-mini/_update/1
{
  "doc": {
    "auteur": "Alice",
    "note": 5
  }
}

O que a consulta pede: atualize (_update) a ficha 1 acrescentando a ela estes dois campos. O que não é mencionado (titre) permanece como está.

json
{
  "_index": "pratique-mini",
  "_id": "1",
  "_version": 2,
  "result": "updated",
  "_shards": {
    "total": 1,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 1,
  "_primary_term": 1
}

O que olhar: "result": "updated", "_version": 2. Releia a ficha com GET pratique-mini/_doc/1:

json
{
  "_index": "pratique-mini",
  "_id": "1",
  "_version": 2,
  "_seq_no": 1,
  "_primary_term": 1,
  "found": true,
  "_source": {
    "titre": "Mon premier document",
    "note": 5,
    "auteur": "Alice"
  }
}

Três campos. O título continua lá. Equivalente SQL: UPDATE pratique_mini SET auteur = 'Alice', note = 5 WHERE id = 1, com a diferença de que em SQL as colunas auteur e note deveriam ter existido antes. Esta é a sua resposta-entregável: guarde-a.

Para entender bem
  • A palavra doc no corpo quer dizer "eis os campos a fundir". Sem ela, _update não sabe o que fazer.
  • O mapping cresceu. GET pratique-mini/_mapping mostra agora auteur (text + keyword, como titre) e note do tipo long, um inteiro. O Elasticsearch adivinhou o tipo a partir de 5. Se você tivesse escrito "note": "5" entre aspas, ele teria declarado texto, e você não poderia mais calcular uma média sobre ele. É o assunto da lição sobre o mapping, no módulo 2.
  • _version conta as escritas nesta ficha, não as leituras: GET nunca o incrementa.

Etapa 6 — A armadilha: PUT substitui tudo

Reenvie exatamente a consulta da etapa 3:

text
PUT pratique-mini/_doc/1
{
  "titre": "Mon premier document"
}
json
{
  "_index": "pratique-mini",
  "_id": "1",
  "_version": 3,
  "result": "updated",
  "_shards": {
    "total": 1,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 3,
  "_primary_term": 1
}

O que olhar: "result": "updated" (não created: a ficha 1 existia) e "_version": 3. Depois releia-a:

json
{
  "_index": "pratique-mini",
  "_id": "1",
  "_version": 3,
  "_seq_no": 3,
  "_primary_term": 1,
  "found": true,
  "_source": {
    "titre": "Mon premier document"
  }
}

auteur e note desapareceram. PUT _doc/1 não modifica a ficha 1: ele a substitui pelo que você envia. Para completar sem perder, é POST _update/1 com doc. Guarde a regra com os dois verbos: PUT substitui, _update completa. Recoloque os dois campos com a consulta da etapa 5 antes de continuar (você obtém _version: 4).

Etapa 7 — Uma segunda ficha, contar, buscar

text
PUT pratique-mini/_doc/2
{
  "titre": "Deuxième document, écrit par Bob",
  "auteur": "Bob",
  "note": 3
}

Resposta: "_id": "2", "result": "created", "_version": 1. Depois conte:

text
GET pratique-mini/_count
json
{
  "count": 2,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  }
}

Equivalente SQL: SELECT COUNT(*) FROM pratique_mini. Agora, veja tudo o que há dentro:

text
GET pratique-mini/_search

O que a consulta pede: busque em pratique-mini, sem critério, portanto tudo.

json
{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 2,
      "relation": "eq"
    },
    "max_score": 1.0,
    "hits": [
      {
        "_index": "pratique-mini",
        "_id": "2",
        "_score": 1.0,
        "_source": {
          "titre": "Deuxième document, écrit par Bob",
          "auteur": "Bob",
          "note": 3
        }
      },
      {
        "_index": "pratique-mini",
        "_id": "1",
        "_score": 1.0,
        "_source": {
          "titre": "Mon premier document",
          "note": 5,
          "auteur": "Alice"
        }
      }
    ]
  }
}

O que olhar: hits.total.value: 2 (quantas fichas respondem) e depois hits.hits, a lista das fichas, cada uma com seu _id e seu _source. A ordem das duas pode variar: sem critério, todas têm o mesmo _score de 1.0. Equivalente SQL: SELECT * FROM pratique_mini.

Por fim, busque uma palavra:

text
GET pratique-mini/_search
{
  "query": {
    "match": {
      "titre": "premier"
    }
  }
}

O que a consulta pede: as fichas cujo campo titre contém a palavra premier.

json
{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 0.3788134,
    "hits": [
      {
        "_index": "pratique-mini",
        "_id": "1",
        "_score": 0.3788134,
        "_source": {
          "titre": "Mon premier document",
          "note": 5,
          "auteur": "Alice"
        }
      }
    ]
  }
}

O que olhar: uma única ficha, a 1, e um _score que não é mais 1.0: é a relevância, "até que ponto esta ficha responde à pergunta". O módulo 3 é dedicado a esse número. Equivalente SQL aproximado: SELECT * FROM pratique_mini WHERE titre LIKE '%premier%', exceto que match encontraria também Premier com maiúscula, e o módulo 2 explicará por quê.

Para entender bem
  • _count devolve 0 ou 1 logo após uma escrita? O Elasticsearch torna as novas fichas visíveis à busca a cada segundo, não no mesmo instante. Reexecute _count: está atualizado. GET _doc/1, por sua vez, é sempre imediato porque não busca, vai direto ao número. Se você quiser forçar a visibilidade imediata em um teste: PUT pratique-mini/_doc/2?refresh=true.
  • took é o tempo da busca em milissegundos. timed_out: false: ela terminou dentro do prazo.
  • Por que GET com um corpo? É uma particularidade do Elasticsearch: a busca é uma leitura, portanto GET, mas a pergunta cabe em um corpo JSON. POST pratique-mini/_search com o mesmo corpo também funciona; os dois são aceitos.

Etapa 8 — Excluir uma ficha, depois o armário

text
DELETE pratique-mini/_doc/2
json
{
  "_index": "pratique-mini",
  "_id": "2",
  "_version": 2,
  "result": "deleted",
  "_shards": {
    "total": 1,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 4,
  "_primary_term": 1
}

O que olhar: "result": "deleted". GET pratique-mini/_count devolve 1 (após um segundo). Equivalente SQL: DELETE FROM pratique_mini WHERE id = 2.

Depois exclua o armário inteiro, fichas incluídas:

text
DELETE pratique-mini
json
{
  "acknowledged": true
}

Prova de que ele não existe mais, GET pratique-mini/_doc/1:

json
{
  "error": {
    "root_cause": [
      {
        "type": "index_not_found_exception",
        "reason": "no such index [pratique-mini]",
        "resource.type": "index_or_alias",
        "resource.id": "pratique-mini",
        "index_uuid": "_na_",
        "index": "pratique-mini"
      }
    ],
    "type": "index_not_found_exception",
    "reason": "no such index [pratique-mini]",
    "resource.type": "index_or_alias",
    "resource.id": "pratique-mini",
    "index_uuid": "_na_",
    "index": "pratique-mini"
  },
  "status": 404
}

O que olhar: a diferença em relação à etapa 4. Ficha ausente em um armário presente: "found": false, sem erro. Armário ausente: index_not_found_exception, 404. Os dois são respostas normais de um serviço que roda. Equivalente SQL: DROP TABLE pratique_mini.

Verificação final

text
GET _cat/indices/pratique-*?v

Resposta esperada: só a linha de cabeçalho. Nada seu ficou para trás no cluster, e GET _cat/indices/cours,avis,acces?v mostra ainda 504, 609, 12000.

  • Você criou pratique-mini e sabe por que ele estava amarelo, depois verde.
  • Você guardou a ficha 1 com PUT _doc/1 e a releu com GET _doc/1.
  • Você acrescentou auteur e note com POST _update/1 sem perder titre.
  • Você viu PUT _doc/1 apagar os dois campos, e sabe dizer a regra: PUT substitui, _update completa.
  • _count disse 2, _search listou as duas fichas, match só guardou uma.
  • Você excluiu a ficha 2 e depois o índice, e pratique-* está vazio.
  • Você guardou a resposta de GET pratique-mini/_doc/1 com três campos (etapa 5) como entregável.

Se travar

Mostrar os casos frequentes
  • 400 com Unexpected character ou was expecting double-quote to start field name → o JSON do corpo está malformado: cada nome de campo e cada texto entre aspas duplas ", uma vírgula entre os campos, nenhuma vírgula após o último. O Dev Tools sublinha o lugar.
  • 400 com resource_already_exists_exception em PUT pratique-mini → o índice já existe (você reexecutou a etapa 1). Continue na etapa 2, ou DELETE pratique-mini para recomeçar do zero.
  • 400 com no handler found for uri → erro de digitação em uma palavra com _: _serch, _doc/ esquecido, _udpate. O Elasticsearch valida o caminho antes de tudo o mais.
  • 405 com Incorrect HTTP method for uri [/pratique-mini/_update/1] and method [GET], allowed: [POST] → verbo errado para este caminho. A mensagem diz ela mesma qual é aceito.
  • 400 com [UpdateRequest] unknown field [titre] → você enviou os campos diretamente a _update, sem envolvê-los em "doc": { … }. Acrescente o envelope.
  • _count ou _search não veem a ficha que você acabou de escrever → aguarde um segundo e reexecute (veja "Para entender bem" da etapa 7). GET _doc/1 a vê imediatamente.
  • "result": "noop" em _update → os valores enviados já eram os da ficha; nada a mudar, _version não se moveu. Não é um erro.
  • "status": "yellow" persiste em GET _cluster/health após a etapa 2 → outro índice seu ainda tem rep 1. GET _cat/indices?v&health=yellow o aponta; aplique a ele o mesmo _settings, ou exclua-o.
  • O Dev Tools exibe "Kibana server is not ready yet" → o Kibana reinicia ou aguarda o Elasticsearch; .\labo.ps1 etat ou ./labo.sh etat, depois a lição 04.