Website logo

Robert Chang

技術部落格

Elasticsearch - 資料類型的強制轉換

上一篇文章 中有提到如何使用 Explicit Mapping 的方式來定義一個 index 的資料類型和結構。

那定義了資料的類型之後呢?

Elasticsearch ( 以下簡稱 ES ) 還有一個功能叫做 type coercion ( 類型轉換 )。

很多高階的程式語言也有相同的功能,舉例像是 JavaScript ( 以下簡稱 JS ) 來說,是可以比較兩個不同型別的值。

> 1 == '1'
=> true

了解 JS 的人都知道這樣是不好的使用方式,但還是可以用呀?JS 怎麼辦到的呢?其實也是透過 type coercion 的方式來轉換成相同的型別進行比較。

那這樣的轉換總是有極限的,拿一個物件和整數去做比較,就是預期要出現錯誤。

Type coercion

讓我們來做做實驗,看一下 ES 的 type coercion 能做到什麼程度呢?怎麼樣會出現錯誤呢?

直接建立一個 test 的 index 並往裡面新增一個 document,使用 float 這個型別作為 number 的值。

screen shot

成功,接著我們使用 GET /test/_mapping 這個 API 看一下現在 mapping 的結構:

// GET /test/_mapping
{
  "test": {
    "mappings": {
      "properties": {
        "number": {
          "type": "float"
        }
      }
    }
  }
}

可以看到 number 的 type 確實被自動地歸類成 float 了。

接著實驗第二個,用字串的方式來建立 document:

screen shot

也可以成功地建立資料,這邊就進行了一次 type coercion 的動作,ES 檢查這個值有沒有辦法被轉換成 float 的格式,可以的話,將會以 float 的格式儲存到 Lucene 之中。

有沒有辦法被完全轉換的規則大概就是字串中只含有正整數或是浮點數可以轉換,若含有文字,則沒辦法轉換

接著我們實驗看看第三種方式,故意在裡面加入一個不相干的字母:

screen shot

也是很不出意外地被阻止了,錯誤訊息如下:

failed to parse field [number] of type [float] in document with id '3'. Preview of field's value: '7.4q'

儲存的地方和你想的不一樣

接著讓我們讀取第二個 index 進去的 document 看看,有一個很有趣的事情,下面是得到的結果:

// GET /test/_doc/2
{
  "_index": "test",
  "_id": "2",
  "_version": 1,
  "_seq_no": 2,
  "_primary_term": 1,
  "found": true,
  "_source": {
    "number": "7.4"
  }
}

有發現什麼有趣的地方嗎?

_source 內的 number 竟然是字串!

你不是告訴我說會進行 type coercion 嗎?怎麼還是字串呢?

這個地方就很有趣了,其實 _source 內存放的是當時 index 我們傳向 ES 的值,而不是真正被儲存到 Lucene 的值,而 ES 也是對著 Lucene 那些被有意義的儲存起來的值進行搜尋,像是 這篇文章 中提到過的 inverted index 就是一個很好的例子,inverted index 本身並不是 _source 的內容,但它才是搜尋的主角。

所以如果您的應用程式有讀取 _source 中的內容,並且拿來做些什麼的話,要記住這些並不是真正地被儲存起來的值,而我們也要保持一個觀念,這個 "7.4" 其實是被以 float 的型態儲存到 Lucene 之中。

還有一些 Tips 可以紀錄一下:

Type Coercion 本身並不會在 Dynamic Mapping 時發生

什麼意思?當我們在一開始 index 第一份資料時,就是用 "7.4" 的話,ES 只會把它當作是 text 而不會自動轉換成 float

總是使用正確的資料類型

最棒的方式還是確保自己送過去的資料類型都是正確的,以避免非預期的錯誤發生。

關掉 type coercion

另一個方式就是告訴 ES,『 嘿!如果我送錯誤的資料類型,告訴我,我不要你幫我轉換。

怎麼關呢?這樣的設定方式只能在一開始建立 index 的時候透過 explicit mapping 的方式告訴 ES,像是這樣:

// PUT /test_index
{
  "settings": {
    "index.mapping.coerce": false
  },
  "mappings": {
    "properties": {
      "number": {
        "type": "float"
      }
    }
  }
}

之後 ES 遇到這個 index 都不會在幫助你進行 type coercion,並且會告訴你錯誤,錯誤訊息大致上如下:

{
  "error": {
    "root_cause": [
      {
        "type": "document_parsing_exception",
        "reason": "[2:13] failed to parse field [number] of type [float] in document with id '1'. Preview of field's value: '7.4'"
      }
    ],
    "type": "document_parsing_exception",
    "reason": "[2:13] failed to parse field [number] of type [float] in document with id '1'. Preview of field's value: '7.4'",
    "caused_by": {
      "type": "illegal_argument_exception",
      "reason": "Float value passed as String" // 原因
    }
  },
  "status": 400
}

你可能會想說,好麻煩,這樣如果一開始沒有設定好,之後要怎麼更改呢?

所以接下來的文章會介紹針對已經存在的 index 進行設定更改的策略和方式。

上一篇文章Elasticsearch - Mapping 以及基礎資料類型

下一篇文章Elasticsearch - ReIndex & Alias API