Skip to content
Navigation

Type at least two characters. Search covers page titles, headings, tags and database names.

↑ ↓ to navigateEnter to openEsc to close0 pages
Elasticsearch and OpenSearchintermediate

Mapping

Defining field types explicitly, choosing between text and keyword, and configuring the analysis chain.

2 min readIntermediateUpdated Edit this page

Mapping defines how each field is stored and indexed. It is the schema, and it is largely immutable once data exists.

Define it explicitly

PUT /articles
{
  "mappings": {
    "dynamic": "strict",
    "properties": {
      "title":     { "type": "text", "analyzer": "english",
                     "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } },
      "body":      { "type": "text", "analyzer": "english" },
      "status":    { "type": "keyword" },
      "tags":      { "type": "keyword" },
      "views":     { "type": "integer" },
      "published": { "type": "date" },
      "author":    { "type": "object",
                     "properties": { "id": { "type": "keyword" }, "name": { "type": "keyword" } } }
    }
  }
}

text versus keyword

This is the distinction that matters most:

textkeyword
Analysed into termsYesNo
Full-text searchYesNo
Exact match, sort, aggregateNoYes

A field you both search and facet on needs both, via a multi-field as in title above: title for matching, title.keyword for sorting and aggregation.

Analyzers

An analyzer is a chain: character filters, then a tokenizer, then token filters. It runs at index time and at query time, and the two must agree or nothing matches.

PUT /articles
{
  "settings": {
    "analysis": {
      "filter": {
        "english_stop":    { "type": "stop", "stopwords": "_english_" },
        "english_stemmer": { "type": "stemmer", "language": "english" },
        "synonyms":        { "type": "synonym", "synonyms": ["db, database", "k8s, kubernetes"] }
      },
      "analyzer": {
        "content": {
          "type": "custom",
          "char_filter": ["html_strip"],
          "tokenizer": "standard",
          "filter": ["lowercase", "english_stop", "synonyms", "english_stemmer"]
        }
      }
    }
  }
}

Test the chain rather than reasoning about it:

POST /articles/_analyze
{ "analyzer": "content", "text": "Running <b>databases</b> in production" }

The output shows exactly which terms are indexed. Most "why does this search not match" questions are answered here in seconds.

Changing a mapping

Fields worth disabling

Every enabled feature costs index size and indexing time:

{ "properties": {
    "raw_payload": { "type": "object", "enabled": false },        // stored, not indexed
    "internal_id": { "type": "keyword", "doc_values": false },    // not aggregatable or sortable
    "body":        { "type": "text", "norms": false }             // no length normalisation
}}

For log data in particular, disabling indexing on fields nobody queries is one of the largest available savings.