ElasticSearch7.3 學習之訂製動態映射(dynamic mapping)

1、dynamic mapping

ElasticSearch中有一個非常重要的特性——動態映射,即索引文檔前不需要創建索引、類型等資訊,在索引的同時會自動完成索引、類型、映射的創建。

當ES在文檔中碰到一個以前沒見過的欄位時,它會利用動態映射(dynamic mapping)來決定該欄位的類型,並自動地對該欄位添加映射。

有時這正是需要的行為,但有時不是,需要留意。你或許不知道在以後你的文檔中會添加哪些欄位,但是你想要它們能夠被自動地索引。或許你只是想要忽略它們。或者,尤其當你將ES當做主要的數據存儲使用時,大概你會希望這些未知的欄位會拋出異常來提醒你注意這一問題。

幸運的是,你可以通過dynamic設置來控制這一行為,它能夠接受以下的選項:

  • true:默認值。動態添加欄位
  • false:新檢測到的欄位將被忽略。這些欄位將不會被索引,因此將無法搜索,但仍將出現在返回點擊的源欄位中。這些欄位不會添加到映射中,必須顯式添加新欄位。
  • strict:如果碰到陌生欄位,拋出異常

dynamic設置可以適用在根對象上或者object類型的任意欄位上。你應該默認地將dynamic設置為strict,但是為某個特定的內部對象啟用它:

創建mapping

PUT /my_index
{
  "mappings": {
    "dynamic": "strict",
    "properties": {
      "title": {
        "type": "text"
      },
      "address": {
        "type": "object",
        "dynamic": "true"
      }
    }
  }

插入數據

PUT /my_index/_doc/1
{
  "title": "my article",
  "content": "this is my article",
  "address": {
    "province": "guangdong",
    "city": "guangzhou"
  }
}

報錯,原因為content為新增欄位。會拋出異常

{
  "error": {
    "root_cause": [
      {
        "type": "strict_dynamic_mapping_exception",
        "reason": "mapping set to strict, dynamic introduction of [content] within [_doc] is not allowed"
      }
    ],
    "type": "strict_dynamic_mapping_exception",
    "reason": "mapping set to strict, dynamic introduction of [content] within [_doc] is not allowed"
  },
  "status": 400
}

2、自定義 dynamic mapping策略

2.1 數據類型

如果你知道你需要動態的添加的新欄位,那麼你也許會啟用動態映射。然而有時動態映射的規則又有些不夠靈活。幸運的是,你可以調整某些設置來讓動態映射的規則更加適合你的數據。

es會根據傳入的值,推斷類型,具體如下表所示。

JSON data type

Elasticsearch data type

ES中的數據類型

null

No field is added.

不會添加欄位

true or false

boolean field

boolean

floating point number

float field

double

integer

long field

long

object

object field

object

array

Depends on the first non-null value in the array.

依賴於第一個非null得值

string

Either a date field (if the value passes date detection), a double or long field (if the value passes numeric detection) or a text field, with a keyword sub-field.

如果通過了date檢測,則為date

如果通過了numeric檢測,則為Number

2.2 date_detection 日期探測

默認會按照一定格式識別date,比如yyyy-MM-dd。但是如果某個field先過來一個2017-01-01的值,就會被自動dynamic mappingdate,後面如果再來一個”hello world”之類的值,就會報錯。可以手動關閉某個typedate_detection,如果有需要,自己手動指定某個fielddate類型。

首先刪除上面新建的索引

DELETE my_index

然後下面的語句代表的含義為:日期探測為false,表示可添加不是date數據類型的欄位,也不會被推斷成date類型。address欄位裡面為true,代表可動態往裡面添加新的欄位。

PUT /my_index
{
  "mappings": {
    "date_detection": false,
    "properties": {
      "title": {
        "type": "text"
      },
      "address": {
        "type": "object",
        "dynamic": "true"
      }
    }
  }
}

測試插入數據。下面的語句代表最外層級新增contentpost_date兩個欄位,address層級新增provincecity欄位。

PUT /my_index/_doc/1
{
  "title": "my article",
  "content": "this is my article",
  "address": {
    "province": "guangdong",
    "city": "guangzhou"
  },
  "post_date": "2019-09-10"
}

查看映射

GET /my_index/_mapping

返回,可以看到欄位都成功新增,日期檢測(date_detection)為false,所以post_date欄位類型不是date類型。

{
  "my_index" : {
    "mappings" : {
      "date_detection" : false,
      "properties" : {
        "address" : {
          "dynamic" : "true",
          "properties" : {
            "city" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            },
            "province" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            }
          }
        },
        "content" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "post_date" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "title" : {
          "type" : "text"
        }
      }
    }
  }
}

下面為自定義日期格式語法,讀者可自行試驗。

PUT my_index
{
  "mappings": {
    "dynamic_date_formats": ["MM/dd/yyyy"]
  }
}

插入數據

PUT my_index/_doc/1
{
  "create_date": "09/25/2019"
}

2.3 numeric_detection 數字探測

雖然json支援浮點和整數數據類型,但某些應用程式或語言有時可能需要將數字呈現為字元串。通常正確的解決方案是顯式地映射這些欄位,但是可以啟用數字檢測(默認情況下禁用)來自動完成這些操作。

首先刪除上面新建的索引

DELETE my_index

然後開啟數字檢測

PUT my_index
{
  "mappings": {
    "numeric_detection": true
  }
}

插入數據

PUT my_index/_doc/1
{
  "my_float": "1.0",
  "my_integer": "1"
}

查看映射

GET my_index/_mapping

返回

{
  "my_index" : {
    "mappings" : {
      "numeric_detection" : true,
      "properties" : {
        "my_float" : {
          "type" : "float"
        },
        "my_integer" : {
          "type" : "long"
        }
      }
    }
  }
}

可以看到兩個欄位被映射為浮點類型了。

3、訂製dynamic mapping template

通過dynamic_templates,你可以擁有對新欄位的動態映射規則擁有完全的控制。你設置可以根據欄位名稱或者類型來使用一個不同的映射規則。

每個模板都有一個名字,可以用來描述這個模板做了什麼。同時它有一個mapping用來指定具體的映射資訊,和至少一個參數(比如match)用來規定對於什麼欄位需要使用該模板。

首先刪除上面新建的索引

DELETE my_index

運行下面的語句,代表的含義為:匹配以_en結尾並且是string類型的欄位,設置它的typetext,使用english分詞。

PUT /my_index
{
  "mappings": {
    "dynamic_templates": [
      {
        "en": {
          "match": "*_en",
          "match_mapping_type": "string",
          "mapping": {
            "type": "text",
            "analyzer": "english"
          }
        }
      }
    ]
  }
}

插入數據

PUT /my_index/_doc/1
{
  "title": "this is my first article"
}
PUT /my_index/_doc/2
{
  "title_en": "this is my first article"
}

查看映射

GET my_index/_mapping

返回,可以看到title_en欄位採用english分詞器,說明模板生效了。

{
  "my_index" : {
    "mappings" : {
      "dynamic_templates" : [
        {
          "en" : {
            "match" : "*_en",
            "match_mapping_type" : "string",
            "mapping" : {
              "analyzer" : "english",
              "type" : "text"
            }
          }
        }
      ],
      "properties" : {
        "title" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "title_en" : {
          "type" : "text",
          "analyzer" : "english"
        }
      }
    }
  }
}

搜索

GET my_index/_search?q=first
GET my_index/_search?q=is

title欄位沒有匹配到任何的dynamic模板,默認就是standard分詞器,不會過濾停用詞,is會進入倒排索引,用is來搜索是可以搜索到的

title_en欄位匹配到了dynamic模板,就是english分詞器,會過濾停用詞,is這種停用詞就會被過濾掉,用is來搜索就搜索不到了

4、模板寫法

下面給出了一些大概的寫法,讀者可根據自身實際需求自定義模板

PUT my_index
{
  "mappings": {
    "dynamic_templates": [
      {
        "integers": {
          "match_mapping_type": "long",
          "mapping": {
            "type": "integer"
          }
        }
      },
      {
        "strings": {
          "match_mapping_type": "string",
          "mapping": {
            "type": "text",
            "fields": {
              "raw": {
                "type":  "keyword",
                "ignore_above": 256
              }
            }
          }
        }
      }
    ]
  }
}

模板參數:匹配滿足的欄位、不匹配滿足的欄位、匹配的數據類型、路徑匹配、路徑不匹配。

"match":   "long_*",
"unmatch": "*_text",
"match_mapping_type": "string",
"path_match":   "name.*",
"path_unmatch": "*.middle",
"match_pattern": "regex",
"match": "^profit_\d+$"

5、應用場景

5.1 結構化搜索

默認情況下,elasticsearch將字元串欄位映射為帶有子關鍵字(keyword)欄位的文本欄位。但是,如果只對結構化內容進行索引,而對全文搜索不感興趣,則可以僅將「欄位」映射為「關鍵字」。但是請注意,這意味著為了搜索這些欄位,必須搜索索引所用的完全相同的值。

    {
        "strings_as_keywords": {
          "match_mapping_type": "string",
          "mapping": {
            "type": "keyword"
          }
        }
      }

5.2 僅搜索

與前面的示例相反,如果您只關心字元串欄位的全文搜索,並且不打算對字元串欄位運行聚合、排序或精確搜索,您可以將其僅映射為文本欄位(這是es5之前的默認行為)

    {
        "strings_as_text": {
          "match_mapping_type": "string",
          "mapping": {
            "type": "text"
          }
        }
      }

5.3 norms 不關心評分

當計算得分的時候,是否需要把欄位長度用作參數計算。

儘管計算得分時把欄位長度考慮在內可以提高得分的精確性,但這樣會消耗大量的磁碟空間(每個文檔的每個欄位都會消耗一個位元組,即使某些文檔不包含這個欄位)。因此,如果不需要計算欄位的得分,你應該禁用該欄位的norms。特別是這個欄位僅用於聚合或者過濾。

{
  "properties": {
    "title": {
      "type": "text",
      "norms": false
    }
  }
}