Cómo leer esta página. Ocho pasos, una consulta a la vez. Para cada uno: la consulta que escribes, la respuesta exacta del laboratorio, y qué mirar en ella. Escribe tú mismo cada consulta (sin copiar y pegar): es escribiendo
PUT,GET,_docque las palabras se fijan. Los bloques «Para entender bien» son opcionales; ábrelos si un paso te deja una duda. Si el laboratorio no está iniciado, vuelve a la práctica guiada: la sección En resumen da los comandos, kit incluido (https://github.com/hrhouma2/aiopsatlas-recherche-graphes-labo-fr).
La práctica guiada te hizo cargar 504 cursos, 609 reseñas y 12 000 líneas de registro de una sola vez, con un script. Viste los números, pero aún no has escrito nada tú mismo. Aquí, partes de cero: un índice vacío que creas, una primera ficha que guardas dentro, que vuelves a leer, que completas, luego una segunda, una búsqueda, una eliminación. Al final, sabes qué es un índice y un documento porque fabricaste uno, no porque te lo dijeron.
Un índice es un armario. Un documento es una ficha guardada en el armario: un pequeño texto JSON con campos. Cada ficha tiene un número, su _id, que permite encontrarla directamente sin buscar. No tienes que declarar las columnas de antemano: la primera ficha guardada crea el mapping (el plano del armario) por sí sola.
| Elasticsearch | Base SQL clásica | En esta práctica |
|---|---|---|
| índice | tabla | pratique-mini |
| documento | fila | {"titre": "Mon premier document"} |
| campo | columna | titre, auteur, note |
_id | clave primaria | 1, 2 |
| mapping | esquema de la tabla (CREATE TABLE …) | creado automáticamente en el paso 3 |
_source | la fila tal como la escribiste | lo que GET _doc/1 te devuelve |
Abre http://localhost:5601, menú ☰ → Management → Dev Tools. El panel izquierdo recibe las consultas, el de la derecha muestra la respuesta. Envías con Ctrl + Enter (Cmd + Enter en macOS) o el botón ▶ a la derecha de la línea.
Toda consulta tiene la misma forma: un verbo, una ruta, y a veces un cuerpo JSON debajo.
| Verbo | Qué hace | Equivalente SQL |
|---|---|---|
GET | leer, sin cambiar nada | SELECT |
PUT | crear, o reemplazar por completo | CREATE TABLE, INSERT (o reemplazar la fila) |
POST | actuar: actualizar, buscar con un cuerpo | UPDATE |
DELETE | eliminar | DROP TABLE, DELETE |
La ruta dice sobre qué se actúa: pratique-mini (el armario), pratique-mini/_doc/1 (la ficha número 1 del armario), pratique-mini/_search (buscar en el armario). Las palabras que empiezan con _ son comandos de Elasticsearch, no nombres tuyos.
PUT pratique-miniQué pide la consulta: crea un índice llamado pratique-mini. Nada más: sin columnas, sin contenido.
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "pratique-mini"
}A observar: "acknowledged": true, «está hecho», y el nombre como eco. La insignia en la parte superior derecha de la respuesta dice 200 - OK.
pratique- delante? Todos los índices que creas en este curso llevan este prefijo. Así, GET _cat/indices/pratique-*?v lista todo lo que es tuyo y nada más, y cours, avis, acces permanecen intactos./. Pratique-Mini sería rechazado.400 con resource_already_exists_exception: el armario ya existe. No es un fallo, es una respuesta.GET _cat/indices/pratique-mini?vQué pide la consulta: una línea de resumen sobre este índice, con la línea de encabezado (?v, verbose).
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size dataset.size
yellow open pratique-mini YHjAfGXBTwSZcl7CLcc2jw 1 1 0 0 227b 227b 227bA observar: docs.count 0, el armario está vacío. Y health yellow con rep 1: Elasticsearch previó una copia de respaldo (una réplica) de tu índice en una segunda máquina, y el laboratorio solo tiene una. La copia no puede colocarse en ningún lado, de ahí el amarillo. Los tres índices del kit están en verde porque su mapping fija number_of_replicas: 0. Haz lo mismo, en una consulta:
PUT pratique-mini/_settings
{
"index": { "number_of_replicas": 0 }
}{
"acknowledged": true
}Vuelve a escribir GET _cat/indices/pratique-mini?v:
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size dataset.size
green open pratique-mini YHjAfGXBTwSZcl7CLcc2jw 1 0 0 0 227b 227b 227bA observar: green, rep 0. Tu uuid será diferente: es el identificador interno del índice, elegido al azar en la creación.
GET _cluster/health decía "status": "yellow" para todo el cluster: el color del cluster es el peor color de sus índices. Es la explicación del fallo «yellow» del catálogo de la lección 04.GET pratique-mini (sin _cat) devuelve la ficha completa del índice: "mappings": { } (vacío, ninguna ficha guardada) y "settings" con number_of_replicas.PUT pratique-mini/_doc/1
{
"titre": "Mon premier document"
}Qué pide la consulta: en el armario pratique-mini, guarda una ficha (_doc) número 1 que contiene un campo titre.
{
"_index": "pratique-mini",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}A observar: "result": "created" y "_version": 1: primera versión de la ficha 1. Equivalente SQL: INSERT INTO pratique_mini (id, titre) VALUES (1, 'Mon premier document'), salvo que no fue necesario ningún CREATE TABLE.
GET pratique-mini/_mapping: el campo titre ahora está declarado de tipo text (para buscar palabras dentro) con un subcampo titre.keyword (para ordenar o filtrar por el valor exacto). Elasticsearch lo dedujo del valor "Mon premier document", una cadena.1 de _doc/1 lo elegiste tú. Los documentos del kit hacen lo mismo (C0001, A00001…). Si escribes POST pratique-mini/_doc sin número, Elasticsearch inventa un _id de veinte caracteres; práctico para registros, molesto para una ficha que quieres encontrar a mano._shards, _seq_no, _primary_term son contabilidad interna (en cuántos fragmentos se confirmó la escritura, qué número de orden). No los necesitas en este curso.GET pratique-mini/_doc/1Qué pide la consulta: dame la ficha número 1 de pratique-mini, directamente, sin buscar.
{
"_index": "pratique-mini",
"_id": "1",
"_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"found": true,
"_source": {
"titre": "Mon premier document"
}
}A observar: "found": true, y _source, tu ficha tal como la escribiste, al carácter exacto. Equivalente SQL: SELECT * FROM pratique_mini WHERE id = 1.
Prueba con una ficha que no existe: GET pratique-mini/_doc/3.
{
"_index": "pratique-mini",
"_id": "3",
"found": false
}Sin error, sin _source: "found": false, insignia 404 - Not Found. Elasticsearch entendió la pregunta; la respuesta es «no hay nada en ese número».
POST pratique-mini/_update/1
{
"doc": {
"auteur": "Alice",
"note": 5
}
}Qué pide la consulta: actualiza (_update) la ficha 1 añadiendo estos dos campos. Lo que no se menciona (titre) queda igual.
{
"_index": "pratique-mini",
"_id": "1",
"_version": 2,
"result": "updated",
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"_seq_no": 1,
"_primary_term": 1
}A observar: "result": "updated", "_version": 2. Vuelve a leer la ficha con GET pratique-mini/_doc/1:
{
"_index": "pratique-mini",
"_id": "1",
"_version": 2,
"_seq_no": 1,
"_primary_term": 1,
"found": true,
"_source": {
"titre": "Mon premier document",
"note": 5,
"auteur": "Alice"
}
}Tres campos. El título sigue ahí. Equivalente SQL: UPDATE pratique_mini SET auteur = 'Alice', note = 5 WHERE id = 1, con la diferencia de que en SQL las columnas auteur y note habrían tenido que existir antes. Esta es tu respuesta-entregable: guárdala.
doc en el cuerpo significa «aquí están los campos a fusionar». Sin ella, _update no sabe qué hacer.GET pratique-mini/_mapping ahora muestra auteur (text + keyword, como titre) y note de tipo long, un entero. Elasticsearch adivinó el tipo a partir de 5. Si hubieras escrito "note": "5" entre comillas, habría declarado texto, y ya no podrías calcular un promedio sobre ese campo. Es el tema de la lección sobre el mapping, en el módulo 2._version cuenta las escrituras en esta ficha, no las lecturas: GET nunca la incrementa.PUT reemplaza todoVuelve a enviar exactamente la consulta del paso 3:
PUT pratique-mini/_doc/1
{
"titre": "Mon premier document"
}{
"_index": "pratique-mini",
"_id": "1",
"_version": 3,
"result": "updated",
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"_seq_no": 3,
"_primary_term": 1
}A observar: "result": "updated" (no created: la ficha 1 ya existía) y "_version": 3. Luego vuelve a leerla:
{
"_index": "pratique-mini",
"_id": "1",
"_version": 3,
"_seq_no": 3,
"_primary_term": 1,
"found": true,
"_source": {
"titre": "Mon premier document"
}
}auteur y note han desaparecido. PUT _doc/1 no modifica la ficha 1: la reemplaza por lo que envías. Para completar sin perder, es POST _update/1 con doc. Recuerda la regla con los dos verbos: PUT reemplaza, _update completa. Vuelve a poner los dos campos con la consulta del paso 5 antes de continuar (obtienes _version: 4).
PUT pratique-mini/_doc/2
{
"titre": "Deuxième document, écrit par Bob",
"auteur": "Bob",
"note": 3
}Respuesta: "_id": "2", "result": "created", "_version": 1. Luego cuenta:
GET pratique-mini/_count{
"count": 2,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
}
}Equivalente SQL: SELECT COUNT(*) FROM pratique_mini. Ahora, mira todo lo que hay dentro:
GET pratique-mini/_searchQué pide la consulta: busca en pratique-mini, sin criterio, así que todo.
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 1.0,
"hits": [
{
"_index": "pratique-mini",
"_id": "2",
"_score": 1.0,
"_source": {
"titre": "Deuxième document, écrit par Bob",
"auteur": "Bob",
"note": 3
}
},
{
"_index": "pratique-mini",
"_id": "1",
"_score": 1.0,
"_source": {
"titre": "Mon premier document",
"note": 5,
"auteur": "Alice"
}
}
]
}
}A observar: hits.total.value: 2 (cuántas fichas responden) luego hits.hits, la lista de fichas, cada una con su _id y su _source. El orden de las dos puede variar: sin criterio, todas tienen el mismo _score de 1.0. Equivalente SQL: SELECT * FROM pratique_mini.
Por último, busca una palabra:
GET pratique-mini/_search
{
"query": {
"match": {
"titre": "premier"
}
}
}Qué pide la consulta: las fichas cuyo campo titre contiene la palabra premier.
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 0.3788134,
"hits": [
{
"_index": "pratique-mini",
"_id": "1",
"_score": 0.3788134,
"_source": {
"titre": "Mon premier document",
"note": 5,
"auteur": "Alice"
}
}
]
}
}A observar: una sola ficha, la 1, y un _score que ya no es 1.0: es la relevancia, «cuánto responde esta ficha a la pregunta». El módulo 3 está dedicado a este número. Equivalente SQL aproximado: SELECT * FROM pratique_mini WHERE titre LIKE '%premier%', salvo que match también encontraría Premier con mayúscula, y el módulo 2 te explicará por qué.
_count devuelve 0 o 1 justo después de una escritura? Elasticsearch hace visibles los documentos nuevos para la búsqueda cada segundo, no al instante. Vuelve a lanzar _count: está actualizado. GET _doc/1, en cambio, siempre es inmediato porque no busca, va directo al número. Si quieres forzar la visibilidad inmediata en una prueba: PUT pratique-mini/_doc/2?refresh=true.took es el tiempo de la búsqueda en milisegundos. timed_out: false: terminó dentro de los plazos.GET con un cuerpo? Es una particularidad de Elasticsearch: la búsqueda es una lectura, por lo tanto GET, pero la pregunta cabe en un cuerpo JSON. POST pratique-mini/_search con el mismo cuerpo también funciona; ambos se aceptan.DELETE pratique-mini/_doc/2{
"_index": "pratique-mini",
"_id": "2",
"_version": 2,
"result": "deleted",
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"_seq_no": 4,
"_primary_term": 1
}A observar: "result": "deleted". GET pratique-mini/_count devuelve 1 (después de un segundo). Equivalente SQL: DELETE FROM pratique_mini WHERE id = 2.
Luego elimina el armario entero, fichas incluidas:
DELETE pratique-mini{
"acknowledged": true
}Prueba de que ya no existe, GET pratique-mini/_doc/1:
{
"error": {
"root_cause": [
{
"type": "index_not_found_exception",
"reason": "no such index [pratique-mini]",
"resource.type": "index_or_alias",
"resource.id": "pratique-mini",
"index_uuid": "_na_",
"index": "pratique-mini"
}
],
"type": "index_not_found_exception",
"reason": "no such index [pratique-mini]",
"resource.type": "index_or_alias",
"resource.id": "pratique-mini",
"index_uuid": "_na_",
"index": "pratique-mini"
},
"status": 404
}A observar: la diferencia con el paso 4. Ficha ausente en un armario presente: "found": false, sin error. Armario ausente: index_not_found_exception, 404. Ambas son respuestas normales de un servicio que está corriendo. Equivalente SQL: DROP TABLE pratique_mini.
GET _cat/indices/pratique-*?vRespuesta esperada: solo la línea de encabezado. Nada tuyo permanece en el cluster, y GET _cat/indices/cours,avis,acces?v sigue mostrando 504, 609, 12000.
pratique-mini y sabes por qué estaba amarillo, luego verde.PUT _doc/1 y la volviste a leer con GET _doc/1.auteur y note con POST _update/1 sin perder titre.PUT _doc/1 borrar los dos campos, y sabes decir la regla: PUT reemplaza, _update completa._count dijo 2, _search listó las dos fichas, match solo conservó una.pratique-* está vacío.GET pratique-mini/_doc/1 con tres campos (paso 5) como entregable.400 con Unexpected character o was expecting double-quote to start field name → el JSON del cuerpo está mal formado: cada nombre de campo y cada texto entre comillas dobles ", una coma entre los campos, sin coma después del último. Dev Tools subraya el lugar.400 con resource_already_exists_exception en PUT pratique-mini → el índice ya existe (relanzaste el paso 1). Continúa en el paso 2, o DELETE pratique-mini para volver a empezar de cero.400 con no handler found for uri → error de tipeo en una palabra con _: _serch, _doc/ olvidado, _udpate. Elasticsearch valida la ruta antes que todo lo demás.405 con Incorrect HTTP method for uri [/pratique-mini/_update/1] and method [GET], allowed: [POST] → verbo equivocado para esta ruta. El mensaje mismo dice cuál se acepta.400 con [UpdateRequest] unknown field [titre] → enviaste los campos directamente a _update, sin envolverlos en "doc": { … }. Añade la envoltura._count o _search no ven la ficha que acabas de escribir → espera un segundo y vuelve a lanzar (ver «Para entender bien» del paso 7). GET _doc/1 la ve de inmediato."result": "noop" en _update → los valores enviados ya eran los de la ficha; nada que cambiar, _version no se movió. No es un error."status": "yellow" persiste en GET _cluster/health después del paso 2 → otro índice tuyo todavía tiene rep 1. GET _cat/indices?v&health=yellow lo señala; aplícale el mismo _settings, o elimínalo..\labo.ps1 etat o ./labo.sh etat, luego la lección 04.