Prática guiada — Iniciar, verificar, quebrar e reparar o laboratório

Prática guiada64 min
Duração
45 a 60 min
Módulo
1/7
Você vai construir
um laboratório carregado, verificado por três caminhos (script, Dev Tools, Neo4j Browser), depois voluntariamente quebrado e reparado
Entregável
a saída completa de etat com (healthy) em todos os lugares, 504 / 609 / 12000 documentos e 872 nós, mais duas linhas explicando a falha que você provocou

Como ler esta página. Cada seção está recolhida sob seu título: clique em "Mostrar …" para abri-la, e feche-a quando terminar para manter a página legível. Ordem de leitura: Objetivo, depois Em resumo (os comandos a digitar), depois O conjunto de dados (a ler antes de qualquer consulta), depois as consultas Elasticsearch e Neo4j, classificadas da mais simples (GET _cat/indices, MATCH (n) RETURN n) à mais impressionante, com uma explicação após cada uma. O passo a passo detalhado, com a saída esperada de cada comando e as falhas a provocar, está em anexo: anexo A para Windows (PowerShell), anexo B para Linux, macOS, WSL 2 e Git Bash. Abra um único anexo, o do seu sistema. O anexo C, comum, reúne os casos em que algo trava.

Objetivo

Você entra na equipe que constrói o motor de busca da plataforma de cursos online. Sua chefe lhe entrega o kit do laboratório: "Amanhã de manhã, quero um laboratório rodando na sua estação, os dados carregados, e a certeza de que você sabe consertá-lo sem me chamar." Você vai então iniciar a stack, provar que os três motores contêm o que devem, depois parar um serviço de propósito para ver como a falha se lê no etat, no navegador e no log, e colocá-lo de volta em funcionamento. Reconhecer "este serviço está parado" em dez segundos é o que evita horas de busca no lugar errado.

As nove etapas deste esquema são detalhadas, com a saída esperada de cada comando, no anexo A (Windows) ou no anexo B (Linux, macOS) no fim da página.

Em resumo: os comandos do lab

Mostrar os comandos

Kit do laboratório: https://github.com/hrhouma2/aiopsatlas-recherche-graphes-labo-fr

Você clona o kit em uma pasta lab1, verifica que o Docker está pronto, inicia os três serviços (Elasticsearch, Kibana, Neo4j), abre suas três páginas web, depois carrega os dados. Você executa importer e charger-graphe duas vezes: a segunda não deve mudar nada nos contadores, é a prova de que o carregamento é reexecutável sem duplicata. No fim, etat deve exibir (healthy) em todos os lugares, acces 12000 avis 609 cours 504 e nœuds : 872. Comece executando este bloco.

Windows (PowerShell)

powershell
git clone https://github.com/hrhouma2/aiopsatlas-recherche-graphes-labo-fr.git lab1
cd lab1
ls                       # explorer le contenu : docker-compose.yml, labo.ps1, labo.sh, elasticsearch/, neo4j/, outils/
.\labo.ps1 prerequis
.\labo.ps1 demarrer
.\labo.ps1 etat

Verificar as três URLs no navegador:

text
Kibana           http://localhost:5601   (Dev Tools : menu ☰ → Management → Dev Tools)
Elasticsearch    http://localhost:9200
Neo4j Browser    http://localhost:7474   (utilisateur neo4j · mot de passe aiopsatlas2026)
powershell
.\labo.ps1 importer
.\labo.ps1 charger-graphe
.\labo.ps1 importer          # 2e passage : mêmes compteurs, rien ne double
.\labo.ps1 charger-graphe
.\labo.ps1 etat              # attendu : acces 12000  avis 609  cours 504  nœuds : 872

Se o PowerShell recusar .\labo.ps1 («l'exécution de scripts est désactivée», a execução de scripts está desabilitada): Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, responda O, reexecute.

Linux, macOS, WSL 2, Git Bash

bash
git clone https://github.com/hrhouma2/aiopsatlas-recherche-graphes-labo-fr.git lab1
cd lab1
ls                       # explorer le contenu : docker-compose.yml, labo.sh, labo.ps1, elasticsearch/, neo4j/, outils/
./labo.sh prerequis
./labo.sh demarrer
./labo.sh etat

Verificar as três URLs no navegador:

text
Kibana           http://localhost:5601   (Dev Tools : menu ☰ → Management → Dev Tools)
Elasticsearch    http://localhost:9200
Neo4j Browser    http://localhost:7474   (utilisateur neo4j · mot de passe aiopsatlas2026)
bash
./labo.sh importer
./labo.sh charger-graphe
./labo.sh importer           # 2e passage : mêmes compteurs, rien ne double
./labo.sh charger-graphe
./labo.sh etat               # attendu : acces 12000  avis 609  cours 504  nœuds : 872

O conjunto de dados: o que você vai manipular

Mostrar o conjunto de dados

Antes de digitar uma única consulta, olhe os dados. Todo o laboratório gira em torno de uma plataforma de cursos online fictícia: um catálogo de cursos, as avaliações deixadas pelos estudantes, o log do servidor web que serve as páginas, e os vínculos entre estudantes, professores, cursos e competências. Os mesmos dados são carregados no Elasticsearch (para buscar) e no Neo4j (para seguir os vínculos). Eles estão no kit, em texto claro, em duas pastas:

text
lab1/
├── elasticsearch/donnees/        ← ce qui va dans Elasticsearch (3 fichiers NDJSON)
│   ├── cours.ndjson                 504 cours
│   ├── avis.ndjson                  609 avis
│   └── acces.ndjson              12 000 lignes de journal web
└── neo4j/import/                 ← ce qui va dans Neo4j (8 fichiers CSV)
    ├── cours.csv                    504 cours          → nœuds Cours
    ├── etudiants.csv                300 étudiants      → nœuds Etudiant
    ├── professeurs.csv               30 professeurs    → nœuds Professeur
    ├── competences.csv               22 compétences    → nœuds Competence
    ├── villes.csv                    16 villes         → nœuds Ville
    ├── inscriptions.csv           1 654 inscriptions   → relations INSCRIT_A
    ├── couvre.csv                   994 liens          → relations COUVRE
    └── prerequis.csv                230 liens          → relations PREREQUIS_DE

Abra-os você mesmo, leva dez segundos e você saberá exatamente o que está manipulando:

powershell
# Windows (PowerShell), depuis le dossier lab1
Get-Content elasticsearch\donnees\cours.ndjson -TotalCount 2
Get-Content elasticsearch\donnees\avis.ndjson  -TotalCount 2
Get-Content elasticsearch\donnees\acces.ndjson -TotalCount 2
Get-Content neo4j\import\cours.csv -TotalCount 3
Get-Content neo4j\import\inscriptions.csv -TotalCount 3
bash
# Linux, macOS, WSL 2, Git Bash, depuis le dossier lab1
head -n 2 elasticsearch/donnees/cours.ndjson
head -n 2 elasticsearch/donnees/avis.ndjson
head -n 2 elasticsearch/donnees/acces.ndjson
head -n 3 neo4j/import/cours.csv
head -n 3 neo4j/import/inscriptions.csv

Do lado do Elasticsearch: três índices, três tipos de documentos

Um arquivo .ndjson (newline-delimited JSON) contém um objeto JSON por linha. No kit, as linhas vêm em pares: uma linha de instrução ("guarde o que segue no índice cours sob o identificador C0001"), depois o próprio documento. É o formato que o comando importer envia ao Elasticsearch em bloco.

text
{"index":{"_index":"cours","_id":"C0001"}}
{"id":"C0001","titre":"Docker expliqué simplement","description":"…","categorie":"DevOps", … }

O primeiro documento, tal como está no Elasticsearch:

json
{
  "id": "C0001",
  "titre": "Docker expliqué simplement",
  "description": "Dans ce cours accessible sans prérequis, vous apprenez à automatiser vos applications avec Docker. …",
  "categorie": "DevOps",
  "sujet": "Docker",
  "niveau": "debutant",
  "langue": "en",
  "prix": 129,
  "gratuit": false,
  "duree_heures": 5,
  "tags": ["docker", "linux", "helm", "devops"],
  "date_publication": "2024-08-14",
  "note_moyenne": 4.4,
  "nb_avis": 327,
  "professeur": { "id": "P001", "nom": "Karim Caron", "ville": "Gatineau" },
  "competences": ["Conteneurisation", "Intégration continue"]
}
CampoExemploO que é
idC0001Identificador do curso. C de curso (cours), depois um número. É também o _id do documento.
titreDocker expliqué simplementO título. Texto livre, é sobre ele que faremos as buscas por palavras.
descriptionDans ce cours…Um parágrafo de apresentação. Texto livre também.
categorieDevOpsUma das seis grandes famílias: Cloud, DevOps, Données, Développement web, IA, Sécurité. 84 cursos cada.
sujetDockerMais preciso que a categoria: Docker, Kubernetes, Neo4j, Elasticsearch…
niveaudebutantdebutant, intermediaire ou avance.
langueenfr ou en.
prix129Em dólares. 0 para um curso gratuito.
gratuitfalseVerdadeiro ou falso.
duree_heures5Duração total do curso.
tags["docker", "linux", …]Uma lista de palavras-chave. Um campo pode conter vários valores.
date_publication2024-08-14Uma data.
note_moyenne4.4Média das notas recebidas, de 5.
nb_avis327Número de avaliações recebidas.
professeur{ "id": "P001", "nom": …, "ville": … }Um objeto dentro do objeto: o professor é descrito diretamente na ficha do curso, com seu identificador, seu nome e sua cidade.
competences["Conteneurisation", …]Lista das competências que o curso cobre.

Índice avis: 609 documentos, um por avaliação deixada por um estudante

json
{
  "id": "A00001",
  "cours_id": "C0028",
  "etudiant": "Nathan",
  "ville": "Sherbrooke",
  "pays": "Canada",
  "note": 3,
  "texte": "Les vidéos sont bonnes, la partie théorique est dense.",
  "date": "2024-06-21",
  "utile": 33
}
CampoExemploO que é
idA00001Identificador da avaliação. A de avaliação (avis).
cours_idC0028O curso em questão. É o vínculo com o índice cours: esse C0028 é o id de um documento de cours.
etudiantNathanPrimeiro nome do autor.
ville, paysSherbrooke, CanadaDe onde ele escreve.
note3A nota dada, de 1 a 5.
texteLes vidéos sont bonnes…O comentário. Texto livre.
date2024-06-21Data da avaliação.
utile33Número de pessoas que acharam esta avaliação útil.

Índice acces: 12.000 documentos, uma linha por requisição recebida pelo servidor web

json
{
  "id": "L000001",
  "@timestamp": "2026-08-10T08:17:19.000Z",
  "methode": "GET",
  "chemin": "/robots.txt",
  "cours_id": null,
  "categorie": null,
  "statut": 200,
  "octets": 108506,
  "duree_ms": 100,
  "ip": "108.190.166.1",
  "pays": "CA",
  "appareil": "desktop",
  "navigateur": "Edge",
  "referent": "google"
}
CampoExemploO que é
idL000001Identificador da linha. L de linha de log.
@timestamp2026-08-10T08:17:19.000ZData e hora exatas da requisição. O @ é uma convenção: é o campo de tempo que o Kibana detecta automaticamente.
methodeGETGET ou POST.
chemin/robots.txtO endereço solicitado no site: /, /cours, /catalogue, /contact, /tarifs, ou a página de um curso.
cours_idnull ou C0042O curso consultado, se a página se refere a um; null (vazio) caso contrário, como aqui para /robots.txt. Vínculo com o índice cours.
categorienull ou DevOpsCategoria do curso consultado, copiada para simplificar os gráficos.
statut200O código HTTP da resposta: 200 OK, 301 e 304 redirecionamento ou cache, 404 não encontrado, 500 e 503 erro de servidor.
octets108506Tamanho da resposta enviada.
duree_ms100Tempo de resposta em milissegundos.
ip108.190.166.1Endereço IP do visitante.
paysCAPaís do visitante, código de duas letras.
appareildesktopdesktop, mobile ou tablette.
navigateurEdgeChrome, Firefox, Safari, Edge…
referentgoogleDe onde vinha o visitante: direct, google, linkedin, youtube ou newsletter.

Os três índices estão ligados por cours_id: uma avaliação fala de um curso, uma linha de log consulta um curso. Mas o Elasticsearch não faz junção: cada índice é pesquisado separadamente. Para seguir os vínculos, passamos ao Neo4j.

Do lado do Neo4j: cinco tipos de nós, cinco tipos de relações

O Neo4j recebe os mesmos cursos (mesmos identificadores C0001…, mesmos títulos), mais o que não está no Elasticsearch: os estudantes, suas inscrições, os professores, as competências, as cidades, e os pré-requisitos entre cursos. O modelo cabe em um desenho:

As cinco setas se leem como frases: um estudante está inscrito em um curso; um professor ensina um curso; um curso cobre uma competência; um curso é pré-requisito de outro curso; um estudante ou um professor mora em uma cidade. No total, 872 nós e 3.712 relações.

Os dados chegam na forma de arquivos CSV clássicos, uma linha de cabeçalho e depois uma linha por elemento:

text
cours.csv          id,titre,categorie,sujet,niveau,prix,duree_heures,date_publication,professeur_id
                   C0001,Docker expliqué simplement,DevOps,Docker,debutant,129,5,2024-08-14,P001

etudiants.csv      id,prenom,nom,ville,pays,inscription_le,interet
                   E0001,Nathan,Ben Ali,Sherbrooke,Canada,2023-05-15,DevOps

professeurs.csv    id,prenom,nom,ville,pays,specialite,annees_experience
                   P001,Karim,Caron,Gatineau,Canada,DevOps,23

competences.csv    id,nom
                   K01,Conteneurisation

villes.csv         nom,pays,latitude,longitude
                   Montréal,Canada,45.5019,-73.5674

inscriptions.csv   etudiant_id,cours_id,date,progression,note
                   E0001,C0028,2024-04-01,100,3

couvre.csv         cours_id,competence_id
                   C0001,K01

prerequis.csv      prerequis_id,cours_id
                   C0001,C0003

Veja como isso se traduz em grafo. Os cinco primeiros arquivos se tornam nós; cada linha é um nó, cada coluna uma propriedade. Os três últimos se tornam relações; cada linha liga dois nós por seus identificadores. A linha E0001,C0028,2024-04-01,100,3 de inscriptions.csv se torna a seta (Etudiant E0001)-[:INSCRIT_A {date, progression, note}]->(Cours C0028): Nathan Ben Ali está inscrito no curso C0028, terminou-o a 100%, e lhe deu nota 3. Compare com a primeira avaliação do índice avis: é o mesmo Nathan, o mesmo curso C0028, a mesma nota 3.

PropriedadesExemplo
Coursid, titre, categorie, sujet, niveau, prix, duree_heures, date_publicationC0001, «Docker expliqué simplement», 129 $
Etudiantid, prenom, nom, interet, inscription_leE0001, Nathan Ben Ali, interesse DevOps
Professeurid, prenom, nom, specialite, annees_experienceP001, Karim Caron, DevOps, 23 anos
Competenceid, nomK01, Conteneurisation
Villenom, pays, latitude, longitudeMontréal, Canada
RelaçãoDe → paraPropriedadesSentido
INSCRIT_AEtudiant → Coursdate, progression, noteO estudante faz este curso
ENSEIGNEProfesseur → CoursEste professor dá este curso (um único por curso)
COUVRECours → CompetenceEste curso ensina esta competência
PREREQUIS_DECours → CoursÉ preciso ter feito o primeiro antes do segundo
HABITEEtudiant ou Professeur → VilleOnde a pessoa mora

Os identificadores, para se orientar

Todos os dados usam a mesma convenção: uma letra, depois um número. Quando você vê um identificador, sabe imediatamente do que se trata.

PrefixoCoisaExemploOnde se encontra
CCurso (cours)C0001Elasticsearch cours, avis.cours_id, acces.cours_id, Neo4j Cours
AAvaliação (avis)A00001Elasticsearch avis
LLinha de logL000001Elasticsearch acces
EEstudante (étudiant)E0001Neo4j Etudiant
PProfessorP001cours.professeur.id no Elasticsearch, Neo4j Professeur
KCompetênciaK01Neo4j Competence

Guarde um único fio condutor para todo o restante: o curso C0001, «Docker expliqué simplement», ensinado por P001, Karim Caron. Você o reencontrará no Elasticsearch (consulta E9), no Neo4j (consultas N7 e N13), e ele servirá para provar que os dois motores contêm de fato a mesma coisa.

Primeiras consultas: mostrar os dados, do mais simples ao mais impressionante

Os contadores de etat provam que os dados estão lá; as consultas abaixo os fazem ver. Elas são todas somente leitura: você pode reexecutá-las quantas vezes quiser, nada será modificado. Elas são idênticas no Windows e no Linux, tudo acontece no navegador.

A regra desta seção: uma única novidade por consulta. Começamos por "mostre-me o que há", sem nenhum parâmetro, e acrescentamos uma noção a cada passo. Após cada consulta, uma explicação curta; quando é preciso aprofundar, um bloco recolhível "Para entender bem". Cole as consultas uma por uma, na ordem, e leia a resposta antes de passar à seguinte.

Elasticsearch, no Kibana Dev Tools

Mostrar as 15 consultas Elasticsearch (E1 a E15)

Abra http://localhost:5601, depois menu ManagementDev Tools (ou «Outils de développement», Ferramentas de desenvolvimento). O painel da esquerda é um editor: cole uma consulta, coloque o cursor sobre ela, depois Ctrl+Enter ou o triângulo ▶. A resposta aparece à direita, com o código HTTP (200 - OK) embaixo.

Uma imagem para guardar na cabeça durante todo o restante: o Elasticsearch é um grande armário.

text
O armário   = Elasticsearch
Uma gaveta  = um índice          (a gaveta « cours », a gaveta « avis », a gaveta « acces »)
Uma ficha   = um documento       (uma ficha por curso, uma ficha por avaliação, uma ficha por linha de log)

_cat/indices  = ler as etiquetas coladas nas gavetas: nome, estado, número de fichas, espessura
_search       = abrir uma gaveta e ler as fichas que estão dentro

As consultas E1 a E4 olham as etiquetas das gavetas. A partir de E5, abrimos as gavetas. Nunca confunda as duas: é o erro número um dos iniciantes.

E1. Listar as gavetas

text
GET _cat/indices

O que a consulta pede: "Elasticsearch, mostre-me a lista de todas as suas gavetas." Resposta na máquina do curso:

text
green open avis                                            W9j_JrwJT4mdzpWcS5k7xg 1 0   609 0  74.6kb  74.6kb  74.6kb
green open .internal.alerts-security.alerts-default-000001 YJq3VGeRQKqD0fqJW6Fw3w 1 0     0 0    249b    249b    249b
green open acces                                           aii68fsfQXKyt5wqkE1mPA 1 0 12000 0   1.5mb   1.5mb   1.5mb
green open cours                                           pmq403ZgSZWeHJY9uNw1Qw 1 0   504 0 183.6kb 183.6kb 183.6kb

Quatro linhas = quatro gavetas. Três são suas: avis, acces, cours. A quarta, aquela cujo nome começa com um ponto (.internal.alerts-security…), não é sua: o Kibana a criou sozinho para seu uso interno (a gestão de alertas de segurança). Ela está vazia (0 fichas), não incomoda, não a exclua. No restante, vamos deixá-la de fora da exibição.

O que é incômodo aqui: há números por todos os lados e nenhum título de coluna. É como uma planilha Excel sem linha de cabeçalho. Corrigimos isso em E2.

Para ler a própria consulta, palavra por palavra:

PedaçoO que significa
GET"Eu quero ler algo." Não modifico nada, não excluo nada, só olho. Sem risco.
_cat"Responda-me em tabela de texto", legível por um humano, não em JSON. O sublinhado no início sinaliza um comando do próprio Elasticsearch, não um nome de índice.
indices"… a tabela dos índices." É o plural em inglês de index.
Para entender bem: o que é um índice, e ler uma linha valor por valor

Um índice é uma gaveta: um lugar onde se guardam fichas que se parecem. Todas as fichas "curso" vão na gaveta cours, todas as fichas "avaliação" na gaveta avis, e cada linha do log do servidor web é uma ficha da gaveta acces. Se você conhece SQL, o índice é o que o SQL chama de tabela:

ElasticsearchNa imagemSQL
Índice coursA gaveta "cours"Tabela cours
Documento JSONUma ficha na gavetaLinha
CampoUm quadro na ficha (título, preço…)Coluna
_id do documentoO número escrito no alto da fichaChave primária

As três gavetas do laboratório:

text
Elasticsearch
├── index cours  → 504 documents     (un document = un cours du catalogue)
├── index avis   → 609 documents     (un document = un avis laissé par un étudiant)
└── index acces  → 12 000 documents  (un document = une ligne de journal du serveur web)

Duas armadilhas de vocabulário. Em português, o singular é um índice e o plural é índices (o Elasticsearch escreve indices, o plural em inglês de index). E não é um "indício" no sentido policial: é um índice como aquele no fim de um livro, a lista que diz em qual página se encontra cada palavra. O Elasticsearch faz exatamente isso, em escala muito grande.

Agora, tomemos uma única linha da resposta E1 e vamos lê-la valor por valor, da esquerda para a direita:

text
green open avis W9j_JrwJT4mdzpWcS5k7xg 1 0 609 0 74.6kb 74.6kb 74.6kb
ValorO que significa
greenA gaveta está em bom estado. Verde = tudo bem.
openA gaveta está aberta: pode-se ler e escrever dentro. (O contrário seria close.)
avisO nome da gaveta.
W9j_JrwJT4mdzpWcS5k7xgUm número de série técnico, gerado pelo Elasticsearch. Você nunca vai usá-lo.
1A gaveta está em um único pedaço (um shard primário). Um índice grande pode ser dividido em vários pedaços distribuídos em várias máquinas; aqui, não.
0Zero cópia de segurança (replica). Normal em um laboratório de uma única máquina: uma cópia não teria para onde ir.
609A gaveta contém 609 fichas. É o valor que olhamos primeiro.
0Zero ficha marcada "para jogar fora" aguardando limpeza.
74.6kbEspaço total ocupado no disco.
74.6kbEspaço ocupado somente pelo pedaço primário (idêntico: há um só pedaço e nenhuma cópia).
74.6kbTamanho dos próprios dados.

Três vezes 74.6kb porque, com um único pedaço e zero cópia, as três maneiras de medir dão o mesmo resultado. Em um cluster de verdade com cópias, store.size seria maior que pri.store.size.

Em resumo, suas três gavetas:

text
avis   →    609 fiches  →  74,6 Ko
acces  → 12 000 fiches  →   1,5 Mo
cours  →    504 fiches  → 183,6 Ko

E2. A mesma lista, com os títulos das colunas: ?v

text
GET _cat/indices?v
text
health status index                                           uuid                   pri rep docs.count docs.deleted store.size pri.store.size dataset.size
green  open   avis                                            W9j_JrwJT4mdzpWcS5k7xg   1   0        609            0     74.6kb         74.6kb       74.6kb
green  open   .internal.alerts-security.alerts-default-000001 YJq3VGeRQKqD0fqJW6Fw3w   1   0          0            0       249b           249b         249b
green  open   acces                                           aii68fsfQXKyt5wqkE1mPA   1   0      12000            0      1.5mb          1.5mb        1.5mb
green  open   cours                                           pmq403ZgSZWeHJY9uNw1Qw   1   0        504            0    183.6kb        183.6kb      183.6kb

Mesma resposta que em E1, mais uma linha de títulos em cima. É tudo o que ?v faz. Agora você não precisa mais adivinhar: a coluna docs.count é o número de fichas (609, 0, 12000, 504), a coluna store.size é o espaço no disco.

?v quer dizer o quê, exatamente?

  • O ? diz: "o que segue são opções". Ele separa o comando (_cat/indices) dos seus ajustes.
  • O v é a opção verbose, "falante": "exiba também os títulos das colunas".

Portanto ?v = "responda-me com os cabeçalhos, para que eu entenda o que estou lendo". Adquira o hábito de colocá-lo sempre nos comandos _cat. Sem ?v, você tem números; com ?v, você tem informação.

Para entender bem: cada título de coluna, em uma frase
ColunaEm termos simples
healthSaúde da gaveta. green: tudo bem. yellow: as fichas estão lá, mas algumas cópias de segurança faltam. red: algumas fichas estão inacessíveis.
statusopen: utilizável. close: fechada, não se pode nem ler nem escrever.
indexO nome da gaveta.
uuidNúmero de série técnico. Nunca usado à mão.
priEm quantos pedaços (primary shards) a gaveta está dividida. 1 no laboratório.
repQuantas cópias de segurança (replicas) de cada pedaço. 0 no laboratório.
docs.countNúmero de fichas. A coluna a olhar primeiro.
docs.deletedFichas marcadas "para jogar fora" ainda não limpas do disco.
store.sizeEspaço total no disco.
pri.store.sizeEspaço somente dos pedaços primários, sem as cópias.
dataset.sizeTamanho dos próprios dados.

Por que tudo está green no laboratório: rep vale 0, portanto não há nenhuma cópia a fazer, portanto nenhuma cópia pode faltar. Em um cluster de verdade, rep 1 com uma única máquina daria yellow, porque a cópia não teria para onde ir.

E3. A etiqueta de uma única gaveta

text
GET _cat/indices/cours?v
text
health status index uuid                   pri rep docs.count docs.deleted store.size pri.store.size dataset.size
green  open   cours pmq403ZgSZWeHJY9uNw1Qw   1   0        504            0    183.6kb        183.6kb      183.6kb

O que a consulta pede: "Mostre-me a etiqueta da gaveta cours." Uma única novidade: o nome da gaveta acrescentado depois de _cat/indices/. Uma única linha em resposta, a de cours, com suas 504 fichas. O que vem depois da última / serve de filtro.

Faça o mesmo para as duas outras gavetas:

text
GET _cat/indices/avis?v
GET _cat/indices/acces?v

"Mostre-me a etiqueta da gaveta das avaliações": 609 em docs.count. "Mostre-me a etiqueta da gaveta dos logs de acesso": 12000. No Dev Tools, quando várias consultas são coladas em sequência, só a que está sob o cursor é enviada: coloque-o na linha desejada antes de Ctrl+Enter.

E4. O resumo limpo das três gavetas

text
GET _cat/indices/acces,avis,cours?v&s=index&h=index,health,docs.count,store.size
text
index health docs.count store.size
acces green       12000      1.5mb
avis  green         609     74.6kb
cours green         504    183.6kb

O que a consulta pede: "Mostre-me uma tabela resumida das gavetas acces, avis e cours. Ordene-as por nome, e exiba só o nome, a saúde, o número de fichas e o tamanho delas." Quatro linhas, quatro colunas, tudo o que é preciso e nada mais. É a consulta para manter à mão para verificar o laboratório em um olhar.

Ela parece complicada porque é longa, mas é só a E3 com três opções a mais. Decomposição, pedaço por pedaço:

PedaçoEm termos simples
GETPeço uma informação, não modifico nada.
_cat/indicesAs etiquetas das gavetas, em tabela legível.
/acces,avis,coursSomente estas três gavetas (nomes separados por vírgulas, sem espaço). A gaveta interna do Kibana desaparece.
?vCom os títulos das colunas.
&s=indexs de sort: ordena as linhas em ordem alfabética da coluna index.
&h=index,health,docs.count,store.sizeh de headers: exibe somente estas colunas, nesta ordem.

Uma regra, e uma só, para as opções: a primeira começa por ?, todas as seguintes por &. É por isso que se lê ?v&s=…&h=….

E as quatro colunas escolhidas:

ColunaEm termos simples
indexO nome da gaveta.
healthSua saúde: green tudo funciona; yellow as fichas existem mas cópias de segurança faltam; red algumas fichas estão indisponíveis.
docs.countQuantas fichas ela contém.
store.sizeQuanto espaço ocupa no disco.

Em uma frase: suas três gavetas estão abertas, com boa saúde, e contêm exatamente as fichas esperadas, 12.000, 609 e 504.

Para entender bem: por que esta é a consulta a memorizar

Sem h=, o Elasticsearch retorna onze colunas, a maioria das quais não lhe ensina nada no dia a dia. Sem s=, a ordem das linhas é arbitrária e muda de uma chamada para outra (veja E2: avis vinha antes de acces). Ao nomear as três gavetas, você também deixa de fora a do Kibana. O resultado cabe em quatro linhas e se compara num olhar com os números esperados. É exatamente o que o comando etat do kit faz nos bastidores.

Variante útil se você também quiser ver que as gavetas estão realmente abertas: acrescente status na lista h=:

text
GET _cat/indices/acces,avis,cours?v&s=index&h=index,health,status,docs.count,store.size
text
index health status docs.count store.size
acces green  open        12000      1.5mb
avis  green  open          609     74.6kb
cours green  open          504    183.6kb

Balanço de E1 a E4: você ainda não leu nenhuma ficha. Você só olhou as etiquetas nas gavetas. Você sabe que há 504 cursos, mas ainda não viu um único título de curso. É o que fazemos agora.

E5. Contar as fichas de uma gaveta

text
GET cours/_count
json
{
  "count": 504,
  "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }
}

O que a consulta pede: "Conte as fichas da gaveta cours." Resposta: 504. Observe bem a forma da consulta, ela é nova: não falamos mais com o armário (_cat/…), falamos com uma gaveta. O nome da gaveta vem primeiro (cours), depois o que queremos fazer com ela (_count), separados por uma /. Todas as consultas que seguem terão esta forma: nome-da-gaveta/_acao.

Outra mudança: a resposta não é mais uma tabela de texto, mas JSON, com chaves e aspas. É a forma normal das respostas do Elasticsearch; _cat era a exceção. Ignore a parte _shards, ela só diz "o pedaço da gaveta respondeu, nada falhou".

Experimente GET avis/_count (609) e GET acces/_count (12000). Em SQL: SELECT COUNT(*) FROM cours.

E6. Abrir a gaveta e ler as fichas

text
GET cours/_search

O que a consulta pede: "Abra a gaveta cours e mostre-me as fichas que estão dentro." É a primeira vez que você vê um curso de verdade: seu título, seu preço, seu professor.

A resposta é longa: é normal, ela contém dez fichas completas. O Elasticsearch lhe dá somente as 10 primeiras, mesmo que a gaveta contenha 504; é uma proteção, para não lhe enviar 504 fichas de uma vez sem que você tenha pedido. Olhe a estrutura em vez do conteúdo:

json
{
  "took": 1,
  "timed_out": false,
  "_shards": {  },
  "hits": {
    "total": { "value": 504, "relation": "eq" },
    "max_score": 1,
    "hits": [
      {
        "_index": "cours",
        "_id": "C0001",
        "_score": 1,
        "_source": {
          "id": "C0001",
          "titre": "Docker expliqué simplement",
          "categorie": "DevOps",
          "niveau": "debutant",
          "prix": 129,
          "tags": ["docker", "linux", "helm", "devops"],
          "professeur": { "id": "P001", "nom": "Karim Caron", "ville": "Gatineau" },

        }
      },
 9 autres documents
    ]
  }
}

_search quer dizer "busque"; sem outra precisão, quer dizer "dê-me fichas, quaisquer que sejam". Os dois números a localizar: "total": { "value": 504 } (há 504 fichas na gaveta) e a lista hits que só contém 10 (as que lhe mostram). Cada ficha está em _source, com todos os seus quadros: titre, prix, categorie

Faça o mesmo com as duas outras gavetas: GET avis/_search ("abra a gaveta avis, mostre-me as 10 primeiras avaliações") e GET acces/_search (as 10 primeiras linhas de log). Em SQL: SELECT * FROM cours LIMIT 10.

A diferença essencial, para nunca mais confundir.

GET _cat/indices/cours?v olha a etiqueta da gaveta: seu estado, seu número de fichas, seu tamanho. Uma linha. Você não vê nenhum curso.

GET cours/_search abre a gaveta e lê as fichas: títulos, preços, categorias, professores. Dez fichas. Você vê os próprios cursos.

A primeira responde "há 504 cursos". A segunda responde "eis alguns cursos".

Para entender bem: ler uma resposta de busca, chave por chave
ChaveEm termos simples
tookO tempo que o Elasticsearch levou, em milissegundos (aqui 1 ms).
hits.total.valueO número total de fichas que correspondem: 504. Mesmo que só lhe mostrem 10, ele diz quantas há no total.
hits.hitsA lista das fichas que lhe mostram: as 10 primeiras por padrão.
_indexA gaveta de onde vem a ficha (cours).
_idO número escrito no alto da ficha (C0001, C0002…).
_scoreUma nota de relevância. Vale 1 em todos os lugares aqui porque não buscamos nada de preciso: todas as fichas se equivalem.
_sourceA própria ficha, tal como foi guardada na gaveta, com todos os seus quadros.

A palavra hit quer dizer "acerto", como no tiro: uma ficha "acertada" pela consulta. O campo professeur é uma ficha dentro da ficha (um JSON dentro do JSON): o professor está escrito diretamente na ficha do curso, com seu nome e sua cidade. O SQL não faz isso naturalmente, seria preciso uma segunda tabela e uma junção.

E7. Exibir mais de dez

json
GET cours/_search
{
  "size": 20
}

O que a consulta pede: "Abra a gaveta cours e mostre-me 20 fichas em vez das 10 habituais."

Uma única novidade, mas importante: a consulta agora tem duas partes. A primeira linha (GET cours/_search) diz o que fazer; o bloco entre chaves embaixo, que chamamos de corpo, dá precisões. Aqui a precisão é "size": 20: "tamanho do lote = 20". É tudo. size quer dizer simplesmente "quantas fichas você quer que lhe mostrem".

Verifique na resposta: hits.total.value continua valendo 504 (a gaveta não mudou), mas a lista hits agora contém 20 fichas em vez de 10. Em SQL: LIMIT 20.

Para entender bem: um GET com corpo?

Em HTTP clássico, GET não tem corpo; o Elasticsearch o aceita mesmo assim porque é prático no Dev Tools. Se uma ferramenta recusar, POST cours/_search com o mesmo corpo faz exatamente a mesma coisa. Outro limite a conhecer: size não pode ultrapassar 10.000 de uma vez (parâmetro index.max_result_window); para percorrer mais, paginamos. Para nossos 504 cursos, "size": 504 funcionaria, mas a resposta teria milhares de linhas: não é assim que se leem dados, as agregações (E13) são feitas para isso.

E8. Escolher os campos a exibir

json
GET cours/_search
{
  "size": 3,
  "_source": ["titre", "prix"]
}
json
"hits": [
  { "_id": "C0001", "_source": { "titre": "Docker expliqué simplement", "prix": 129 } },
  { "_id": "C0002", "_source": { "titre": "Docker avancé : industrialiser des conteneurs en production", "prix": 29 } },
  { "_id": "C0003", "_source": { "titre": "Docker : le guide complet", "prix": 19 } }
]

O que a consulta pede: "Mostre-me 3 fichas, mas somente os quadros titre e prix de cada ficha." Novidade: _source com uma lista de campos. Em vez da ficha inteira, guardamos só os quadros que nos interessam. A resposta se torna legível num olhar. Em SQL: SELECT titre, prix FROM cours LIMIT 3. Usaremos _source em quase todas as consultas seguintes, justamente para manter respostas curtas.

E9. Um documento preciso, pelo seu identificador

text
GET cours/_doc/C0001
json
{
  "_index": "cours",
  "_id": "C0001",
  "_version": 6,
  "found": true,
  "_source": {
    "id": "C0001",
    "titre": "Docker expliqué simplement",
    "categorie": "DevOps",
    "sujet": "Docker",
    "niveau": "debutant",
    "prix": 129,
    "note_moyenne": 4.4,
    "nb_avis": 327,
    "professeur": { "id": "P001", "nom": "Karim Caron", "ville": "Gatineau" },

  }
}

Nenhuma busca aqui: pedimos o documento cujo identificador é C0001, e o Elasticsearch o devolve diretamente ("found": true). É o acesso mais rápido que existe. Em SQL: SELECT * FROM cours WHERE id = 'C0001'. Guarde este curso, «Docker expliqué simplement», de Karim Caron: vamos reencontrá-lo no Neo4j daqui a pouco, para provar que os dois motores contêm os mesmos dados.

Para entender bem: _id e id, duas coisas diferentes

_id (com sublinhado) é o identificador técnico do documento no Elasticsearch; id (sem sublinhado) é um campo comum dentro de _source. O kit os tornou voluntariamente idênticos (C0001 dos dois lados) para que seja legível. É também o que torna importer reexecutável: enviar duas vezes um documento com o mesmo _id substitui o primeiro em vez de criar um segundo, daí _version: 6 (o documento foi reescrito seis vezes na máquina do curso, sem nunca ser duplicado).

E10. A primeira busca de verdade

json
GET cours/_search
{
  "query": { "match": { "titre": "kubernetes" } },
  "_source": ["titre"]
}
json
"hits": {
  "total": { "value": 7, "relation": "eq" },
  "hits": [
    { "_score": 5.0897474, "_source": { "titre": "Les bases de Kubernetes" } },
    { "_score": 5.0897474, "_source": { "titre": "Kubernetes en pratique" } },
    { "_score": 5.0897474, "_source": { "titre": "Kubernetes pour les débutants" } },

  ]
}

Novidade: query, a parte do corpo que diz o que buscar. match é a busca básica: "os documentos cujo campo titre contém a palavra kubernetes". Sete cursos respondem, e pela primeira vez _score não é mais 1: é a relevância, e os resultados são classificados do mais relevante ao menos relevante. Em SQL, o equivalente aproximado seria WHERE titre LIKE '%kubernetes%', mas observe bem: digitamos kubernetes em minúsculas e encontramos "Kubernetes" com maiúscula. LIKE não teria feito isso.

Para entender bem: o que match faz

No momento em que um curso é indexado, o Elasticsearch divide seu título em palavras, coloca-as em minúsculas, retira os acentos e reduz cada palavra à sua raiz ("conteneurs" vira "conteneur"): é a análise, feita aqui pelo analisador french definido no mapping do kit. Quando você busca, sua consulta passa pelo mesmo tratamento, depois o Elasticsearch compara palavra a palavra. Resultado: maiúsculas, acentos e plurais não contam mais. O _score sobe quando a palavra é rara no índice e frequente no documento. O módulo 3 dedica várias lições a isso; aqui, guarde apenas: match busca palavras, não sequências de caracteres.

E11. O erro de digitação perdoado

Primeiro a prova de que, por padrão, um erro de verdade não encontra nada. "kubrenetes" (duas letras invertidas):

json
GET cours/_count
{
  "query": { "match": { "titre": "kubrenetes" } }
}
json
{ "count": 0,  }

Depois a mesma consulta com uma única linha a mais, fuzziness, que autoriza uma ou duas letras de diferença:

json
GET cours/_search
{
  "query": { "match": { "titre": { "query": "kubrenetes", "fuzziness": "AUTO" } } },
  "size": 3,
  "_source": ["titre", "niveau", "prix"]
}
json
"total": { "value": 7, "relation": "eq" },
"hits": [
  { "_score": 4.4535294, "_source": { "titre": "Les bases de Kubernetes", "niveau": "debutant", "prix": 0 } },
  { "_score": 4.4535294, "_source": { "titre": "Kubernetes en pratique", "niveau": "debutant", "prix": 129 } },
  { "_score": 4.4535294, "_source": { "titre": "Kubernetes pour les débutants", "niveau": "debutant", "prix": 29 } }
]

Os 7 cursos Kubernetes voltam, apesar do erro. É o momento que sempre surpreende em sala, e é a razão de ser do Elasticsearch em uma barra de busca: o usuário digita errado, o motor entende mesmo assim. Um LIKE '%kubrenetes%' em SQL nunca teria retornado nada. Observe a sintaxe: quando match precisa de opções, o valor do campo se torna um objeto { "query": …, "fuzziness": … } em vez de uma simples string.

Para entender bem: AUTO

fuzziness conta as modificações (uma letra acrescentada, retirada, mudada ou trocada com sua vizinha) que se tolera entre a palavra digitada e a palavra indexada. AUTO adapta a tolerância ao comprimento da palavra: 0 erro para uma palavra de 1 ou 2 letras, 1 erro de 3 a 5 letras, 2 erros além disso. "kubrenetes" tem 10 letras, portanto 2 erros admitidos; ele só tem um (a troca reer): encontrado. O _score é um pouco mais baixo do que em E10 (4,45 contra 5,09): o Elasticsearch penaliza levemente as correspondências aproximadas, o que mantém as correspondências exatas na frente.

E12. Ordenar: o ranking dos cursos

json
GET cours/_search
{
  "query": { "range": { "nb_avis": { "gte": 10 } } },
  "sort": [{ "note_moyenne": "desc" }, { "nb_avis": "desc" }],
  "size": 5,
  "_source": ["titre", "note_moyenne", "nb_avis", "categorie"]
}

Duas novidades, que se entendem na leitura. range com gte (greater than or equal): somente os cursos que têm pelo menos 10 avaliações, para deixar de fora as notas baseadas em um único voto. sort: classificar por note_moyenne decrescente, depois por nb_avis decrescente para desempatar. Em SQL: WHERE nb_avis >= 10 ORDER BY note_moyenne DESC, nb_avis DESC LIMIT 5. Quando ordenamos nós mesmos, _score se torna null: a relevância não serve mais, é a sua ordem que conta.

E13. Contar por categoria sem ler uma única linha

json
GET cours/_search
{
  "size": 0,
  "aggs": {
    "par_categorie": { "terms": { "field": "categorie" } }
  }
}
json
"aggregations": {
  "par_categorie": {
    "buckets": [
      { "key": "Cloud", "doc_count": 84 },
      { "key": "DevOps", "doc_count": 84 },
      { "key": "Données", "doc_count": 84 },
      { "key": "Développement web", "doc_count": 84 },
      { "key": "IA", "doc_count": 84 },
      { "key": "Sécurité", "doc_count": 84 }
    ]
  }
}

Novidade: aggs (agregações). terms sobre categorie faz um pacote (bucket) por valor distinto e conta os documentos dentro. size: 0 diz "não me retorne nenhum documento, só os contadores": a resposta é minúscula e instantânea, mesmo sobre milhões de linhas. Em SQL: SELECT categorie, COUNT(*) FROM cours GROUP BY categorie. Resultado: seis categorias de 84 cursos cada. par_categorie é um nome que você escolhe; ele serve apenas para reencontrar o resultado na resposta.

Para entender bem: por que esta é a base do Kibana

Cada gráfico do Kibana, cada gráfico de pizza, cada histograma é uma agregação como essa, executada pelo Elasticsearch e desenhada pelo Kibana. Quando você construir um painel no módulo 4, clicará em "campo: categorie, agregação: terms" e o Kibana enviará exatamente esta consulta. Saber lê-la é saber o que o painel faz nos bastidores.

E14. Uma agregação dentro de uma agregação

json
GET cours/_search
{
  "size": 0,
  "aggs": {
    "par_categorie": {
      "terms": { "field": "categorie" },
      "aggs": { "par_niveau": { "terms": { "field": "niveau" } } }
    }
  }
}

Mesma consulta que E13, com um segundo aggs dentro do primeiro. Em cada pacote de categoria, refazemos pacotes por niveau. Resultado: para "Cloud", 45 iniciante, 28 avançado, 11 intermediário; e assim por diante para as seis categorias, em uma única passada. Em SQL: GROUP BY categorie, niveau, mas a resposta já está hierarquizada, pronta para um gráfico empilhado.

E15. Os erros de servidor nos 12.000 acessos

json
GET acces/_search
{
  "size": 0,
  "query": { "range": { "statut": { "gte": 500 } } },
  "aggs": { "codes": { "terms": { "field": "statut" } } }
}
json
"hits": { "total": { "value": 178, "relation": "eq" } },
"aggregations": {
  "codes": {
    "buckets": [
      { "key": 500, "doc_count": 122 },
      { "key": 503, "doc_count": 56 }
    ]
  }
}

Nada de novo: combinamos range (E12) e terms (E13) sobre o índice acces. Entre as 12.000 linhas de log, 178 têm um código HTTP ≥ 500: 122 erros 500 e 56 503. É a pergunta que um responsável de plataforma faz toda manhã, e ela é respondida em um milissegundo. Em SQL: SELECT statut, COUNT(*) FROM acces WHERE statut >= 500 GROUP BY statut. É exatamente o que você colocará em um painel no módulo 4.

Neo4j, no Neo4j Browser

Mostrar as 16 consultas Neo4j (N1 a N16)

Abra http://localhost:7474. Tela de conexão: URL neo4j://localhost:7687, usuário neo4j, senha aiopsatlas2026. No alto, uma barra de edição que começa por neo4j$: cole uma única consulta, depois Ctrl+Enter ou o triângulo ▶. O resultado aparece em um quadro embaixo, com abas à esquerda: Graph (um desenho, quando o resultado contém nós), Table (linhas e colunas) e Text.

N1. Exibir tudo

cypher
MATCH (n) RETURN n

O resultado é uma nuvem de bolhas coloridas, ligadas por setas, que você pode mover com o mouse. O Neo4j Browser exibe no máximo 300 nós por vez (uma mensagem no alto do resultado sinaliza isso); os 872 estão realmente lá, ele só desenha uma parte para permanecer legível.

É a consulta mais simples do Cypher, a linguagem do Neo4j. MATCH quer dizer "encontre", (n) designa um nó qualquer (os parênteses desenham um círculo, como uma bolha) ao qual damos o nome n, e RETURN n quer dizer "mostre-o para mim". Em SQL, não há equivalente: seria "SELECT * de todas as tabelas ao mesmo tempo", o que o SQL não sabe fazer.

Para entender bem: nós, rótulos, relações

Um grafo é feito de duas coisas: nós (as bolhas) e relações (as setas entre bolhas). Cada nó carrega um rótulo que diz o que ele é (Cours, Etudiant…) e propriedades (título, preço…). Cada relação carrega um tipo (INSCRIT_A, ENSEIGNE…) e uma direção.

Neo4jSQL
Rótulo CoursTabela cours
Linha
PropriedadeColuna
Relação INSCRIT_ATabela de ligação inscriptions + junções

O grafo do laboratório:

text
Neo4j
├── 504 nœuds Cours
├── 300 nœuds Etudiant
├──  30 nœuds Professeur
├──  22 nœuds Competence
└──  16 nœuds Ville
     = 872 nœuds, reliés par 3 712 relations

Os 504 cursos são os mesmos que os 504 documentos do índice Elasticsearch cours: são dois motores que organizam os mesmos dados de duas maneiras, cada um para responder a perguntas diferentes.

N2. Limitar o número de resultados

cypher
MATCH (n) RETURN n LIMIT 25

Uma única novidade: LIMIT 25, mesma palavra que em SQL. Vinte e cinco bolhas em vez de trezentas: enfim vemos alguma coisa. Passe o mouse sobre uma bolha: suas propriedades aparecem embaixo do quadro. Dê um duplo clique nela: seus vizinhos se desdobram.

N3. Contar em vez de exibir

cypher
MATCH (n) RETURN count(n) AS total
text
total
872

count(n) conta em vez de desenhar; AS total nomeia a coluna. Sem desenho: o resultado passa sozinho para a visão Table, já que um número não se desenha. 872, o número de etat. Em SQL: SELECT COUNT(*).

N4. Um único tipo de nó

cypher
MATCH (c:Cours) RETURN c LIMIT 5

Novidade: :Cours depois do nome da variável. É o rótulo: "somente os nós que são cursos". Cinco bolhas, todas da mesma cor. Em SQL: SELECT * FROM cours LIMIT 5. Por hábito, chamamos a variável pela inicial do rótulo (c para Cours, e para Etudiant), mas n também funcionaria.

N5. Colunas em vez de bolhas

cypher
MATCH (c:Cours) RETURN c.titre, c.prix LIMIT 5
text
c.titre                                                        c.prix
"Docker expliqué simplement"                                   129.0
"Docker avancé : industrialiser des conteneurs en production"  29.0
"Docker : le guide complet"                                    19.0

Novidade: c.titre, c.prix. O ponto dá acesso a uma propriedade do nó. Quando retornamos propriedades em vez de nós inteiros, o Neo4j Browser passa para a visão Table. Em SQL: SELECT titre, prix FROM cours LIMIT 5. Compare com E8: mesmos títulos, mesmos preços, mesma ordem. Para conhecer todas as propriedades de um curso: MATCH (c:Cours) RETURN keys(c) LIMIT 1 responde prix, duree_heures, date_publication, sujet, niveau, categorie, id, titre.

N6. Contar por rótulo

cypher
MATCH (n) RETURN labels(n)[0] AS type, count(*) AS nombre ORDER BY nombre DESC
text
type          nombre
"Cours"       504
"Etudiant"    300
"Professeur"  30
"Competence"  22
"Ville"       16

labels(n) retorna a lista dos rótulos do nó (um nó pode ter vários; aqui um só, daí o [0], o primeiro elemento). O count(*) se agrupa automaticamente por tudo o que não é uma agregação: não é preciso escrever GROUP BY, o Cypher o deduz. ORDER BY nombre DESC ordena. A soma das cinco linhas dá 872. Em SQL, seriam necessários cinco SELECT COUNT(*) e UNIONs.

N7. Um nó preciso, pelo seu identificador

cypher
MATCH (c:Cours {id: 'C0001'}) RETURN c.titre, c.prix, c.niveau
text
c.titre                       c.prix  c.niveau
"Docker expliqué simplement"  129.0   "debutant"

Novidade: as chaves {id: 'C0001'} no padrão. Elas filtram por uma propriedade, como um WHERE id = 'C0001'. E é o mesmo curso que em E9 no Elasticsearch: mesmo título, mesmo preço. Eis a prova de que os dois motores contêm de fato os mesmos dados; o que muda é o que podemos pedir a eles.

N8. Filtrar com WHERE

cypher
MATCH (c:Cours) WHERE c.titre CONTAINS 'Kubernetes' RETURN c.titre ORDER BY c.titre
text
c.titre
"Kubernetes : de zéro à la production"
"Kubernetes : le guide complet"
"Kubernetes avancé : superviser des conteneurs en production"
"Kubernetes en pratique"
"Kubernetes expliqué simplement"
"Kubernetes pour les débutants"
"Les bases de Kubernetes"

WHERE se escreve como em SQL e CONTAINS busca uma sequência de caracteres. Sete cursos, os mesmos que em E10. Mas experimente CONTAINS 'kubernetes' em minúsculas: zero resultado. E CONTAINS 'kubrenetes': zero também. O Neo4j compara caracteres, exatamente; ele não conhece nem maiúsculas/minúsculas, nem palavras, nem erros de digitação. É precisamente por isso que o laboratório tem os dois motores: a barra de busca é o Elasticsearch; os vínculos entre as coisas são o Neo4j.

N9. Ordenar

cypher
MATCH (c:Cours) RETURN c.titre, c.prix ORDER BY c.prix DESC LIMIT 5
text
c.titre                                                  c.prix
"Helm par la pratique : superviser un pipeline CI/CD"    199.0
"Atelier GitHub Actions : vos applications"              199.0
"Docker pour les débutants"                              199.0

Nada de novo: ORDER BY … DESC LIMIT 5, como em SQL. Os cinco cursos mais caros, todos a 199 $.

N10. Contar por categoria

cypher
MATCH (c:Cours) RETURN c.categorie AS categorie, count(*) AS nombre ORDER BY nombre DESC
text
categorie            nombre
"DevOps"             84
"Données"            84
"IA"                 84
"Développement web"  84
"Sécurité"           84
"Cloud"              84

Exatamente o resultado da agregação E13 no Elasticsearch: seis categorias de 84. Mesmo dado, dois motores, duas sintaxes. Até aqui, o Neo4j não fez nada que o SQL não saiba fazer. Isso muda na próxima consulta.

N11. A primeira relação

cypher
MATCH (p:Professeur)-[r:ENSEIGNE]->(c:Cours) RETURN p, r, c LIMIT 30

Passe para a visão Graph: bolhas "professor" ligadas por setas ENSEIGNE a bolhas "curso". Agarre um professor com o mouse, você vê todos os seus cursos acompanharem.

Novidade: a seta. (p:Professeur)-[r:ENSEIGNE]->(c:Cours) se lê literalmente "um professor, que ensina, um curso". Os parênteses são nós, os colchetes são a relação, -> dá o sentido. É um desenho ASCII do que buscamos, e o Neo4j encontra todos os lugares do grafo que se parecem com esse desenho. Em SQL, seria SELECT * FROM professeurs JOIN cours ON cours.professeur_id = professeurs.id, e isso não se desenharia.

Para entender bem: por que as relações mudam tudo

Em SQL, uma relação entre duas linhas não existe de fato: ela é recalculada a cada consulta por uma junção, que compara identificadores. Com dez junções encadeadas, fica lento e ilegível. No Neo4j, a relação é armazenada como uma seta física entre dois nós: seguir uma seta custa o mesmo preço qualquer que seja o número de nós no banco. É o que torna possíveis as consultas N14 a N16, que encadeiam vários saltos sem esforço.

N12. Contar as relações por tipo

cypher
MATCH ()-[r]->() RETURN type(r) AS relation, count(*) AS nombre ORDER BY nombre DESC
text
relation        nombre
"INSCRIT_A"     1654
"COUVRE"        994
"ENSEIGNE"      504
"HABITE"        330
"PREREQUIS_DE"  230

() é um nó qualquer do qual nem sequer guardamos o nome; [r] uma relação de qualquer tipo; type(r) seu tipo. Cinco tipos de relações, 3.712 no total: o número exibido pelo painel Database information do Neo4j Browser. Lemos o modelo do laboratório em uma linha: estudantes inscritos em cursos, cursos que cobrem competências, professores que ensinam cursos, pessoas que moram em cidades, e cursos pré-requisitos de outros cursos. O ENSEIGNE em 504 diz que há exatamente uma relação por curso: cada curso tem um e um só professor.

N13. Seguir uma relação a partir de um nó preciso

cypher
MATCH (p:Professeur {id: 'P001'})-[:ENSEIGNE]->(c:Cours)
RETURN p.prenom + ' ' + p.nom AS professeur, count(c) AS nb_cours
text
professeur     nb_cours
"Karim Caron"  11

Combinamos N7 (o filtro {id: 'P001'}) e N11 (a seta). P001 é o professor de «Docker expliqué simplement» visto em E9 e N7; ele ensina 11 cursos. O + cola strings, como em SQL com ||. Observe [:ENSEIGNE] sem nome de variável: quando não precisamos da relação no RETURN, não a nomeamos.

N14. O caminho de formação

cypher
MATCH (debut:Cours {id: 'C0111'}), (fin:Cours {id: 'C0110'}),
      chemin = shortestPath((debut)-[:PREREQUIS_DE*]-(fin))
RETURN length(chemin) AS sauts, [n IN nodes(chemin) | n.titre] AS parcours
text
sauts  parcours
5      ["Neo4j : de zéro à la production", "Neo4j en pratique", "Neo4j par la pratique : interroger des journaux applicatifs", "Neo4j avancé : modéliser des journaux applicatifs", "Neo4j : le guide complet", "Maîtriser Neo4j"]

Primeira consulta que não se parece mais com nada em SQL. [:PREREQUIS_DE*] com o asterisco quer dizer "seguindo esta relação quantas vezes for preciso". shortestPath pede o mais curto dos caminhos possíveis entre os dois cursos. A resposta: 5 saltos, e a lista dos seis títulos a seguir, na ordem, para ir do primeiro ao último curso da trilha Neo4j. É um plano de formação calculado na hora. Em SQL, seria preciso uma consulta recursiva de várias dezenas de linhas, e ela seria lenta.

Para entender bem: a linha RETURN

nodes(chemin) dá a lista dos nós atravessados. [n IN nodes(chemin) | n.titre] se lê "para cada nó n desta lista, guarde seu título": é uma maneira compacta de transformar uma lista de nós em lista de títulos. length(chemin) conta as relações atravessadas (5 relações para 6 nós). O módulo 6 detalha essas funções; aqui o importante é o resultado: o Neo4j encontrou um itinerário no grafo.

N15. "Os estudantes que fizeram este curso também fizeram…"

cypher
MATCH (x:Cours {id: 'C0213'})<-[:INSCRIT_A]-(e:Etudiant)-[:INSCRIT_A]->(autre:Cours)
WHERE autre <> x
RETURN autre.titre AS recommandation, autre.categorie AS categorie,
       count(DISTINCT e) AS etudiants_communs
ORDER BY etudiants_communs DESC, recommandation
LIMIT 5
text
recommandation                                                  categorie  etudiants_communs
"Agents IA par la pratique : évaluer un pipeline de prédiction"  "IA"       3
"Atelier NLP : des agents autonomes"                             "IA"       2
"Deep learning par la pratique : orchestrer un assistant …"      "IA"       2

O motor de recomendação de um site de comércio, em cinco linhas. Leia o padrão da esquerda para a direita: partimos do curso x, subimos a seta INSCRIT_A (ela aponta para x, daí o <-) até os estudantes e que o fazem, depois descemos outra seta INSCRIT_A para os outros cursos autre desses mesmos estudantes. WHERE autre <> x deixa de fora o próprio curso de partida. count(DISTINCT e) conta os estudantes em comum, sem duplicata. Os cursos no topo são os que os estudantes de C0213 fazem com mais frequência em paralelo: são as recomendações. Em SQL: duas junções sobre a tabela de inscrições, um GROUP BY, e uma consulta que se relê três vezes antes de entendê-la.

N16. O mesmo resultado, desenhado

cypher
MATCH chemin = (x:Cours {id: 'C0213'})<-[:INSCRIT_A]-(:Etudiant)-[:INSCRIT_A]->(autre:Cours)
WHERE autre <> x
RETURN chemin LIMIT 50

Passe para a visão Graph: o curso de partida no centro, seus estudantes ao redor, e os cursos que eles compartilham na periferia. É a consulta N15 sem a contagem: retornamos os caminhos inteiros em vez de colunas, e o Neo4j Browser os desenha. É a imagem para mostrar quando alguém pergunta "para que serve um banco de grafos".

A mensagem a transmitir. Quinze consultas Elasticsearch, dezesseis consultas Cypher, e vimos, na ordem: listar, contar, exibir, filtrar, ordenar, agrupar, o que o SQL também faz; depois perdoar um erro de digitação, agregar 12.000 linhas em um milissegundo, calcular um caminho mais curto e produzir recomendações, o que o SQL faz mal ou não faz de jeito nenhum. O mesmo curso «Docker expliqué simplement» apareceu nos dois motores: mesmos dados, perguntas diferentes. É exatamente por isso que este laboratório existe, e todo o resto do curso detalha como cada uma dessas consultas funciona.

Desafio bônus (opcional)

Refaça a etapa 7 do seu anexo com docker compose stop neo4j: o que acontece com a linha Neo4j répond — nœuds : 872 no etat? O que diz o Neo4j Browser, já conectado, quando você reexecuta a consulta N3 (MATCH (n) RETURN count(n))? Qual é a última linha de journal neo4j? Reinicie com docker compose start neo4j, verifique que os 872 nós continuam lá sem recarregar nada, e anote qual dos dois serviços reinicia mais rápido.

Anexo A — Passo a passo detalhado no Windows (PowerShell)

Mostrar o passo a passo Windows (A.0 a A.11)

Todos os comandos deste anexo são digitados no PowerShell (Windows Terminal, ou PowerShell 7), com .\labo.ps1 …. As saídas reproduzidas são as da máquina do curso, no Windows 11 e Docker Desktop.

A.0 — Antes de começar

  • Ter lido as quatro lições do módulo: 01, 02, 03 e 04.
  • Docker Desktop aberto (ícone verde), o kit clonado, um terminal PowerShell aberto na pasta do kit (a que contém docker-compose.yml e labo.ps1).
  • Se o PowerShell recusar executar .\labo.ps1 («l'exécution de scripts est désactivée sur ce système», a execução de scripts está desabilitada neste sistema): Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, responda O, reexecute.
  • O laboratório pode estar iniciado ou não: a prática começa pelos pré-requisitos. No fim, .\labo.ps1 etat deve exibir (healthy) em todos os lugares, "status":"green", acces 12000 avis 609 cours 504 e nœuds : 872.
  • Todas as consultas desta prática: elasticsearch/requetes/01-pratique-demarrer-verifier-reparer.txt. Nenhuma escreve no cluster.

A.1 — Percorrer a lista de verificação dos pré-requisitos

Antes de ligar qualquer coisa, faça o script falar.

powershell
.\labo.ps1 prerequis

Ponto de controle: só vistos verdes e a frase final.

text
== Prérequis ==
  ✔ docker : Docker version 29.3.1, build c2be9cc
  ✔ le démon Docker répond
  ✔ docker compose : 5.1.1
  ✔ mémoire disponible pour Docker : 31 Go
  ✔ processeurs : 20
  ✔ port 9200 : utilisé par le labo lui-même
  ✔ port 5601 : utilisé par le labo lui-même
  ✔ port 7474 : utilisé par le labo lui-même
  ✔ port 7687 : utilisé par le labo lui-même

Tout est prêt. Lancez : .\labo.ps1 demarrer

Na sua máquina, versões e memória diferem, e se o laboratório nunca rodou as quatro últimas linhas dizem port 9200 : libre (livre). As duas leituras são boas.

Se você vir outra coisa: uma cruz contém seu remédio na frase (Docker Desktop não aberto, memória abaixo de 4 GB, porta ocupada); corrija, reexecute, só avance com a linha verde. Para a memória: Docker Desktop → SettingsResources, ou o arquivo %UserProfile%\.wslconfig se o Docker Desktop usar WSL 2.

A.2 — Iniciar e ler os três blocos

Execute a inicialização e, desta vez, leia o que passa na tela em vez de esperar o fim.

powershell
.\labo.ps1 demarrer

Ponto de controle: três blocos == … == e depois três prêt.

text
== Téléchargement des images (long la première fois : ~6 Go, ~12 Go avec OpenSearch) ==

== Démarrage ==

== Attente que chaque service soit prêt ==
  elasticsearch            prêt (0 s)
  kibana                   prêt (0 s)
  neo4j                   ..... prêt (15 s)

Le labo est prêt.
  Kibana                 http://localhost:5601   (Dev Tools : menu ☰ → Management → Dev Tools)
  Elasticsearch          http://localhost:9200
  Neo4j Browser          http://localhost:7474   (utilisateur neo4j · mot de passe aiopsatlas2026)

Étape suivante : .\labo.ps1 importer   puis   .\labo.ps1 charger-graphe

O primeiro bloco fica vazio se as imagens já estão lá; o segundo contém as linhas do Compose (Container labo-elasticsearch Started, ou Running se já estava rodando); o terceiro acrescenta um ponto a cada três segundos até prêt (… s). Laboratório frio: um a dois minutos; laboratório já em funcionamento: prêt (0 s).

Se você vir outra coisa: unhealthy, exited ou délai dépassé.\labo.ps1 journal <serviço> e o catálogo da lição 04; causa mais frequente no Windows: a memória alocada ao Docker Desktop (Exited (137)).

A.3 — Anotar os quatro números de etat

Você vai digitar etat várias vezes; aprenda primeiro a extrair dele os quatro números que contam.

powershell
.\labo.ps1 etat

Ponto de controle: saída real da máquina do curso, onde o perfil OpenSearch do módulo 5 está ativo. Na sua máquina, as duas linhas labo-opensearch… e ✔ OpenSearch são substituídas por — OpenSearch non démarré (profil optionnel : .\labo.ps1 demarrer opensearch).

text
== Conteneurs ==
NAME                         STATUS                    PORTS
labo-elasticsearch           Up 10 hours (healthy)     0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp
labo-kibana                  Up 10 hours (healthy)     0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp
labo-neo4j                   Up 37 seconds (healthy)   0.0.0.0:7474->7474/tcp, [::]:7474->7474/tcp, 0.0.0.0:7687->7687/tcp, [::]:7687->7687/tcp
labo-opensearch              Up 10 hours (healthy)     0.0.0.0:9201->9200/tcp, [::]:9201->9200/tcp
labo-opensearch-dashboards   Up 10 hours               0.0.0.0:5602->5601/tcp, [::]:5602->5601/tcp

== Services ==
  ✔ Elasticsearch : {"status":"green","number_of_nodes":1}
     index : acces 12000 avis    609 cours   504
  ✔ Kibana répond (http://localhost:5601)
  ✔ Neo4j répond — nœuds : 872
  ✔ OpenSearch : {"status":"green","number_of_nodes":1}

Anote os quatro números esperados: 12000, 609, 504, 872. Em um laboratório novinho você lê index : aucun index du labo (nenhum índice do laboratório) e nœuds : 0: normal, a etapa A.4 os preenche.

Se você vir outra coisa: ✘ Kibana ne répond pas encore (Kibana ainda não responde) no minuto seguinte a demarrer → o Kibana termina de criar seus índices internos; redigite etat trinta segundos depois.

A.4 — Carregar os dados, depois recarregar para provar que nada muda

Carregue os índices e depois o grafo, e reexecute os dois comandos uma segunda vez: os contadores não devem mudar em uma unidade.

powershell
.\labo.ps1 importer
.\labo.ps1 charger-graphe
.\labo.ps1 importer
.\labo.ps1 charger-graphe

Ponto de controle: na segunda passada, importer sinaliza que os índices já existem e devolve os mesmos contadores:

text
== Import dans elasticsearch ==
— index cours existe déjà — conservé
  ✔ données cours chargées
— index avis existe déjà — conservé
  ✔ données avis chargées
— index acces existe déjà — conservé
  ✔ données acces chargées

index docs.count store.size
acces      12000      1.5mb
avis         609     74.8kb
cours        504    183.6kb

Import terminé. Attendu : cours = 504, avis = 609, acces = 12000.

e charger-graphe dá o mesmo balanço que na primeira:

text
== Chargement du graphe Neo4j ==
  ✔ contraintes et index en place
etiquette, noeuds
"Competence", 22
"Cours", 504
"Etudiant", 300
"Professeur", 30
"Ville", 16

Graphe chargé. Attendu : Competence 22, Cours 504, Etudiant 300, Professeur 30, Ville 16.

Os documentos carregam seu próprio _id (C0001, A00001…), portanto um segundo _bulk substitui cada documento em vez de acrescentá-lo; do lado do Neo4j, tudo está em MERGE. Só o store.size pode variar alguns kilobytes (o Lucene guarda por um momento as versões antigas); os docs.count, nunca.

Se você vir outra coisa: um docs.count diferente de 504 / 609 / 12000 → alguém escreveu nesses índices; .\labo.ps1 reinitialiser depois demarrer, importer, charger-graphe lhe devolve o estado de referência.

A.5 — Verificar o Elasticsearch a partir do Dev Tools

O script diz que tudo está bem; verifique isso sem ele. Abra http://localhost:5601, menu ManagementOutils de développement (Ferramentas de desenvolvimento), e envie estas consultas uma por uma (Ctrl + Enter ou o botão ▶ «Cliquer pour envoyer la requête», Clique para enviar a consulta).

text
GET _cluster/health

Ponto de controle: "status": "green", "unassigned_shards": 0, e 200 - OK embaixo à direita do painel de resposta.

json
{
  "cluster_name": "labo",
  "status": "green",
  "timed_out": false,
  "number_of_nodes": 1,
  "number_of_data_nodes": 1,
  "active_primary_shards": 53,
  "active_shards": 53,

  "unassigned_shards": 0,

  "active_shards_percent_as_number": 100
}
text
GET _cat/indices/cours,avis,acces?v&s=index

Ponto de controle: três linhas green, pri 1, rep 0, e os três contadores em docs.count.

text
health status index uuid                   pri rep docs.count docs.deleted store.size pri.store.size dataset.size
green  open   acces aii68fsfQXKyt5wqkE1mPA   1   0      12000            0      1.5mb          1.5mb        1.5mb
green  open   avis  W9j_JrwJT4mdzpWcS5k7xg   1   0        609            0     74.8kb         74.8kb       74.8kb
green  open   cours pmq403ZgSZWeHJY9uNw1Qw   1   0        504            0    183.6kb        183.6kb      183.6kb

Termine com GET cours/_count, GET avis/_count, GET acces/_count: "count": 504, 609, 12000. Você tem os mesmos números por dois caminhos independentes: o script (curl no contêiner) e o Dev Tools (via Kibana). Se um dia eles divergirem, é o caminho que está em causa, não os dados.

Se você vir outra coisa: "status": "yellow" → um índice tem réplicas não alocadas, impossível com os mappings do kit (number_of_replicas: 0); GET _cat/indices?v&health=yellow aponta o culpado, em geral um índice criado à mão.

A.6 — Contar o grafo no Neo4j Browser

Mesmo exercício para o Neo4j. Abra http://localhost:7474, conecte-se (neo4j / aiopsatlas2026, URL localhost:7687), digite no editor neo4j$ e clique em Run:

cypher
MATCH (n) RETURN labels(n)[0] AS label, count(*) ORDER BY label

Ponto de controle: um quadro com duas visões, Table e Raw (sem Graph: a consulta retorna números, não nós), duas colunas label e count(*), cinco linhas ordenadas, e embaixo à direita Started streaming 5 records after … ms and completed after … ms.

text
label          count(*)
"Competence"   22
"Cours"        504
"Etudiant"     300
"Professeur"   30
"Ville"        16

22 + 504 + 300 + 30 + 16 = 872, o número de etat. O painel Database information (ícone Database overview, o primeiro da barra lateral) exibe Nodes (872) e Relationships (3,712).

Se você vir outra coisa: um sexto rótulo desconhecido → nós criados fora do kit (o módulo 6 ensinará a excluí-los corretamente); Nodes (0) → você pulou charger-graphe, volte à etapa A.4.

A.7 — Quebrar o Kibana voluntariamente e observar a falha

Você sabe como se parece um laboratório saudável; provoque uma falha cuja causa você conhece para aprender a lê-la. Pare somente o Kibana, com o Compose (não arreter, que pararia tudo):

powershell
docker compose stop kibana
text
 Container labo-kibana Stopping
 Container labo-kibana Stopped

Depois os três gestos da lição 04, na ordem: etat, o navegador, journal.

Ponto de controle 1, .\labo.ps1 etat: a linha labo-kibana desapareceu do bloco == Conteneurs == (o Compose exibe por padrão só os contêineres em execução) e o bloco == Services == marca uma cruz:

text
== Conteneurs ==
NAME                         STATUS                   PORTS
labo-elasticsearch           Up 10 hours (healthy)    0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp
labo-neo4j                   Up 3 minutes (healthy)   0.0.0.0:7474->7474/tcp, [::]:7474->7474/tcp, 0.0.0.0:7687->7687/tcp, [::]:7687->7687/tcp
labo-opensearch              Up 10 hours (healthy)    0.0.0.0:9201->9200/tcp, [::]:9201->9200/tcp
labo-opensearch-dashboards   Up 10 hours              0.0.0.0:5602->5601/tcp, [::]:5602->5601/tcp

== Services ==
  ✔ Elasticsearch : {"status":"green","number_of_nodes":1}
     index : acces 12000 avis    609 cours   504
  ✘ Kibana ne répond pas encore
  ✔ Neo4j répond — nœuds : 872
  ✔ OpenSearch : {"status":"green","number_of_nodes":1}

Para ver mesmo assim o contêiner parado: docker compose ps -a exibe labo-kibana Exited (0) 31 seconds ago. O 0 diz "parada limpa, solicitada"; um 137 diria "morto, memória".

Ponto de controle 2, o navegador: recarregue http://localhost:5601. Nenhum "Kibana server is not ready yet" (essa frase vem do Kibana, mas não há mais Kibana para dizê-la), e sim o erro de conexão do próprio navegador: no Chrome ou Edge, "Não é possível acessar esse site", código ERR_CONNECTION_REFUSED. Ninguém escuta na porta 5601. Para lembrar: página do Kibana que se desculpa = o Kibana roda mas aguarda o Elasticsearch; erro do navegador = o Kibana não está rodando.

Ponto de controle 3, .\labo.ps1 journal kibana: as cem últimas linhas terminam com uma parada limpa, datada do segundo em que você digitou stop:

text
labo-kibana  | [2026-09-09T23:44:06.893+00:00][INFO ][root] SIGTERM received - initiating shutdown
labo-kibana  | [2026-09-09T23:44:06.894+00:00][INFO ][root] Kibana is shutting down
labo-kibana  | [2026-09-09T23:44:06.902+00:00][INFO ][plugins-system.standard] Stopping all plugins.

labo-kibana  | [2026-09-09T23:44:07.265+00:00][INFO ][plugins-system.standard] All plugins stopped.

SIGTERM received assina uma parada solicitada (por você, por docker compose stop, por uma reinicialização do Docker Desktop). Nenhuma linha ERROR nem FATAL: nada a reparar, basta reiniciar. As dezenas de linhas at OperatorSubscriber… entre as duas são uma pilha de chamadas emitida por um plugin durante a parada: ruído.

Se você vir outra coisa: no configuration file provided: not found → você não está na pasta do kit; no such service: kibana → você digitou o nome do contêiner (labo-kibana) em vez do serviço Compose (kibana).

A.8 — Reparar o Kibana e provar que o Elasticsearch não viu nada

Reinicie o serviço e aguarde que seu healthcheck volte ao verde: 40 a 60 segundos, o tempo de se reconectar ao Elasticsearch e verificar seus índices internos.

powershell
docker compose start kibana
text
 Container labo-elasticsearch Waiting
 Container labo-elasticsearch Healthy
 Container labo-kibana Starting
 Container labo-kibana Started

O Compose verificou primeiro que o Elasticsearch estava Healthy (o depends_on … service_healthy da lição 03), depois iniciou o Kibana. Monitore a cada cinco segundos:

powershell
docker inspect --format '{{.State.Health.Status}}' labo-kibana

Ponto de controle: starting durante 40 a 50 segundos, depois healthy (na máquina do curso: starting de 0 a 45 s, healthy aos 50 s). etat então mostra novamente a linha:

text
labo-kibana                  Up 52 seconds (healthy)   0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp

  ✔ Kibana répond (http://localhost:5601)

e journal kibana termina com as linhas que queremos ver:

text
labo-kibana  | [2026-09-09T23:47:49.408+00:00][INFO ][http.server.Kibana] http server running at http://0.0.0.0:5601
labo-kibana  | [2026-09-09T23:47:50.374+00:00][INFO ][status] Kibana is now available

Reabra o Dev Tools (se cair em "Kibana server is not ready yet", você foi mais rápido que o healthy: aguarde dez segundos) e envie a consulta que o próprio etat usa:

text
GET _cluster/health?filter_path=status,number_of_nodes
json
{
  "status": "green",
  "number_of_nodes": 1
}

O Elasticsearch respondeu green durante toda a falha: o Kibana é uma janela sobre os dados, não os dados. Parar o Kibana não apagou nem reindexou nada.

Se você vir outra coisa: unhealthy após dois minutos → journal kibana e procure ECONNREFUSED (Elasticsearch caído nesse meio-tempo); port is already allocated → outro programa ocupou a porta 5601 durante a parada (lição 04, falha 1).

A.9 — Sua vez: provocar e explicar um 404

Você reconhece um serviço parado; aprenda agora a reconhecer uma consulta errada em um serviço saudável, a confusão mais frequente em sala. No Dev Tools, escreva uma consulta de busca que retorne 404 com "type": "index_not_found_exception", depois explique em uma frase por que o cluster permanece green.

Dica: o Elasticsearch nunca adivinha o nome de um índice. Escolha um que não exista, com o prefixo pratique-; nada a criar, nada a escrever.

Solução
text
GET pratique-inexistant/_search

Resposta, com o selo 404 - Not Found embaixo à direita do painel de saída:

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

  },
  "status": 404
}

Explicação esperada: o 404 é uma resposta normal e completa do Elasticsearch: "entendi sua consulta, mas esse recurso não existe". O serviço roda, o cluster permanece green; só há o nome a corrigir (GET _cat/indices?v dá a lista). Na etapa A.7, não havia nenhuma resposta.

Duas variantes para experimentar: GET pratique-inexistant/_count?ignore_unavailable=true retorna 200 e "count": 0 (você pede para ignorar o índice ausente); GET cours/_serch retorna 400 com "no handler found for uri [/cours/_serch] and method [GET]". Um 400: "não entendo a consulta"; um 404: "entendo, mas isso não existe".

A.10 — Verificação final

Um único comando prova que tudo está feito: etat, com o Kibana de volta e os quatro números.

powershell
.\labo.ps1 etat
text
== Conteneurs ==
NAME                         STATUS                        PORTS
labo-elasticsearch           Up 10 hours (healthy)         0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp
labo-kibana                  Up About a minute (healthy)   0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp
labo-neo4j                   Up 7 minutes (healthy)        0.0.0.0:7474->7474/tcp, [::]:7474->7474/tcp, 0.0.0.0:7687->7687/tcp, [::]:7687->7687/tcp
labo-opensearch              Up 10 hours (healthy)         0.0.0.0:9201->9200/tcp, [::]:9201->9200/tcp
labo-opensearch-dashboards   Up 10 hours                   0.0.0.0:5602->5601/tcp, [::]:5602->5601/tcp

== Services ==
  ✔ Elasticsearch : {"status":"green","number_of_nodes":1}
     index : acces 12000 avis    609 cours   504
  ✔ Kibana répond (http://localhost:5601)
  ✔ Neo4j répond — nœuds : 872
  ✔ OpenSearch : {"status":"green","number_of_nodes":1}

(Sem o perfil OpenSearch, os dois contêineres labo-opensearch… não aparecem e a última linha diz — OpenSearch non démarré …: é o estado esperado até o módulo 5.)

  • prerequis termina com Tout est prêt.
  • labo-elasticsearch, labo-kibana e labo-neo4j estão Up … (healthy), Kibana incluído.
  • etat exibe "status":"green", acces 12000 avis 609 cours 504, nœuds : 872, inalterados após o segundo importer / charger-graphe.
  • Os mesmos contadores foram relidos no Dev Tools e no Neo4j Browser.
  • Você sabe dizer o que mostram etat, o navegador e journal kibana quando o Kibana está parado, e em que isso difere de um 404.
  • Você guardou a saída de etat acima (cópia ou captura) como entregável.

A.11 — Limpeza

Esta prática não cria nada: nem índice, nem nó, nem objeto Kibana. Duas coisas a garantir: que o Kibana está rodando (senão docker compose start kibana a partir da pasta do kit), e que nenhum índice de trabalho ficou para trás:

text
GET _cat/indices/pratique-*?v

Resposta esperada: só a linha de cabeçalho (health status index uuid pri rep docs.count …). Não toque em cours, avis, acces nem no grafo: eles servem a todos os módulos seguintes.

Anexo B — Passo a passo detalhado no Linux, macOS, WSL 2 e Git Bash

Mostrar o passo a passo Linux, macOS, WSL 2 e Git Bash (B.0 a B.11)

Todos os comandos deste anexo são digitados em um terminal bash (ou zsh), com ./labo.sh …. As saídas são idênticas às do Windows, exceto pelo nome do script: o kit é o mesmo, só os lançadores mudam.

B.0 — Antes de começar

  • Ter lido as quatro lições do módulo: 01, 02, 03 e 04.
  • Docker em execução (Docker Desktop no macOS e WSL 2, o serviço docker no Linux: docker info deve responder), o kit clonado, um terminal aberto na pasta do kit (a que contém docker-compose.yml e labo.sh).
  • Se o bash responder Permission denied em ./labo.sh: chmod +x labo.sh, uma única vez.
  • O laboratório pode estar iniciado ou não: a prática começa pelos pré-requisitos. No fim, ./labo.sh etat deve exibir (healthy) em todos os lugares, "status":"green", acces 12000 avis 609 cours 504 e nœuds : 872.
  • Todas as consultas desta prática: elasticsearch/requetes/01-pratique-demarrer-verifier-reparer.txt. Nenhuma escreve no cluster.

B.1 — Percorrer a lista de verificação dos pré-requisitos

Antes de ligar qualquer coisa, faça o script falar.

bash
./labo.sh prerequis

Ponto de controle: só vistos verdes e a frase final.

text
== Prérequis ==
  ✔ docker : Docker version 29.3.1, build c2be9cc
  ✔ le démon Docker répond
  ✔ docker compose : 5.1.1
  ✔ mémoire disponible pour Docker : 31 Go
  ✔ processeurs : 20
  ✔ port 9200 : utilisé par le labo lui-même
  ✔ port 5601 : utilisé par le labo lui-même
  ✔ port 7474 : utilisé par le labo lui-même
  ✔ port 7687 : utilisé par le labo lui-même

Tout est prêt. Lancez : ./labo.sh demarrer

Na sua máquina, versões e memória diferem, e se o laboratório nunca rodou as quatro últimas linhas dizem port 9200 : libre (livre). As duas leituras são boas.

Se você vir outra coisa: uma cruz contém seu remédio na frase (Docker não iniciado, memória abaixo de 4 GB, porta ocupada); corrija, reexecute, só avance com a linha verde. No Linux nativo, a memória é a da máquina; no macOS e WSL 2, é a alocada em Docker Desktop → SettingsResources.

B.2 — Iniciar e ler os três blocos

Execute a inicialização e, desta vez, leia o que passa na tela em vez de esperar o fim.

bash
./labo.sh demarrer

Ponto de controle: três blocos == … == e depois três prêt.

text
== Téléchargement des images (long la première fois : ~6 Go, ~12 Go avec OpenSearch) ==

== Démarrage ==

== Attente que chaque service soit prêt ==
  elasticsearch            prêt (0 s)
  kibana                   prêt (0 s)
  neo4j                   ..... prêt (15 s)

Le labo est prêt.
  Kibana                 http://localhost:5601   (Dev Tools : menu ☰ → Management → Dev Tools)
  Elasticsearch          http://localhost:9200
  Neo4j Browser          http://localhost:7474   (utilisateur neo4j · mot de passe aiopsatlas2026)

Étape suivante : ./labo.sh importer   puis   ./labo.sh charger-graphe

O primeiro bloco fica vazio se as imagens já estão lá; o segundo contém as linhas do Compose (Container labo-elasticsearch Started, ou Running se já estava rodando); o terceiro acrescenta um ponto a cada três segundos até prêt (… s). Laboratório frio: um a dois minutos; laboratório já em funcionamento: prêt (0 s).

Se você vir outra coisa: unhealthy, exited ou délai dépassé./labo.sh journal <serviço> e o catálogo da lição 04; causas mais frequentes: a memória (Exited (137)) e, no Linux nativo, vm.max_map_count muito baixo (sudo sysctl -w vm.max_map_count=262144, depois ./labo.sh demarrer).

B.3 — Anotar os quatro números de etat

Você vai digitar etat várias vezes; aprenda primeiro a extrair dele os quatro números que contam.

bash
./labo.sh etat

Ponto de controle: saída real da máquina do curso, onde o perfil OpenSearch do módulo 5 está ativo. Na sua máquina, as duas linhas labo-opensearch… e ✔ OpenSearch são substituídas por — OpenSearch non démarré (profil optionnel : ./labo.sh demarrer opensearch).

text
== Conteneurs ==
NAME                         STATUS                    PORTS
labo-elasticsearch           Up 10 hours (healthy)     0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp
labo-kibana                  Up 10 hours (healthy)     0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp
labo-neo4j                   Up 37 seconds (healthy)   0.0.0.0:7474->7474/tcp, [::]:7474->7474/tcp, 0.0.0.0:7687->7687/tcp, [::]:7687->7687/tcp
labo-opensearch              Up 10 hours (healthy)     0.0.0.0:9201->9200/tcp, [::]:9201->9200/tcp
labo-opensearch-dashboards   Up 10 hours               0.0.0.0:5602->5601/tcp, [::]:5602->5601/tcp

== Services ==
  ✔ Elasticsearch : {"status":"green","number_of_nodes":1}
     index : acces 12000 avis    609 cours   504
  ✔ Kibana répond (http://localhost:5601)
  ✔ Neo4j répond — nœuds : 872
  ✔ OpenSearch : {"status":"green","number_of_nodes":1}

Anote os quatro números esperados: 12000, 609, 504, 872. Em um laboratório novinho você lê index : aucun index du labo (nenhum índice do laboratório) e nœuds : 0: normal, a etapa B.4 os preenche.

Se você vir outra coisa: ✘ Kibana ne répond pas encore (Kibana ainda não responde) no minuto seguinte a demarrer → o Kibana termina de criar seus índices internos; redigite etat trinta segundos depois.

B.4 — Carregar os dados, depois recarregar para provar que nada muda

Carregue os índices e depois o grafo, e reexecute os dois comandos uma segunda vez: os contadores não devem mudar em uma unidade.

bash
./labo.sh importer
./labo.sh charger-graphe
./labo.sh importer
./labo.sh charger-graphe

Ponto de controle: na segunda passada, importer sinaliza que os índices já existem e devolve os mesmos contadores:

text
== Import dans elasticsearch ==
— index cours existe déjà — conservé
  ✔ données cours chargées
— index avis existe déjà — conservé
  ✔ données avis chargées
— index acces existe déjà — conservé
  ✔ données acces chargées

index docs.count store.size
acces      12000      1.5mb
avis         609     74.8kb
cours        504    183.6kb

Import terminé. Attendu : cours = 504, avis = 609, acces = 12000.

e charger-graphe dá o mesmo balanço que na primeira:

text
== Chargement du graphe Neo4j ==
  ✔ contraintes et index en place
etiquette, noeuds
"Competence", 22
"Cours", 504
"Etudiant", 300
"Professeur", 30
"Ville", 16

Graphe chargé. Attendu : Competence 22, Cours 504, Etudiant 300, Professeur 30, Ville 16.

Os documentos carregam seu próprio _id (C0001, A00001…), portanto um segundo _bulk substitui cada documento em vez de acrescentá-lo; do lado do Neo4j, tudo está em MERGE. Só o store.size pode variar alguns kilobytes (o Lucene guarda por um momento as versões antigas); os docs.count, nunca.

Se você vir outra coisa: um docs.count diferente de 504 / 609 / 12000 → alguém escreveu nesses índices; ./labo.sh reinitialiser depois demarrer, importer, charger-graphe lhe devolve o estado de referência.

B.5 — Verificar o Elasticsearch a partir do Dev Tools

O script diz que tudo está bem; verifique isso sem ele. Abra http://localhost:5601, menu ManagementOutils de développement (Ferramentas de desenvolvimento), e envie estas consultas uma por uma (Ctrl + Enter, ou Cmd + Enter no macOS, ou o botão ▶ «Cliquer pour envoyer la requête», Clique para enviar a consulta).

text
GET _cluster/health

Ponto de controle: "status": "green", "unassigned_shards": 0, e 200 - OK embaixo à direita do painel de resposta.

json
{
  "cluster_name": "labo",
  "status": "green",
  "timed_out": false,
  "number_of_nodes": 1,
  "number_of_data_nodes": 1,
  "active_primary_shards": 53,
  "active_shards": 53,

  "unassigned_shards": 0,

  "active_shards_percent_as_number": 100
}
text
GET _cat/indices/cours,avis,acces?v&s=index

Ponto de controle: três linhas green, pri 1, rep 0, e os três contadores em docs.count.

text
health status index uuid                   pri rep docs.count docs.deleted store.size pri.store.size dataset.size
green  open   acces aii68fsfQXKyt5wqkE1mPA   1   0      12000            0      1.5mb          1.5mb        1.5mb
green  open   avis  W9j_JrwJT4mdzpWcS5k7xg   1   0        609            0     74.8kb         74.8kb       74.8kb
green  open   cours pmq403ZgSZWeHJY9uNw1Qw   1   0        504            0    183.6kb        183.6kb      183.6kb

Termine com GET cours/_count, GET avis/_count, GET acces/_count: "count": 504, 609, 12000. Você tem os mesmos números por dois caminhos independentes: o script (curl no contêiner) e o Dev Tools (via Kibana). Se um dia eles divergirem, é o caminho que está em causa, não os dados.

No bash, você tem até um terceiro caminho, sem navegador:

bash
curl -s 'http://localhost:9200/_cat/indices/cours,avis,acces?v&s=index'

Se você vir outra coisa: "status": "yellow" → um índice tem réplicas não alocadas, impossível com os mappings do kit (number_of_replicas: 0); GET _cat/indices?v&health=yellow aponta o culpado, em geral um índice criado à mão.

B.6 — Contar o grafo no Neo4j Browser

Mesmo exercício para o Neo4j. Abra http://localhost:7474, conecte-se (neo4j / aiopsatlas2026, URL localhost:7687), digite no editor neo4j$ e clique em Run:

cypher
MATCH (n) RETURN labels(n)[0] AS label, count(*) ORDER BY label

Ponto de controle: um quadro com duas visões, Table e Raw (sem Graph: a consulta retorna números, não nós), duas colunas label e count(*), cinco linhas ordenadas, e embaixo à direita Started streaming 5 records after … ms and completed after … ms.

text
label          count(*)
"Competence"   22
"Cours"        504
"Etudiant"     300
"Professeur"   30
"Ville"        16

22 + 504 + 300 + 30 + 16 = 872, o número de etat. O painel Database information (ícone Database overview, o primeiro da barra lateral) exibe Nodes (872) e Relationships (3,712).

Se você vir outra coisa: um sexto rótulo desconhecido → nós criados fora do kit (o módulo 6 ensinará a excluí-los corretamente); Nodes (0) → você pulou charger-graphe, volte à etapa B.4.

B.7 — Quebrar o Kibana voluntariamente e observar a falha

Você sabe como se parece um laboratório saudável; provoque uma falha cuja causa você conhece para aprender a lê-la. Pare somente o Kibana, com o Compose (não arreter, que pararia tudo):

bash
docker compose stop kibana
text
 Container labo-kibana Stopping
 Container labo-kibana Stopped

Depois os três gestos da lição 04, na ordem: etat, o navegador, journal.

Ponto de controle 1, ./labo.sh etat: a linha labo-kibana desapareceu do bloco == Conteneurs == (o Compose exibe por padrão só os contêineres em execução) e o bloco == Services == marca uma cruz:

text
== Conteneurs ==
NAME                         STATUS                   PORTS
labo-elasticsearch           Up 10 hours (healthy)    0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp
labo-neo4j                   Up 3 minutes (healthy)   0.0.0.0:7474->7474/tcp, [::]:7474->7474/tcp, 0.0.0.0:7687->7687/tcp, [::]:7687->7687/tcp
labo-opensearch              Up 10 hours (healthy)    0.0.0.0:9201->9200/tcp, [::]:9201->9200/tcp
labo-opensearch-dashboards   Up 10 hours              0.0.0.0:5602->5601/tcp, [::]:5602->5601/tcp

== Services ==
  ✔ Elasticsearch : {"status":"green","number_of_nodes":1}
     index : acces 12000 avis    609 cours   504
  ✘ Kibana ne répond pas encore
  ✔ Neo4j répond — nœuds : 872
  ✔ OpenSearch : {"status":"green","number_of_nodes":1}

Para ver mesmo assim o contêiner parado: docker compose ps -a exibe labo-kibana Exited (0) 31 seconds ago. O 0 diz "parada limpa, solicitada"; um 137 diria "morto, memória".

Ponto de controle 2, o navegador: recarregue http://localhost:5601. Nenhum "Kibana server is not ready yet" (essa frase vem do Kibana, mas não há mais Kibana para dizê-la), e sim o erro de conexão do próprio navegador: no Chrome, "Não é possível acessar esse site", código ERR_CONNECTION_REFUSED; no Firefox, "Falha na conexão". Na linha de comando, curl -s http://localhost:5601 || echo REFUSE exibe REFUSE: ninguém escuta na porta 5601. Para lembrar: página do Kibana que se desculpa = o Kibana roda mas aguarda o Elasticsearch; erro do navegador = o Kibana não está rodando.

Ponto de controle 3, ./labo.sh journal kibana: as cem últimas linhas terminam com uma parada limpa, datada do segundo em que você digitou stop:

text
labo-kibana  | [2026-09-09T23:44:06.893+00:00][INFO ][root] SIGTERM received - initiating shutdown
labo-kibana  | [2026-09-09T23:44:06.894+00:00][INFO ][root] Kibana is shutting down
labo-kibana  | [2026-09-09T23:44:06.902+00:00][INFO ][plugins-system.standard] Stopping all plugins.

labo-kibana  | [2026-09-09T23:44:07.265+00:00][INFO ][plugins-system.standard] All plugins stopped.

SIGTERM received assina uma parada solicitada (por você, por docker compose stop, por uma reinicialização do Docker). Nenhuma linha ERROR nem FATAL: nada a reparar, basta reiniciar. As dezenas de linhas at OperatorSubscriber… entre as duas são uma pilha de chamadas emitida por um plugin durante a parada: ruído.

Se você vir outra coisa: no configuration file provided: not found → você não está na pasta do kit; no such service: kibana → você digitou o nome do contêiner (labo-kibana) em vez do serviço Compose (kibana).

B.8 — Reparar o Kibana e provar que o Elasticsearch não viu nada

Reinicie o serviço e aguarde que seu healthcheck volte ao verde: 40 a 60 segundos, o tempo de se reconectar ao Elasticsearch e verificar seus índices internos.

bash
docker compose start kibana
text
 Container labo-elasticsearch Waiting
 Container labo-elasticsearch Healthy
 Container labo-kibana Starting
 Container labo-kibana Started

O Compose verificou primeiro que o Elasticsearch estava Healthy (o depends_on … service_healthy da lição 03), depois iniciou o Kibana. Monitore a cada cinco segundos:

bash
docker inspect --format '{{.State.Health.Status}}' labo-kibana

ou, para não redigitar, watch -n 5 docker inspect --format '{{.State.Health.Status}}' labo-kibana (Ctrl + C para sair; watch não vem por padrão no macOS, redigite o comando à mão).

Ponto de controle: starting durante 40 a 50 segundos, depois healthy (na máquina do curso: starting de 0 a 45 s, healthy aos 50 s). etat então mostra novamente a linha:

text
labo-kibana                  Up 52 seconds (healthy)   0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp

  ✔ Kibana répond (http://localhost:5601)

e journal kibana termina com as linhas que queremos ver:

text
labo-kibana  | [2026-09-09T23:47:49.408+00:00][INFO ][http.server.Kibana] http server running at http://0.0.0.0:5601
labo-kibana  | [2026-09-09T23:47:50.374+00:00][INFO ][status] Kibana is now available

Reabra o Dev Tools (se cair em "Kibana server is not ready yet", você foi mais rápido que o healthy: aguarde dez segundos) e envie a consulta que o próprio etat usa:

text
GET _cluster/health?filter_path=status,number_of_nodes
json
{
  "status": "green",
  "number_of_nodes": 1
}

O Elasticsearch respondeu green durante toda a falha: o Kibana é uma janela sobre os dados, não os dados. Parar o Kibana não apagou nem reindexou nada.

Se você vir outra coisa: unhealthy após dois minutos → journal kibana e procure ECONNREFUSED (Elasticsearch caído nesse meio-tempo); port is already allocated → outro programa ocupou a porta 5601 durante a parada (lição 04, falha 1).

B.9 — Sua vez: provocar e explicar um 404

Você reconhece um serviço parado; aprenda agora a reconhecer uma consulta errada em um serviço saudável, a confusão mais frequente em sala. No Dev Tools, escreva uma consulta de busca que retorne 404 com "type": "index_not_found_exception", depois explique em uma frase por que o cluster permanece green.

Dica: o Elasticsearch nunca adivinha o nome de um índice. Escolha um que não exista, com o prefixo pratique-; nada a criar, nada a escrever.

Solução
text
GET pratique-inexistant/_search

Resposta, com o selo 404 - Not Found embaixo à direita do painel de saída:

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

  },
  "status": 404
}

A mesma coisa na linha de comando, para ver o código HTTP puro: curl -s -o /dev/null -w '%{http_code}\n' http://localhost:9200/pratique-inexistant/_search exibe 404.

Explicação esperada: o 404 é uma resposta normal e completa do Elasticsearch: "entendi sua consulta, mas esse recurso não existe". O serviço roda, o cluster permanece green; só há o nome a corrigir (GET _cat/indices?v dá a lista). Na etapa B.7, não havia nenhuma resposta.

Duas variantes para experimentar: GET pratique-inexistant/_count?ignore_unavailable=true retorna 200 e "count": 0 (você pede para ignorar o índice ausente); GET cours/_serch retorna 400 com "no handler found for uri [/cours/_serch] and method [GET]". Um 400: "não entendo a consulta"; um 404: "entendo, mas isso não existe".

B.10 — Verificação final

Um único comando prova que tudo está feito: etat, com o Kibana de volta e os quatro números.

bash
./labo.sh etat
text
== Conteneurs ==
NAME                         STATUS                        PORTS
labo-elasticsearch           Up 10 hours (healthy)         0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp
labo-kibana                  Up About a minute (healthy)   0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp
labo-neo4j                   Up 7 minutes (healthy)        0.0.0.0:7474->7474/tcp, [::]:7474->7474/tcp, 0.0.0.0:7687->7687/tcp, [::]:7687->7687/tcp
labo-opensearch              Up 10 hours (healthy)         0.0.0.0:9201->9200/tcp, [::]:9201->9200/tcp
labo-opensearch-dashboards   Up 10 hours                   0.0.0.0:5602->5601/tcp, [::]:5602->5601/tcp

== Services ==
  ✔ Elasticsearch : {"status":"green","number_of_nodes":1}
     index : acces 12000 avis    609 cours   504
  ✔ Kibana répond (http://localhost:5601)
  ✔ Neo4j répond — nœuds : 872
  ✔ OpenSearch : {"status":"green","number_of_nodes":1}

(Sem o perfil OpenSearch, os dois contêineres labo-opensearch… não aparecem e a última linha diz — OpenSearch non démarré …: é o estado esperado até o módulo 5.)

  • prerequis termina com Tout est prêt.
  • labo-elasticsearch, labo-kibana e labo-neo4j estão Up … (healthy), Kibana incluído.
  • etat exibe "status":"green", acces 12000 avis 609 cours 504, nœuds : 872, inalterados após o segundo importer / charger-graphe.
  • Os mesmos contadores foram relidos no Dev Tools e no Neo4j Browser.
  • Você sabe dizer o que mostram etat, o navegador e journal kibana quando o Kibana está parado, e em que isso difere de um 404.
  • Você guardou a saída de etat acima (cópia ou captura) como entregável.

B.11 — Limpeza

Esta prática não cria nada: nem índice, nem nó, nem objeto Kibana. Duas coisas a garantir: que o Kibana está rodando (senão docker compose start kibana a partir da pasta do kit), e que nenhum índice de trabalho ficou para trás:

text
GET _cat/indices/pratique-*?v

Resposta esperada: só a linha de cabeçalho (health status index uuid pri rep docs.count …). Não toque em cours, avis, acces nem no grafo: eles servem a todos os módulos seguintes.

Anexo C — Se travar (todos os sistemas)

Mostrar os casos que travam
  • docker compose stop kibana responde no configuration file provided: not found → O Compose procura docker-compose.yml na pasta atual. Faça cd para a raiz do kit (a que contém labo.sh e labo.ps1) e recomece. O script se reposiciona sozinho; os comandos docker compose digitados à mão, não.

  • Após stop, etat não mostra mais labo-kibana e você acha que o excluiu → Não: docker compose ps esconde os contêineres parados. docker compose ps -a o lista em Exited (0), e docker compose start kibana o reinicia com seus dados. Um contêiner realmente excluído não apareceria nem mesmo com -a; demarrer o recriaria.

  • O Kibana permanece starting e depois passa a unhealthy, e journal kibana repete Unable to retrieve version information from Elasticsearch nodes. connect ECONNREFUSED 172.x.x.x:9200 → O Kibana voltou mas o Elasticsearch caiu nesse meio-tempo (frequentemente Exited (137), a memória). Repare o Elasticsearch primeiro (lição 04, falha 2); o Kibana se reconecta sozinho.

  • O Dev Tools retorna 400 em vez do 404 esperado, com no handler found for uri [/pratique-inexistant/_serch] and method [GET] → O erro está na API (_serch), não no índice. O Elasticsearch valida primeiro a rota, depois o índice: corrija para _search e o 404 aparece.

  • Somente Windows — no PowerShell 5.1, demarrer exibe em vermelho docker : Image docker.elastic.co/kibana/kibana:9.5.3 Pulling … NativeCommandError mas termina com Le labo est prêt → Isso só acontece se você redirecionar a saída (2>&1, | Tee-Object): o Compose escreve seu progresso no fluxo de erro e o PowerShell 5.1 o veste como exceção. Não é um erro; execute o script sem redirecionamento, ou passe para o PowerShell 7.

  • Somente Windows — .\labo.ps1 é recusado: «l'exécution de scripts est désactivée sur ce système» (a execução de scripts está desabilitada neste sistema)Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, responda O, reexecute. Uma única vez por máquina.

  • Somente Linux nativo — o Elasticsearch sai em Exited (78) e journal elasticsearch diz max virtual memory areas vm.max_map_count [65530] is too lowsudo sysctl -w vm.max_map_count=262144 depois ./labo.sh demarrer. Para que sobreviva à reinicialização: acrescente vm.max_map_count=262144 em /etc/sysctl.conf.

  • macOS e bash — ./labo.sh responde Permission deniedchmod +x labo.sh, uma única vez. Se bash: ./labo.sh: /bin/bash^M: bad interpreter, o arquivo tem fins de linha Windows: git config core.autocrlf input e depois clone de novo, ou sed -i '' 's/\r$//' labo.sh.