Πώς να διαβάσεις αυτή τη σελίδα. Οκτώ βήματα, ένα ερώτημα τη φορά. Για κάθε ένα: το ερώτημα που πληκτρολογείς, η ακριβής απάντηση του εργαστηρίου, και τι πρέπει να προσέξεις μέσα σε αυτήν. Πληκτρολόγησε μόνος σου κάθε ερώτημα (όχι αντιγραφή-επικόλληση): γράφοντας
PUT,GET,_docείναι που οι λέξεις μπαίνουν στο μυαλό. Τα πλαίσια «Για να κατανοήσεις καλά» είναι προαιρετικά· άνοιξέ τα αν κάποιο βήμα σου αφήνει αμφιβολία. Αν το εργαστήριο δεν έχει ξεκινήσει, γύρνα στην καθοδηγούμενη πρακτική: η ενότητα Εν συντομία δίνει τις εντολές, μαζί με το kit (https://github.com/hrhouma2/aiopsatlas-recherche-graphes-labo-fr).
Η καθοδηγούμενη πρακτική σε έβαλε να φορτώσεις 504 μαθήματα, 609 κριτικές και 12.000 γραμμές αρχείου καταγραφής με τη μια, μέσω ενός σεναρίου. Είδες τους αριθμούς, αλλά δεν έχεις γράψει ακόμα τίποτα μόνος σου. Εδώ ξεκινάς από το μηδέν: ένα άδειο index που δημιουργείς εσύ, μια πρώτη καρτέλα που αποθηκεύεις μέσα του, που ξαναδιαβάζεις, που συμπληρώνεις, μετά μια δεύτερη, μια αναζήτηση, μια διαγραφή. Στο τέλος, ξέρεις τι είναι ένα index και ένα έγγραφο επειδή έφτιαξες ένα, όχι επειδή σου το είπαν.
Ένα index είναι μια ντουλάπα. Ένα έγγραφο είναι μια καρτέλα αποθηκευμένη μέσα στην ντουλάπα: ένα μικρό κείμενο JSON με πεδία. Κάθε καρτέλα έχει έναν αριθμό, το _id της, που επιτρέπει να τη βρεις απευθείας χωρίς αναζήτηση. Δεν χρειάζεται να δηλώσεις τις στήλες εκ των προτέρων: η πρώτη καρτέλα που αποθηκεύεται δημιουργεί το mapping (το σχέδιο της ντουλάπας) μόνη της.
| Elasticsearch | Κλασική βάση SQL | Σε αυτή την πρακτική |
|---|---|---|
| index | πίνακας | pratique-mini |
| document | γραμμή | {"titre": "Mon premier document"} |
| πεδίο | στήλη | titre, auteur, note |
_id | πρωτεύον κλειδί | 1, 2 |
| mapping | σχήμα του πίνακα (CREATE TABLE …) | δημιουργείται αυτόματα στο βήμα 3 |
_source | η γραμμή όπως την έγραψες | αυτό που σου επιστρέφει το GET _doc/1 |
Άνοιξε το http://localhost:5601, μενού ☰ → Management → Dev Tools. Ο αριστερός πίνακας δέχεται τα ερωτήματα, ο δεξιός εμφανίζει την απάντηση. Στέλνεις με Ctrl + Enter (Cmd + Enter σε macOS) ή το κουμπί ▶ δεξιά της γραμμής.
Κάθε ερώτημα έχει την ίδια μορφή: ένα ρήμα, μια διαδρομή, και μερικές φορές ένα σώμα JSON από κάτω.
| Ρήμα | Τι κάνει | Ισοδύναμο SQL |
|---|---|---|
GET | διάβασμα, χωρίς να αλλάζει τίποτα | SELECT |
PUT | δημιουργία, ή πλήρης αντικατάσταση | CREATE TABLE, INSERT (ή αντικατάσταση της γραμμής) |
POST | ενέργεια: ενημέρωση, αναζήτηση με σώμα | UPDATE |
DELETE | διαγραφή | DROP TABLE, DELETE |
Η διαδρομή λέει πάνω σε τι ενεργείς: pratique-mini (η ντουλάπα), pratique-mini/_doc/1 (η καρτέλα νούμερο 1 της ντουλάπας), pratique-mini/_search (αναζήτηση μέσα στην ντουλάπα). Οι λέξεις που ξεκινούν με _ είναι εντολές του Elasticsearch, όχι δικά σου ονόματα.
PUT pratique-miniΤι ζητάει το ερώτημα: δημιούργησε ένα index με το όνομα pratique-mini. Τίποτα άλλο: όχι στήλες, όχι περιεχόμενο.
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "pratique-mini"
}Τι να προσέξεις: "acknowledged": true, «έγινε», και το όνομα σε ηχώ. Το σήμα πάνω δεξιά της απάντησης λέει 200 - OK.
pratique- μπροστά; Όλα τα index που δημιουργείς σε αυτό το μάθημα φέρουν αυτό το πρόθεμα. Έτσι το GET _cat/indices/pratique-*?v απαριθμεί μόνο ό,τι είναι δικό σου και τίποτα άλλο, και τα cours, avis, acces μένουν ανέγγιχτα./. Το Pratique-Mini θα απορριπτόταν.400 με resource_already_exists_exception: η ντουλάπα υπάρχει ήδη. Δεν είναι βλάβη, είναι απάντηση.GET _cat/indices/pratique-mini?vΤι ζητάει το ερώτημα: μια γραμμή περίληψης για αυτό το index, με τη γραμμή κεφαλίδας (?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 227bΤι να προσέξεις: docs.count 0, η ντουλάπα είναι άδεια. Και health yellow με rep 1: το Elasticsearch έχει προβλέψει ένα αντίγραφο ασφαλείας (ένα replica) του index σου σε ένα δεύτερο μηχάνημα, και το εργαστήριο έχει μόνο ένα. Το αντίγραφο δεν μπορεί να τοποθετηθεί πουθενά, εξ ου και το κίτρινο. Τα τρία index του kit είναι πράσινα επειδή το mapping τους ορίζει number_of_replicas: 0. Κάνε το ίδιο, με ένα ερώτημα:
PUT pratique-mini/_settings
{
"index": { "number_of_replicas": 0 }
}{
"acknowledged": true
}Ξαναπληκτρολόγησε 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 227bΤι να προσέξεις: green, rep 0. Το δικό σου uuid θα είναι διαφορετικό: είναι ο εσωτερικός αναγνωριστικός κωδικός του index, τραβηγμένος τυχαία κατά τη δημιουργία.
GET _cluster/health έλεγε "status": "yellow" για ολόκληρο το cluster: το χρώμα του cluster είναι το χειρότερο χρώμα από τα index του. Αυτή είναι η εξήγηση της βλάβης «yellow» του καταλόγου από το μάθημα 04.GET pratique-mini (χωρίς _cat) επιστρέφει την πλήρη καρτέλα του index: "mappings": { } (άδειο, καμία καρτέλα αποθηκευμένη) και "settings" με το number_of_replicas.PUT pratique-mini/_doc/1
{
"titre": "Mon premier document"
}Τι ζητάει το ερώτημα: μέσα στην ντουλάπα pratique-mini, αποθήκευσε μια καρτέλα (_doc) με αριθμό 1 που περιέχει ένα πεδίο titre.
{
"_index": "pratique-mini",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}Τι να προσέξεις: "result": "created" και "_version": 1: πρώτη έκδοση της καρτέλας 1. Ισοδύναμο SQL: INSERT INTO pratique_mini (id, titre) VALUES (1, 'Mon premier document'), μόνο που δεν χρειάστηκε κανένα CREATE TABLE.
GET pratique-mini/_mapping: το πεδίο titre είναι τώρα δηλωμένο ως τύπος text (για αναζήτηση λέξεων μέσα του) με ένα υποπεδίο titre.keyword (για ταξινόμηση ή φιλτράρισμα στην ακριβή τιμή). Το Elasticsearch το συνήγαγε από την τιμή "Mon premier document", μια αλυσίδα χαρακτήρων.1 του _doc/1, το επέλεξες εσύ. Τα έγγραφα του kit κάνουν το ίδιο (C0001, A00001…). Αν γράψεις POST pratique-mini/_doc χωρίς αριθμό, το Elasticsearch επινοεί ένα _id είκοσι χαρακτήρων· βολικό για αρχεία καταγραφής, ενοχλητικό για μια καρτέλα που θέλεις να βρεις με το χέρι._shards, _seq_no, _primary_term είναι εσωτερική λογιστική (σε πόσα κομμάτια επιβεβαιώθηκε η εγγραφή, ποιος αριθμός σειράς). Δεν τα χρειάζεσαι σε αυτό το μάθημα.GET pratique-mini/_doc/1Τι ζητάει το ερώτημα: δώσε μου την καρτέλα νούμερο 1 του pratique-mini, απευθείας, χωρίς αναζήτηση.
{
"_index": "pratique-mini",
"_id": "1",
"_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"found": true,
"_source": {
"titre": "Mon premier document"
}
}Τι να προσέξεις: "found": true, και _source, η καρτέλα σου όπως ακριβώς την έγραψες, γράμμα προς γράμμα. Ισοδύναμο SQL: SELECT * FROM pratique_mini WHERE id = 1.
Δοκίμασε μια καρτέλα που δεν υπάρχει: GET pratique-mini/_doc/3.
{
"_index": "pratique-mini",
"_id": "3",
"found": false
}Χωρίς σφάλμα, χωρίς _source: "found": false, σήμα 404 - Not Found. Το Elasticsearch κατάλαβε την ερώτηση· η απάντηση είναι «δεν υπάρχει τίποτα σε αυτόν τον αριθμό».
POST pratique-mini/_update/1
{
"doc": {
"auteur": "Alice",
"note": 5
}
}Τι ζητάει το ερώτημα: ενημέρωσε (_update) την καρτέλα 1 προσθέτοντας αυτά τα δύο πεδία. Ό,τι δεν αναφέρεται (titre) μένει ως έχει.
{
"_index": "pratique-mini",
"_id": "1",
"_version": 2,
"result": "updated",
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"_seq_no": 1,
"_primary_term": 1
}Τι να προσέξεις: "result": "updated", "_version": 2. Ξαναδιάβασε την καρτέλα με 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"
}
}Τρία πεδία. Ο τίτλος είναι ακόμα εκεί. Ισοδύναμο SQL: UPDATE pratique_mini SET auteur = 'Alice', note = 5 WHERE id = 1, με τη διαφορά ότι σε SQL οι στήλες auteur και note θα έπρεπε να υπάρχουν από πριν. Αυτή είναι η απάντηση-παραδοτέο σου: κράτησέ την.
doc στο σώμα σημαίνει «να τα πεδία προς συγχώνευση». Χωρίς αυτήν, το _update δεν ξέρει τι να κάνει.GET pratique-mini/_mapping δείχνει τώρα auteur (text + keyword, όπως το titre) και note τύπου long, έναν ακέραιο. Το Elasticsearch μάντεψε τον τύπο από το 5. Αν είχες γράψει "note": "5" με εισαγωγικά, θα είχε δηλώσει κείμενο, και δεν θα μπορούσες πια να υπολογίσεις μέσο όρο πάνω σε αυτό. Είναι το θέμα του μαθήματος για το mapping, στην ενότητα 2._version μετράει τις εγγραφές σε αυτή την καρτέλα, όχι τις αναγνώσεις: το GET δεν το αυξάνει ποτέ.PUT αντικαθιστά τα πάνταΞαναστείλε ακριβώς το ερώτημα του βήματος 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
}Τι να προσέξεις: "result": "updated" (όχι created: η καρτέλα 1 υπήρχε) και "_version": 3. Μετά ξαναδιάβασέ την:
{
"_index": "pratique-mini",
"_id": "1",
"_version": 3,
"_seq_no": 3,
"_primary_term": 1,
"found": true,
"_source": {
"titre": "Mon premier document"
}
}Τα auteur και note εξαφανίστηκαν. Το PUT _doc/1 δεν τροποποιεί την καρτέλα 1: την αντικαθιστά με ό,τι στέλνεις. Για να συμπληρώσεις χωρίς να χάσεις τίποτα, είναι το POST _update/1 με doc. Κράτησε τον κανόνα με τα δύο ρήματα: το PUT αντικαθιστά, το _update συμπληρώνει. Ξαναβάλε τα δύο πεδία με το ερώτημα του βήματος 5 πριν συνεχίσεις (θα λάβεις _version: 4).
PUT pratique-mini/_doc/2
{
"titre": "Deuxième document, écrit par Bob",
"auteur": "Bob",
"note": 3
}Απάντηση: "_id": "2", "result": "created", "_version": 1. Μετά μέτρα:
GET pratique-mini/_count{
"count": 2,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
}
}Ισοδύναμο SQL: SELECT COUNT(*) FROM pratique_mini. Τώρα, δες όλο το περιεχόμενο:
GET pratique-mini/_searchΤι ζητάει το ερώτημα: αναζήτησε στο pratique-mini, χωρίς κριτήριο, άρα τα πάντα.
{
"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"
}
}
]
}
}Τι να προσέξεις: hits.total.value: 2 (πόσες καρτέλες ταιριάζουν) μετά hits.hits, η λίστα των καρτελών, καθεμία με το _id και το _source της. Η σειρά των δύο μπορεί να διαφέρει: χωρίς κριτήριο, όλες έχουν το ίδιο _score 1.0. Ισοδύναμο SQL: SELECT * FROM pratique_mini.
Τέλος, αναζήτησε μια λέξη:
GET pratique-mini/_search
{
"query": {
"match": {
"titre": "premier"
}
}
}Τι ζητάει το ερώτημα: οι καρτέλες όπου το πεδίο titre περιέχει τη λέξη 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"
}
}
]
}
}Τι να προσέξεις: μία μόνο καρτέλα, η 1, και ένα _score που δεν είναι πια 1.0: είναι η συνάφεια, «πόσο καλά αυτή η καρτέλα απαντά στην ερώτηση». Η ενότητα 3 είναι αφιερωμένη σε αυτόν τον αριθμό. Κατά προσέγγιση ισοδύναμο SQL: SELECT * FROM pratique_mini WHERE titre LIKE '%premier%', μόνο που το match θα έβρισκε επίσης το Premier με κεφαλαίο, και η ενότητα 2 θα σου εξηγήσει γιατί.
_count δίνει 0 ή 1 αμέσως μετά από μια εγγραφή; Το Elasticsearch κάνει τις νέες καρτέλες ορατές στην αναζήτηση κάθε δευτερόλεπτο, όχι αμέσως. Ξανατρέξε το _count: είναι ενημερωμένο. Το GET _doc/1, από την άλλη, είναι πάντα άμεσο επειδή δεν αναζητά, πηγαίνει κατευθείαν στον αριθμό. Αν θέλεις να επιβάλεις άμεση ορατότητα σε ένα τεστ: PUT pratique-mini/_doc/2?refresh=true.took είναι ο χρόνος της αναζήτησης σε χιλιοστά του δευτερολέπτου. timed_out: false: τελείωσε εντός προθεσμίας.GET με σώμα; Είναι μια ιδιαιτερότητα του Elasticsearch: η αναζήτηση είναι ανάγνωση, άρα GET, αλλά η ερώτηση χωράει σε ένα σώμα JSON. Το POST pratique-mini/_search με το ίδιο σώμα δουλεύει επίσης· και τα δύο γίνονται δεκτά.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
}Τι να προσέξεις: "result": "deleted". Το GET pratique-mini/_count επιστρέφει 1 (μετά από ένα δευτερόλεπτο). Ισοδύναμο SQL: DELETE FROM pratique_mini WHERE id = 2.
Μετά διάγραψε ολόκληρη την ντουλάπα, μαζί με τις καρτέλες:
DELETE pratique-mini{
"acknowledged": true
}Απόδειξη ότι δεν υπάρχει πια, 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
}Τι να προσέξεις: η διαφορά με το βήμα 4. Καρτέλα απούσα σε ντουλάπα παρούσα: "found": false, χωρίς σφάλμα. Ντουλάπα απούσα: index_not_found_exception, 404. Και οι δύο είναι φυσιολογικές απαντήσεις μιας υπηρεσίας που λειτουργεί. Ισοδύναμο SQL: DROP TABLE pratique_mini.
GET _cat/indices/pratique-*?vΑναμενόμενη απάντηση: μόνο η γραμμή κεφαλίδας. Τίποτα δικό σου δεν παραμένει στο cluster, και το GET _cat/indices/cours,avis,acces?v δείχνει ακόμα 504, 609, 12000.
pratique-mini και ξέρεις γιατί ήταν κίτρινο, μετά πράσινο.PUT _doc/1 και την ξαναδιάβασες με GET _doc/1.auteur και note με POST _update/1 χωρίς να χάσεις το titre.PUT _doc/1 να σβήνει τα δύο πεδία, και ξέρεις να πεις τον κανόνα: το PUT αντικαθιστά, το _update συμπληρώνει._count έδειξε 2, το _search απαρίθμησε τις δύο καρτέλες, το match κράτησε μόνο μία.pratique-* είναι άδειο.GET pratique-mini/_doc/1 με τρία πεδία (βήμα 5) ως παραδοτέο.400 με Unexpected character ή was expecting double-quote to start field name → το JSON του σώματος είναι κακοδιατυπωμένο: κάθε όνομα πεδίου και κάθε κείμενο ανάμεσα σε διπλά εισαγωγικά ", ένα κόμμα ανάμεσα στα πεδία, χωρίς κόμμα μετά το τελευταίο. Το Dev Tools υπογραμμίζει το σημείο.400 με resource_already_exists_exception στο PUT pratique-mini → το index υπάρχει ήδη (ξανάτρεξες το βήμα 1). Συνέχισε στο βήμα 2, ή DELETE pratique-mini για να ξεκινήσεις από το μηδέν.400 με no handler found for uri → τυπογραφικό λάθος σε μια λέξη με _: _serch, ξεχασμένο _doc/, _udpate. Το Elasticsearch επικυρώνει τη διαδρομή πριν από όλα τα υπόλοιπα.405 με Incorrect HTTP method for uri [/pratique-mini/_update/1] and method [GET], allowed: [POST] → λάθος ρήμα για αυτή τη διαδρομή. Το μήνυμα λέει το ίδιο ποιο γίνεται δεκτό.400 με [UpdateRequest] unknown field [titre] → έστειλες τα πεδία απευθείας στο _update, χωρίς να τα τυλίξεις σε "doc": { … }. Πρόσθεσε το περίβλημα._count ή το _search δεν βλέπουν την καρτέλα που μόλις έγραψες → περίμενε ένα δευτερόλεπτο και ξανατρέξε (βλέπε «Για να κατανοήσεις καλά» του βήματος 7). Το GET _doc/1 τη βλέπει αμέσως."result": "noop" στο _update → οι τιμές που στάλθηκαν ήταν ήδη αυτές της καρτέλας· τίποτα προς αλλαγή, το _version δεν κινήθηκε. Δεν είναι σφάλμα."status": "yellow" επιμένει στο GET _cluster/health μετά το βήμα 2 → ένα άλλο δικό σου index έχει ακόμα rep 1. Το GET _cat/indices?v&health=yellow το εντοπίζει· εφάρμοσέ του το ίδιο _settings, ή διάγραψέ το..\labo.ps1 etat ή ./labo.sh etat, μετά η ενότητα 04.