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.
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.
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)
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 etatVerificar as três URLs no navegador:
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).\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 : 872Se 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
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 etatVerificar as três URLs no navegador:
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)./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 : 872Antes 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:
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_DEAbra-os você mesmo, leva dez segundos e você saberá exatamente o que está manipulando:
# 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# 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.csvUm 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.
{"index":{"_index":"cours","_id":"C0001"}}
{"id":"C0001","titre":"Docker expliqué simplement","description":"…","categorie":"DevOps", … }cours: 504 documentos, um por curso do catálogoO primeiro documento, tal como está no Elasticsearch:
{
"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"]
}| Campo | Exemplo | O que é |
|---|---|---|
id | C0001 | Identificador do curso. C de curso (cours), depois um número. É também o _id do documento. |
titre | Docker expliqué simplement | O título. Texto livre, é sobre ele que faremos as buscas por palavras. |
description | Dans ce cours… | Um parágrafo de apresentação. Texto livre também. |
categorie | DevOps | Uma das seis grandes famílias: Cloud, DevOps, Données, Développement web, IA, Sécurité. 84 cursos cada. |
sujet | Docker | Mais preciso que a categoria: Docker, Kubernetes, Neo4j, Elasticsearch… |
niveau | debutant | debutant, intermediaire ou avance. |
langue | en | fr ou en. |
prix | 129 | Em dólares. 0 para um curso gratuito. |
gratuit | false | Verdadeiro ou falso. |
duree_heures | 5 | Duração total do curso. |
tags | ["docker", "linux", …] | Uma lista de palavras-chave. Um campo pode conter vários valores. |
date_publication | 2024-08-14 | Uma data. |
note_moyenne | 4.4 | Média das notas recebidas, de 5. |
nb_avis | 327 | Nú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. |
avis: 609 documentos, um por avaliação deixada por um estudante{
"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
}| Campo | Exemplo | O que é |
|---|---|---|
id | A00001 | Identificador da avaliação. A de avaliação (avis). |
cours_id | C0028 | O curso em questão. É o vínculo com o índice cours: esse C0028 é o id de um documento de cours. |
etudiant | Nathan | Primeiro nome do autor. |
ville, pays | Sherbrooke, Canada | De onde ele escreve. |
note | 3 | A nota dada, de 1 a 5. |
texte | Les vidéos sont bonnes… | O comentário. Texto livre. |
date | 2024-06-21 | Data da avaliação. |
utile | 33 | Número de pessoas que acharam esta avaliação útil. |
acces: 12.000 documentos, uma linha por requisição recebida pelo servidor web{
"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"
}| Campo | Exemplo | O que é |
|---|---|---|
id | L000001 | Identificador da linha. L de linha de log. |
@timestamp | 2026-08-10T08:17:19.000Z | Data e hora exatas da requisição. O @ é uma convenção: é o campo de tempo que o Kibana detecta automaticamente. |
methode | GET | GET ou POST. |
chemin | /robots.txt | O endereço solicitado no site: /, /cours, /catalogue, /contact, /tarifs, ou a página de um curso. |
cours_id | null ou C0042 | O 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. |
categorie | null ou DevOps | Categoria do curso consultado, copiada para simplificar os gráficos. |
statut | 200 | O código HTTP da resposta: 200 OK, 301 e 304 redirecionamento ou cache, 404 não encontrado, 500 e 503 erro de servidor. |
octets | 108506 | Tamanho da resposta enviada. |
duree_ms | 100 | Tempo de resposta em milissegundos. |
ip | 108.190.166.1 | Endereço IP do visitante. |
pays | CA | País do visitante, código de duas letras. |
appareil | desktop | desktop, mobile ou tablette. |
navigateur | Edge | Chrome, Firefox, Safari, Edge… |
referent | google | De 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.
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:
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,C0003Veja 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.
| Nó | Propriedades | Exemplo |
|---|---|---|
Cours | id, titre, categorie, sujet, niveau, prix, duree_heures, date_publication | C0001, «Docker expliqué simplement», 129 $ |
Etudiant | id, prenom, nom, interet, inscription_le | E0001, Nathan Ben Ali, interesse DevOps |
Professeur | id, prenom, nom, specialite, annees_experience | P001, Karim Caron, DevOps, 23 anos |
Competence | id, nom | K01, Conteneurisation |
Ville | nom, pays, latitude, longitude | Montréal, Canada |
| Relação | De → para | Propriedades | Sentido |
|---|---|---|---|
INSCRIT_A | Etudiant → Cours | date, progression, note | O estudante faz este curso |
ENSEIGNE | Professeur → Cours | — | Este professor dá este curso (um único por curso) |
COUVRE | Cours → Competence | — | Este curso ensina esta competência |
PREREQUIS_DE | Cours → Cours | — | É preciso ter feito o primeiro antes do segundo |
HABITE | Etudiant ou Professeur → Ville | — | Onde a pessoa mora |
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.
| Prefixo | Coisa | Exemplo | Onde se encontra |
|---|---|---|---|
C | Curso (cours) | C0001 | Elasticsearch cours, avis.cours_id, acces.cours_id, Neo4j Cours |
A | Avaliação (avis) | A00001 | Elasticsearch avis |
L | Linha de log | L000001 | Elasticsearch acces |
E | Estudante (étudiant) | E0001 | Neo4j Etudiant |
P | Professor | P001 | cours.professeur.id no Elasticsearch, Neo4j Professeur |
K | Competência | K01 | Neo4j 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.
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.
Abra http://localhost:5601, depois menu ☰ → Management → Dev 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.
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 dentroAs 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.
GET _cat/indicesO que a consulta pede: "Elasticsearch, mostre-me a lista de todas as suas gavetas." Resposta na máquina do curso:
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.6kbQuatro 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ço | O 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. |
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:
| Elasticsearch | Na imagem | SQL |
|---|---|---|
Índice cours | A gaveta "cours" | Tabela cours |
| Documento JSON | Uma ficha na gaveta | Linha |
| Campo | Um quadro na ficha (título, preço…) | Coluna |
_id do documento | O número escrito no alto da ficha | Chave primária |
As três gavetas do laboratório:
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:
green open avis W9j_JrwJT4mdzpWcS5k7xg 1 0 609 0 74.6kb 74.6kb 74.6kb| Valor | O que significa |
|---|---|
green | A gaveta está em bom estado. Verde = tudo bem. |
open | A gaveta está aberta: pode-se ler e escrever dentro. (O contrário seria close.) |
avis | O nome da gaveta. |
W9j_JrwJT4mdzpWcS5k7xg | Um número de série técnico, gerado pelo Elasticsearch. Você nunca vai usá-lo. |
1 | A 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. |
0 | Zero 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. |
609 | A gaveta contém 609 fichas. É o valor que olhamos primeiro. |
0 | Zero ficha marcada "para jogar fora" aguardando limpeza. |
74.6kb | Espaço total ocupado no disco. |
74.6kb | Espaço ocupado somente pelo pedaço primário (idêntico: há um só pedaço e nenhuma cópia). |
74.6kb | Tamanho 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:
avis → 609 fiches → 74,6 Ko
acces → 12 000 fiches → 1,5 Mo
cours → 504 fiches → 183,6 Ko?vGET _cat/indices?vhealth 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.6kbMesma 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?
? diz: "o que segue são opções". Ele separa o comando (_cat/indices) dos seus ajustes.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.
| Coluna | Em termos simples |
|---|---|
health | Saú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. |
status | open: utilizável. close: fechada, não se pode nem ler nem escrever. |
index | O nome da gaveta. |
uuid | Número de série técnico. Nunca usado à mão. |
pri | Em quantos pedaços (primary shards) a gaveta está dividida. 1 no laboratório. |
rep | Quantas cópias de segurança (replicas) de cada pedaço. 0 no laboratório. |
docs.count | Número de fichas. A coluna a olhar primeiro. |
docs.deleted | Fichas marcadas "para jogar fora" ainda não limpas do disco. |
store.size | Espaço total no disco. |
pri.store.size | Espaço somente dos pedaços primários, sem as cópias. |
dataset.size | Tamanho 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.
GET _cat/indices/cours?vhealth 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.6kbO 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:
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.
GET _cat/indices/acces,avis,cours?v&s=index&h=index,health,docs.count,store.sizeindex health docs.count store.size
acces green 12000 1.5mb
avis green 609 74.6kb
cours green 504 183.6kbO 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ço | Em termos simples |
|---|---|
GET | Peço uma informação, não modifico nada. |
_cat/indices | As etiquetas das gavetas, em tabela legível. |
/acces,avis,cours | Somente estas três gavetas (nomes separados por vírgulas, sem espaço). A gaveta interna do Kibana desaparece. |
?v | Com os títulos das colunas. |
&s=index | s de sort: ordena as linhas em ordem alfabética da coluna index. |
&h=index,health,docs.count,store.size | h 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:
| Coluna | Em termos simples |
|---|---|
index | O nome da gaveta. |
health | Sua saúde: green tudo funciona; yellow as fichas existem mas cópias de segurança faltam; red algumas fichas estão indisponíveis. |
docs.count | Quantas fichas ela contém. |
store.size | Quanto 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.
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=:
GET _cat/indices/acces,avis,cours?v&s=index&h=index,health,status,docs.count,store.sizeindex health status docs.count store.size
acces green open 12000 1.5mb
avis green open 609 74.6kb
cours green open 504 183.6kbBalanç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.
GET cours/_count{
"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.
GET cours/_searchO 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:
{
"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?volha a etiqueta da gaveta: seu estado, seu número de fichas, seu tamanho. Uma linha. Você não vê nenhum curso.
GET cours/_searchabre 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".
| Chave | Em termos simples |
|---|---|
took | O tempo que o Elasticsearch levou, em milissegundos (aqui 1 ms). |
hits.total.value | O número total de fichas que correspondem: 504. Mesmo que só lhe mostrem 10, ele diz quantas há no total. |
hits.hits | A lista das fichas que lhe mostram: as 10 primeiras por padrão. |
_index | A gaveta de onde vem a ficha (cours). |
_id | O número escrito no alto da ficha (C0001, C0002…). |
_score | Uma nota de relevância. Vale 1 em todos os lugares aqui porque não buscamos nada de preciso: todas as fichas se equivalem. |
_source | A 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.
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.
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.
GET cours/_search
{
"size": 3,
"_source": ["titre", "prix"]
}"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.
GET cours/_doc/C0001{
"_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.
_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).
GET cours/_search
{
"query": { "match": { "titre": "kubernetes" } },
"_source": ["titre"]
}"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.
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.
Primeiro a prova de que, por padrão, um erro de verdade não encontra nada. "kubrenetes" (duas letras invertidas):
GET cours/_count
{
"query": { "match": { "titre": "kubrenetes" } }
}{ "count": 0, … }Depois a mesma consulta com uma única linha a mais, fuzziness, que autoriza uma ou duas letras de diferença:
GET cours/_search
{
"query": { "match": { "titre": { "query": "kubrenetes", "fuzziness": "AUTO" } } },
"size": 3,
"_source": ["titre", "niveau", "prix"]
}"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.
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 re ↔ er): 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.
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.
GET cours/_search
{
"size": 0,
"aggs": {
"par_categorie": { "terms": { "field": "categorie" } }
}
}"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.
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.
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.
GET acces/_search
{
"size": 0,
"query": { "range": { "statut": { "gte": 500 } } },
"aggs": { "codes": { "terms": { "field": "statut" } } }
}"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.
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.
MATCH (n) RETURN nO 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.
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.
| Neo4j | SQL |
|---|---|
Rótulo Cours | Tabela cours |
| Nó | Linha |
| Propriedade | Coluna |
Relação INSCRIT_A | Tabela de ligação inscriptions + junções |
O grafo do laboratório:
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 relationsOs 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.
MATCH (n) RETURN n LIMIT 25Uma ú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.
MATCH (n) RETURN count(n) AS totaltotal
872count(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(*).
MATCH (c:Cours) RETURN c LIMIT 5Novidade: :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.
MATCH (c:Cours) RETURN c.titre, c.prix LIMIT 5c.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.
MATCH (n) RETURN labels(n)[0] AS type, count(*) AS nombre ORDER BY nombre DESCtype nombre
"Cours" 504
"Etudiant" 300
"Professeur" 30
"Competence" 22
"Ville" 16labels(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.
MATCH (c:Cours {id: 'C0001'}) RETURN c.titre, c.prix, c.niveauc.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.
MATCH (c:Cours) WHERE c.titre CONTAINS 'Kubernetes' RETURN c.titre ORDER BY c.titrec.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.
MATCH (c:Cours) RETURN c.titre, c.prix ORDER BY c.prix DESC LIMIT 5c.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 $.
MATCH (c:Cours) RETURN c.categorie AS categorie, count(*) AS nombre ORDER BY nombre DESCcategorie nombre
"DevOps" 84
"Données" 84
"IA" 84
"Développement web" 84
"Sécurité" 84
"Cloud" 84Exatamente 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.
MATCH (p:Professeur)-[r:ENSEIGNE]->(c:Cours) RETURN p, r, c LIMIT 30Passe 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.
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.
MATCH ()-[r]->() RETURN type(r) AS relation, count(*) AS nombre ORDER BY nombre DESCrelation 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.
MATCH (p:Professeur {id: 'P001'})-[:ENSEIGNE]->(c:Cours)
RETURN p.prenom + ' ' + p.nom AS professeur, count(c) AS nb_coursprofesseur nb_cours
"Karim Caron" 11Combinamos 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.
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 parcourssauts 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.
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.
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 5recommandation 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.
MATCH chemin = (x:Cours {id: 'C0213'})<-[:INSCRIT_A]-(:Etudiant)-[:INSCRIT_A]->(autre:Cours)
WHERE autre <> x
RETURN chemin LIMIT 50Passe 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.
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.
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.
docker-compose.yml e labo.ps1)..\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..\labo.ps1 etat deve exibir (healthy) em todos os lugares, "status":"green", acces 12000 avis 609 cours 504 e nœuds : 872.elasticsearch/requetes/01-pratique-demarrer-verifier-reparer.txt. Nenhuma escreve no cluster.Antes de ligar qualquer coisa, faça o script falar.
.\labo.ps1 prerequisPonto de controle: só vistos verdes e a frase final.
== 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 demarrerNa 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 → Settings → Resources, ou o arquivo %UserProfile%\.wslconfig se o Docker Desktop usar WSL 2.
Execute a inicialização e, desta vez, leia o que passa na tela em vez de esperar o fim.
.\labo.ps1 demarrerPonto de controle: três blocos == … == e depois três prêt.
== 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-grapheO 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)).
etatVocê vai digitar etat várias vezes; aprenda primeiro a extrair dele os quatro números que contam.
.\labo.ps1 etatPonto 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).
== 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.
Carregue os índices e depois o grafo, e reexecute os dois comandos uma segunda vez: os contadores não devem mudar em uma unidade.
.\labo.ps1 importer
.\labo.ps1 charger-graphe
.\labo.ps1 importer
.\labo.ps1 charger-graphePonto de controle: na segunda passada, importer sinaliza que os índices já existem e devolve os mesmos contadores:
== 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:
== 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.
O script diz que tudo está bem; verifique isso sem ele. Abra http://localhost:5601, menu ☰ → Management → Outils 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).
GET _cluster/healthPonto de controle: "status": "green", "unassigned_shards": 0, e 200 - OK embaixo à direita do painel de resposta.
{
"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
}GET _cat/indices/cours,avis,acces?v&s=indexPonto de controle: três linhas green, pri 1, rep 0, e os três contadores em docs.count.
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.6kbTermine 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.
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:
MATCH (n) RETURN labels(n)[0] AS label, count(*) ORDER BY labelPonto 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.
label count(*)
"Competence" 22
"Cours" 504
"Etudiant" 300
"Professeur" 30
"Ville" 1622 + 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.
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):
docker compose stop kibana Container labo-kibana Stopping
Container labo-kibana StoppedDepois 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:
== 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:
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).
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.
docker compose start kibana Container labo-elasticsearch Waiting
Container labo-elasticsearch Healthy
Container labo-kibana Starting
Container labo-kibana StartedO 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:
docker inspect --format '{{.State.Health.Status}}' labo-kibanaPonto 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:
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:
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 availableReabra 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:
GET _cluster/health?filter_path=status,number_of_nodes{
"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).
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.
GET pratique-inexistant/_searchResposta, com o selo 404 - Not Found embaixo à direita do painel de saída:
{
"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".
Um único comando prova que tudo está feito: etat, com o Kibana de volta e os quatro números.
.\labo.ps1 etat== 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.etat, o navegador e journal kibana quando o Kibana está parado, e em que isso difere de um 404.etat acima (cópia ou captura) como entregável.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:
GET _cat/indices/pratique-*?vResposta 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.
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.
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).Permission denied em ./labo.sh: chmod +x labo.sh, uma única vez../labo.sh etat deve exibir (healthy) em todos os lugares, "status":"green", acces 12000 avis 609 cours 504 e nœuds : 872.elasticsearch/requetes/01-pratique-demarrer-verifier-reparer.txt. Nenhuma escreve no cluster.Antes de ligar qualquer coisa, faça o script falar.
./labo.sh prerequisPonto de controle: só vistos verdes e a frase final.
== 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 demarrerNa 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 → Settings → Resources.
Execute a inicialização e, desta vez, leia o que passa na tela em vez de esperar o fim.
./labo.sh demarrerPonto de controle: três blocos == … == e depois três prêt.
== 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-grapheO 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).
etatVocê vai digitar etat várias vezes; aprenda primeiro a extrair dele os quatro números que contam.
./labo.sh etatPonto 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).
== 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.
Carregue os índices e depois o grafo, e reexecute os dois comandos uma segunda vez: os contadores não devem mudar em uma unidade.
./labo.sh importer
./labo.sh charger-graphe
./labo.sh importer
./labo.sh charger-graphePonto de controle: na segunda passada, importer sinaliza que os índices já existem e devolve os mesmos contadores:
== 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:
== 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.
O script diz que tudo está bem; verifique isso sem ele. Abra http://localhost:5601, menu ☰ → Management → Outils 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).
GET _cluster/healthPonto de controle: "status": "green", "unassigned_shards": 0, e 200 - OK embaixo à direita do painel de resposta.
{
"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
}GET _cat/indices/cours,avis,acces?v&s=indexPonto de controle: três linhas green, pri 1, rep 0, e os três contadores em docs.count.
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.6kbTermine 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:
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.
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:
MATCH (n) RETURN labels(n)[0] AS label, count(*) ORDER BY labelPonto 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.
label count(*)
"Competence" 22
"Cours" 504
"Etudiant" 300
"Professeur" 30
"Ville" 1622 + 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.
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):
docker compose stop kibana Container labo-kibana Stopping
Container labo-kibana StoppedDepois 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:
== 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:
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).
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.
docker compose start kibana Container labo-elasticsearch Waiting
Container labo-elasticsearch Healthy
Container labo-kibana Starting
Container labo-kibana StartedO 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:
docker inspect --format '{{.State.Health.Status}}' labo-kibanaou, 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:
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:
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 availableReabra 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:
GET _cluster/health?filter_path=status,number_of_nodes{
"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).
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.
GET pratique-inexistant/_searchResposta, com o selo 404 - Not Found embaixo à direita do painel de saída:
{
"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".
Um único comando prova que tudo está feito: etat, com o Kibana de volta e os quatro números.
./labo.sh etat== 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.etat, o navegador e journal kibana quando o Kibana está parado, e em que isso difere de um 404.etat acima (cópia ou captura) como entregável.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:
GET _cat/indices/pratique-*?vResposta 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.
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 low → sudo 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 denied → chmod +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.