Exercises
Hands-on tasks for the Elasticsearch for Developers course. They follow the chapter order, so you can do them as you go.
- All tasks are written for Kibana Dev Tools (the same JSON works from any client library).
- Day 1 exercises assume no prior Elasticsearch knowledge.
- Day 2 exercises assume you’re comfortable with everything from Day 1.
Sample dataset. Several exercises use the
shop-orders-demoindex. Load it once: 1. Run the mapping insample-data/shop-orders-demo-mapping.txt(aPUT shop-orders-demo). 2. Run the bulk filesample-data/shop-orders-demo-data.txt(a bigPOST _bulk). 3. Verify:GET shop-orders-demo/_countshould return a few hundred orders.
Day 1 — Fundamentals
1) Environment & first contact (Chapter 01)
- Start a local stack with the
docker-compose.ymlfrom Chapter 01 (docker compose up -d), or use an Elastic Cloud trial. - Open Kibana → Dev Tools and run:
GET /— confirm the versionGET /_cluster/health— note thestatus(green / yellow / red)GET /_cat/nodes?vGET /_cat/indices?v
- Why is a brand-new single-node cluster often yellow? (Hint: replicas.)
2) CRUD (Chapter 02)
Create the following documents in a new index
subscription_list:{ "name": "First customer", "e-mail": "first-customer@icloud.com", "age": 31 }{ "name": "Second customer", "e-mail": "second-customer@gmail.com", "age": 47 }List all documents in index
subscription_list.Modify the e-mail address of the first customer to
first@outlook.comusing a partial update (_update), and check the result.Delete the second customer and list all documents in the
subscription_listindex again.Re-index the first customer with the same
_idbut a changedage. Look at_versionin the response — what happened?
3) Indices, aliases, reindexing (Chapter 02 + a peek at Chapter 07)
Create a document in index
manufacturerwith ID1and fields:name:Applerevenue:274515country:United States
Create another document in index
manufacturer, but this time let Elasticsearch generate the ID:name:Toyota Grouprevenue:256721country:Japan
Now imagine the load on the index has increased and you need more shards.
Create a new empty index
manufacturer-2with settings:number_of_shards:5number_of_replicas:1
Reindex data from
manufacturertomanufacturer-2(_reindex).Delete the index
manufacturer.Create an alias
manufacturerthat points to the indexmanufacturer-2.List all documents through the alias
manufacturer.
Takeaway: the app keeps using the stable name
manufacturerwhile the physical index changed underneath it.
4) Bulk API (Chapter 02)
- Using a single
POST _bulkrequest, in one call:- index two new products into an index
catalog - update one of them
- delete a document by id
- index two new products into an index
- Inspect the response: did any item fail? Where would you look
(
errors, per-itemstatus)?
5) Basic search (Chapter 03)
Use shop-orders-demo.
- Return all orders (
match_all) and read the response envelope: how many hits inhits.total.value, and what isrelation? - Find orders with
order_statusexactlydelivered(which query —termormatch? why?). - Find orders delivered to
country_codeCZorSK(terms). - Find orders with
total_amountbetween1000and5000(range). - Full-text search the
notesfield for a word (e.g.reklamace) withmatch. Then try the same word with atermquery — explain the different result. - Build a single
boolquery that returns orders that:- are delivered (
filteronorder_status) - to
HU(filteroncountry_code) - have
total_amount≥2000(rangeinfilter) - are not returned (
must_notonreturned)
- are delivered (
- Return only the fields
order_id,total_amount,delivery.city(_sourcefiltering), sorted bytotal_amountdescending, page size5.
6) Mappings (Chapter 04)
Create an index template called
commentsthat applies the following mapping to an indexcomments(created in the next step):Field Type title texttext textauthor text+keyword(multi-field)email keywordvisible booleanscore a decimal number created dateaccepting only the formatyyyy-MM-ddStore the following document into index
comments:{ "title": "Test comment", "text": "Hello world!", "author": "Hicham Sharp", "email": "hicham@sharp-family.zzz", "visible": true, "score": 12.7, "created": "2022-02-18" }Check the mappings of
comments(GET comments/_mapping) — they should match step 1.Try to index a document with
"created": "18-02-2022". What happens, and why?
7) Analyzers (Chapter 04)
- Use the
_analyzeAPI to tokenize the textThe Wireless Headphones, 2-pack!with thestandardanalyzer. How many tokens, and what are they? - Run the same text through the
whitespaceanalyzer and compare. - Run
headphones are amazingthrough theenglishanalyzer — notice stemming (headphones→headphon) and stopword removal (are). - Run
POST shop-orders-demo/_analyzeagainst thenotesfield (Czech analyzer) on a Czech sentence and observe stemming/stopwords.
Takeaway: the analyzer decides what is actually searchable — not the raw string.
Day 2 — Advanced
8) Custom analyzer (Chapter 05)
Create a new index
my_en_analyzer. Start from the built-in english analyzer settings (see the ES docs: Language analyzers → english analyzer) and extend it:- add a character filter that strips HTML tags from the input,
- add a custom synonym token filter that converts the
word
failureintoerror.
Create a mapping with a single field:
- name:
message - type:
text - analyzer: the custom analyzer from step 1.
- name:
Index these documents:
POST my_en_analyzer/_doc { "message": "An error occured" } POST my_en_analyzer/_doc { "message": "There was a <b>failure</b> during processing request" }Search for
errorinmessage. Both documents should match. Explain why (synonyms + html_strip).
9) Sorting, pagination, and highlighting (Chapter 03 + 05)
Store the following documents:
POST book/_doc { "title": "The Forever Dog", "publish": "2020-01-20" } POST book/_doc { "title": "The Book Your Dog Wishes", "publish": "2018-01-01" } POST book/_doc { "title": "The Complete Dog Breed Book", "publish": "2018-01-01" }Search for
Dogand:- sort results by
publishdate, oldest first, - if two books share the same publish date, sort them alphabetically by title.
- sort results by
Highlight the matches using the HTML tag
<b></b>.
10) Nested queries (Chapter 05)
In shop-orders-demo, items is a
nested field.
- Find orders that contain an item where
categoryisElektronikaandunit_price>1000in the same item (use anestedquery — a plainboolwould give false matches). - Add
inner_hitsso the response shows which item(s) matched. - (Bonus) Explain what would go wrong if
itemswere a plainobjectinstead ofnested.
11) Autocomplete & typo tolerance (Chapter 05)
- Build a small index
product_suggestwith acompletion-type fieldsuggest. - Index a handful of product names
(e.g.
Bezdrátová sluchátka AirWave 300,Kávovar AromaOne,Běžecké boty TrailPro). - Use the completion suggester to autocomplete the
prefix
Káv. - Enable
fuzzyand try a prefix with a typo (e.g.Kavov). Does it still suggestKávovar?
12) Custom relevance (Chapter 05)
In shop-orders-demo:
- Use
function_scoreto searchnotesfor a term, but boost orders with a higherrating(field_value_factor). Guard against missingrating. - Use a
gaussdecay function so more recent orders (created_at) score higher. - (Bonus) Rewrite the rating boost as a
script_scorequery using a Painless script such as_score * Math.log(2 + doc['rating'].value).
13) Aggregations (Chapter 06)
Use shop-orders-demo (and/or
kibana_sample_data_ecommerce if you have it installed).
- List the
customer.idvalues (or e-mail in the Kibana sample) that created the most orders (terms). - For each
channel, compute the averagetotal_amount(terms+avg). - Orders per month (
date_histogramoncreated_at,calendar_interval: month). - Unique customers (
cardinalityoncustomer.id). p50/p95/p99oftotal_amount(percentiles).- Multi-level: for each
country_code, the averagetotal_amountand the number of unique customers. - Nested aggregation: dive into
items, thentermsbycategory, thensumofunit_price. (Usereverse_nestedto also count distinct parent orders per category.)
14) Pipeline aggregations (Chapter 06)
On a monthly date_histogram of revenue (sum
of total_amount):
- Add a
cumulative_sumof monthly revenue. - Add a
derivative(month-over-month change). - Add a 3-month
moving_fnaverage (MovingFunctions.unweightedAvg(values)). - Add a
bucket_scriptcomputing the average discount ratio per month (discount_amount/subtotal_amount). - (Bonus) Use
bucket_selectorto keep only months whose revenue exceeded a threshold.
15) ILM & data streams (Chapter 07)
ILM rollover + retention (classic alias):
- Create an ILM policy
logs-hot-deletewith a hot phase that rolls over atmax_primary_shard_size: 1mb(tiny, so you can trigger it by hand) and a delete phase atmin_age: 30d. - Create an index template
logs-template(patternlogs-*) that wires inindex.lifecycle.nameandindex.lifecycle.rollover_alias: logs-write, plus a strict mapping with@timestamp,service,level,message. - Bootstrap
logs-000001with a write aliaslogs-write(is_write_index: true) and a read aliaslogs-read. - Index ~20 documents into
logs-write, then force a rollover:POST logs-write/_rollover { "conditions": { "max_primary_shard_size": "1mb" } }. - Verify with
GET logs-000001/_ilm/explainand confirmlogs-000002exists and the write alias moved. - Query
logs-read— both generations should be visible.
Data stream (modern):
- Create a data-stream index template
logs-app-template(patternlogs-app*,data_stream: {},@timestampmapped, the same ILM policy attached) — note there is no rollover alias and no bootstrap index. - Index a few documents directly into
logs-app(each with an@timestamp). Inspect the stream:GET _data_stream/logs-appandGET _cat/indices/.ds-logs-app*?v. - Trigger
POST logs-app/_rollover; confirm a new backing index (...-000002) and that writes advanced. - Search
logs-appwith a@timestamprange filter. Then try a direct update by_idagainstlogs-app— explain why it is rejected.
Takeaway: the data stream gives you the same rollover pipeline as steps 1–6 behind a single name, with no alias to bootstrap — at the cost of being append-only.
16) Performance & profiling (Chapter 07)
- Run an aggregation without a time/range filter,
then with one (
created_atrange). Comparetook. - Add
"profile": trueto a query and find where time is spent (query vs aggregation vs fetch). Try the Search Profiler UI in Dev Tools. - Take a field that is
text-only and try to aggregate/sort on it — observe the failure. Fix it by aggregating on the.keywordmulti-field. - Create a component template (shared mappings) and
compose it into an index template; verify with
_index_template/_simulate_index/<name>.
17) Operations & integration (Chapter 08)
- Cluster health: create an index with
number_of_replicas: 1on a single-node cluster → it goes yellow. UseGET _cluster/allocation/explainto see why, then set replicas to0and watch it return to green. - Snapshots: register a filesystem snapshot
repository, take a snapshot of
shop-orders-demo, then restore it under a new name usingrename_pattern/rename_replacement(so you don’t clobber the live index). - Ingest pipeline: create a pipeline that, for an
incoming order, sets an
@timestamp, lowercases a field, and converts a stringified amount to a number. Test it with_simulate, then index a document through it (?pipeline=...). - Decide: for each scenario, would you use an ingest pipeline, Beats, or Logstash? (a) shipping app log files, (b) light field normalization inside ES, (c) parsing many heterogeneous sources with buffering and multiple outputs.