How to read this page. Eight steps, one query at a time. For each: the query to type, the exact response from the lab, and what to look at inside it. Type each query yourself (no copy-paste): it is by writing
PUT,GET,_docthat the words sink in. The "To understand better" blocks are optional; open them if a step leaves you in doubt. If the lab is not started, go back to the guided practice: the In Brief section gives the commands, kit included (https://github.com/hrhouma2/aiopsatlas-recherche-graphes-labo-fr).
The guided practice had you load 504 courses, 609 reviews, and 12,000 log lines in one go, with a script. You saw the numbers, but you have not written anything yourself yet. Here, you start from zero: an empty index that you create, a first record that you file in it, that you read back, that you complete, then a second one, a search, a deletion. At the end, you know what an index and a document are because you built one, not because you were told.
An index is a filing cabinet. A document is a card filed in the cabinet: a small JSON text with fields. Each card has a number, its _id, which lets you retrieve it directly without searching. You do not have to declare the columns in advance: the first card filed creates the mapping (the cabinet's blueprint) by itself.
| Elasticsearch | Classic SQL database | In this practice |
|---|---|---|
| index | table | pratique-mini |
| document | row | {"titre": "Mon premier document"} |
| field | column | titre, auteur, note |
_id | primary key | 1, 2 |
| mapping | table schema (CREATE TABLE …) | created automatically at step 3 |
_source | the row exactly as you wrote it | what GET _doc/1 gives you back |
Open http://localhost:5601, ☰ menu → Management → Dev Tools. The left panel receives the queries, the right one displays the response. You send with Ctrl + Enter (Cmd + Enter on macOS) or the ▶ button to the right of the line.
Every query has the same shape: a verb, a path, and sometimes a JSON body underneath.
| Verb | What it does | SQL equivalent |
|---|---|---|
GET | read, without changing anything | SELECT |
PUT | create, or replace entirely | CREATE TABLE, INSERT (or replace the row) |
POST | act: update, search with a body | UPDATE |
DELETE | delete | DROP TABLE, DELETE |
The path says what we act on: pratique-mini (the cabinet), pratique-mini/_doc/1 (card number 1 of the cabinet), pratique-mini/_search (search in the cabinet). Words that start with _ are Elasticsearch commands, not names of yours.
PUT pratique-miniWhat the query asks: create an index named pratique-mini. Nothing else: no columns, no content.
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "pratique-mini"
}What to look at: "acknowledged": true, "done", and the name echoed back. The badge at the top right of the response says 200 - OK.
pratique- in front? Every index you create in this course carries this prefix. That way GET _cat/indices/pratique-*?v lists everything that is yours and nothing else, and cours, avis, acces remain untouched./. Pratique-Mini would be refused.400 with resource_already_exists_exception: the cabinet already exists. That is not a failure, it is a response.GET _cat/indices/pratique-mini?vWhat the query asks: one summary line about this index, with the header line (?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 227bWhat to look at: docs.count 0, the cabinet is empty. And health yellow with rep 1: Elasticsearch has planned a backup copy (a replica) of your index on a second machine, and the lab has only one. The copy cannot be placed anywhere, hence the yellow. The kit's three indexes are green because their mapping sets number_of_replicas: 0. Do the same, in one query:
PUT pratique-mini/_settings
{
"index": { "number_of_replicas": 0 }
}{
"acknowledged": true
}Retype 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 227bWhat to look at: green, rep 0. Your uuid will be different: it is the index's internal identifier, drawn at random on creation.
GET _cluster/health said "status": "yellow" for the whole cluster: the cluster's color is the worst color among its indexes. That is the explanation of the "yellow" failure in the lesson 04 catalog.GET pratique-mini (without _cat) returns the index's complete record: "mappings": { } (empty, no card filed) and "settings" with number_of_replicas.PUT pratique-mini/_doc/1
{
"titre": "Mon premier document"
}What the query asks: in the pratique-mini cabinet, file a card (_doc) number 1 that contains a titre field.
{
"_index": "pratique-mini",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}What to look at: "result": "created" and "_version": 1: first version of card 1. SQL equivalent: INSERT INTO pratique_mini (id, titre) VALUES (1, 'Mon premier document'), except that no CREATE TABLE was necessary.
GET pratique-mini/_mapping: the titre field is now declared with type text (to search for words inside it) with a titre.keyword sub-field (to sort or filter on the exact value). Elasticsearch inferred it from the value "Mon premier document", a string.1 in _doc/1 is one you chose. The kit's documents do the same (C0001, A00001…). If you write POST pratique-mini/_doc with no number, Elasticsearch invents a twenty-character _id; handy for logs, painful for a card you want to find by hand._shards, _seq_no, _primary_term are internal bookkeeping (on how many pieces the write was confirmed, which sequence number). You do not need them in this course.GET pratique-mini/_doc/1What the query asks: give me card number 1 of pratique-mini, directly, without searching.
{
"_index": "pratique-mini",
"_id": "1",
"_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"found": true,
"_source": {
"titre": "Mon premier document"
}
}What to look at: "found": true, and _source, your card exactly as you wrote it, to the character. SQL equivalent: SELECT * FROM pratique_mini WHERE id = 1.
Try a card that does not exist: GET pratique-mini/_doc/3.
{
"_index": "pratique-mini",
"_id": "3",
"found": false
}No error, no _source: "found": false, badge 404 - Not Found. Elasticsearch understood the question; the answer is "there is nothing at this number".
POST pratique-mini/_update/1
{
"doc": {
"auteur": "Alice",
"note": 5
}
}What the query asks: update (_update) card 1 by adding these two fields. What is not mentioned (titre) stays as it is.
{
"_index": "pratique-mini",
"_id": "1",
"_version": 2,
"result": "updated",
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"_seq_no": 1,
"_primary_term": 1
}What to look at: "result": "updated", "_version": 2. Read the card back with 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"
}
}Three fields. The title is still there. SQL equivalent: UPDATE pratique_mini SET auteur = 'Alice', note = 5 WHERE id = 1, with the difference that in SQL the auteur and note columns would have had to exist beforehand. This is your deliverable response: keep it.
doc in the body means "here are the fields to merge". Without it, _update does not know what to do.GET pratique-mini/_mapping now shows auteur (text + keyword, like titre) and note of type long, an integer. Elasticsearch guessed the type from 5. If you had written "note": "5" in quotes, it would have declared text, and you could no longer compute an average on it. That is the subject of the mapping lesson, in module 2._version counts the writes on this card, not the reads: GET never increments it.PUT Replaces EverythingSend exactly the query of step 3 again:
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
}What to look at: "result": "updated" (not created: card 1 existed) and "_version": 3. Then read it back:
{
"_index": "pratique-mini",
"_id": "1",
"_version": 3,
"_seq_no": 3,
"_primary_term": 1,
"found": true,
"_source": {
"titre": "Mon premier document"
}
}auteur and note are gone. PUT _doc/1 does not modify card 1: it replaces it with what you send. To complete without losing anything, use POST _update/1 with doc. Remember the rule with the two verbs: PUT replaces, _update completes. Put the two fields back with the query of step 5 before continuing (you get _version: 4).
PUT pratique-mini/_doc/2
{
"titre": "Deuxième document, écrit par Bob",
"auteur": "Bob",
"note": 3
}Response: "_id": "2", "result": "created", "_version": 1. Then count:
GET pratique-mini/_count{
"count": 2,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
}
}SQL equivalent: SELECT COUNT(*) FROM pratique_mini. Now, see everything that is in it:
GET pratique-mini/_searchWhat the query asks: search in pratique-mini, with no criterion, so everything.
{
"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"
}
}
]
}
}What to look at: hits.total.value: 2 (how many cards match) then hits.hits, the list of cards, each with its _id and its _source. The order of the two may vary: with no criterion, all have the same _score of 1.0. SQL equivalent: SELECT * FROM pratique_mini.
Finally, search for a word:
GET pratique-mini/_search
{
"query": {
"match": {
"titre": "premier"
}
}
}What the query asks: the cards whose titre field contains the word 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"
}
}
]
}
}What to look at: a single card, number 1, and a _score that is no longer 1.0: this is relevance, "how well this card answers the question". Module 3 is devoted to this number. Approximate SQL equivalent: SELECT * FROM pratique_mini WHERE titre LIKE '%premier%', except that match would also find Premier with a capital letter, and module 2 will explain why.
_count returns 0 or 1 right after a write? Elasticsearch makes new cards visible to search every second, not at the very instant. Rerun _count: it is up to date. GET _doc/1, on the other hand, is always immediate because it does not search, it goes straight to the number. If you want to force immediate visibility in a test: PUT pratique-mini/_doc/2?refresh=true.took is the search time in milliseconds. timed_out: false: it finished on time.GET with a body? It is a peculiarity of Elasticsearch: a search is a read, hence GET, but the question fits in a JSON body. POST pratique-mini/_search with the same body works too; both are accepted.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
}What to look at: "result": "deleted". GET pratique-mini/_count returns 1 (after one second). SQL equivalent: DELETE FROM pratique_mini WHERE id = 2.
Then delete the entire cabinet, cards included:
DELETE pratique-mini{
"acknowledged": true
}Proof that it no longer exists, 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
}What to look at: the difference with step 4. Missing card in an existing cabinet: "found": false, no error. Missing cabinet: index_not_found_exception, 404. Both are normal responses from a running service. SQL equivalent: DROP TABLE pratique_mini.
GET _cat/indices/pratique-*?vExpected response: the header line alone. Nothing of yours is left lying around in the cluster, and GET _cat/indices/cours,avis,acces?v still shows 504, 609, 12000.
pratique-mini and you know why it was yellow, then green.PUT _doc/1 and read it back with GET _doc/1.auteur and note with POST _update/1 without losing titre.PUT _doc/1 erase the two fields, and you can state the rule: PUT replaces, _update completes._count said 2, _search listed both cards, match kept only one.pratique-* is empty.GET pratique-mini/_doc/1 (step 5) as the deliverable.400 with Unexpected character or was expecting double-quote to start field name → the body's JSON is malformed: every field name and every text in double quotes ", a comma between fields, no comma after the last one. Dev Tools underlines the spot.400 with resource_already_exists_exception on PUT pratique-mini → the index already exists (you reran step 1). Continue at step 2, or DELETE pratique-mini to start from scratch.400 with no handler found for uri → typo in a _ word: _serch, _doc/ forgotten, _udpate. Elasticsearch validates the path before anything else.405 with Incorrect HTTP method for uri [/pratique-mini/_update/1] and method [GET], allowed: [POST] → wrong verb for this path. The message itself says which one is accepted.400 with [UpdateRequest] unknown field [titre] → you sent the fields directly to _update, without wrapping them in "doc": { … }. Add the wrapper._count or _search do not see the card you just wrote → wait one second and rerun (see "To understand better" in step 7). GET _doc/1 sees it right away."result": "noop" on _update → the values sent were already those of the card; nothing to change, _version did not move. That is not an error."status": "yellow" persists in GET _cluster/health after step 2 → another index of yours still has rep 1. GET _cat/indices?v&health=yellow points it out; apply the same _settings to it, or delete it..\labo.ps1 etat or ./labo.sh etat, then lesson 04.