Elasticsearch 同一索引不同類型下同名字段的映射沖突實例

字號:


    這個標(biāo)題肯定繞暈很多人吧。具體說明一下場景就明白了:Nginx 和 Apache 的訪問日志,因為都屬于網(wǎng)站訪問,所以寫入到同一個索引的不同類型下,比方 logstash-accesslog-2015.04.03/nginx 和 logstash-accesslog-2015.04.03/apache。既然都是訪問日志,肯定很多字段的內(nèi)容含義是雷同的,比如 clientip, domain, urlpath 等等。其中 nginx 有一個變量叫 $request_time,apache 有一個變量叫 %T,乍看上去也是同義的,我就統(tǒng)一命名為 “requestTime” 了。這就是”同一索引(logstash-accesslog-YYYY.MM.DD)下不同類型(nginx,apache)的同名字段(requestTime)”。
    但事實上,這里有個問題:nginx 中的以秒為單位,是把毫秒算作小數(shù);apache 中的以秒為單位,是真的只記秒鐘整數(shù)位!
    所以,這兩個類型生成的映射在這個字段上是不一致的。nginx 類型的 requestTime 是 double,apache 類型的 requestTime 是 long。
    不過平??雌饋硭坪跻矝]什么影響,寫入數(shù)據(jù)都照常,查看數(shù)據(jù)的時候默認顯示的 JSON 也各自無異。直到我準(zhǔn)備用一把 scripted field 的時候,發(fā)現(xiàn)計算 doc['requestTime'].value * 1000 得到的數(shù)都大的嚇人!
    因為類似計算之前在只有 nginx 日志入庫的時候曾經(jīng)正確運行過,所以只能是猜測 apache 日志對此造成了影響,但是即使我把請求修改成限定在 nginx 類型數(shù)據(jù)中進行,結(jié)果也沒發(fā)生變化。
    仔細閱讀 scripting module 的文檔,其中提到了 doc['fieldname'].value 和 _source.fieldname 兩種寫法的區(qū)別:前者會利用內(nèi)存中的數(shù)據(jù),而后者強制讀取磁盤上 _source 存儲的 JSON 內(nèi)容,從中釋放出相應(yīng)字段內(nèi)容。莫非是 requestTime 字段跟 _source JSON 里存的數(shù)據(jù)確實不一樣,而我們平常搜索查看的都是從 JSON 里釋放出來的,所以才會如此?
    為了驗證我的猜測,做了一個請求測試:
    # curl es.domain.com:9200/logstash-accesslog-2015.04.03/nginx/_search?q=_id:AUx-QvSBS-dhpiB8_1f1&pretty -d '{
    "fields": ["requestTime", "bodySent"],
    "script_fields" : {
    "test1" : {
    "script" : "doc["requestTime"].value"
    },
    "test3" : {
    "script" : "_source.bodySent / _source.requestTime"
    },
    "test2" : {
    "script" : "doc["requestTime"].value * 1000"
    }
    }
    }'
    得到的結(jié)果如下:
    {
    "took" : 43,
    "timed_out" : false,
    "_shards" : {
    "total" : 56,
    "successful" : 56,
    "failed" : 0
    },
    "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [ {
    "_index" : "logstash-accesslog-2015.04.03",
    "_type" : "nginx",
    "_id" : "AUx-QvSBS-dhpiB8_1f1",
    "_score" : 1.0,
    "fields" : {
    "test1" : [ 4603039107142836552 ],
    "test2" : [ -8646911284551352000 ],
    "requestTime" : [ 0.54 ],
    "test3" : [ 2444.4444444444443 ],
    "bodySent" : [ 1320 ]
    }
    } ]
    }
    }
    果然!直接讀取的字段,以及采用 _source.fieldname 方式讀取的內(nèi)容,都是正確的;而采用 doc['fieldname'].value 獲取的內(nèi)存數(shù)據(jù),就不對。(0.54 存成 long 型會變成 4603039107142836552。這個 460 還正好能跟 540 湊成 1000,應(yīng)該是某種特定存法,不過這里我就沒深究了)
    再作下一步驗證。我們知道,ES 數(shù)據(jù)的映射是根據(jù)第一條數(shù)據(jù)的類型確定的,之后的數(shù)據(jù)如何類型跟已經(jīng)成型的映射不統(tǒng)一,那么寫入會失敗?,F(xiàn)在這個 nginx 和 apache 兩個類型在 requestTime 字段上的映射是不一樣的,但是內(nèi)存里卻并沒有按照映射來處理。那么,我往一個類型下寫入另一個類型映射要求的數(shù)據(jù),會報錯還是會通過呢?
    # curl -XPOST es.domain.com:9200/test/t1/1 -d '{"key":1}'
    {"_index":"test","_type":"t1","_id":"1","_version":1,"created":true}
    # curl -XPOST es.domain.com:9200/test/t2/1 -d '{"key":2.2}'
    {"_index":"test","_type":"t2","_id":"1","_version":1,"created":true}
    # curl -XPOST es.domain.com:9200/test/t1/2 -d '{"key":2.2}'
    {"_index":"test","_type":"t1","_id":"2","_version":1,"created":true}
    # curl -XPOST es.domain.com:9200/test/t2/2 -d '{"key":1}'
    {"_index":"test","_type":"t2","_id":"2","_version":1,"created":true}
    # curl -XPOST es.domain.com:9200/test/t1/3 -d '{"key":"1"}'
    {"_index":"test","_type":"t1","_id":"3","_version":1,"created":true}
    # curl -XPOST es.domain.com:9200/test/t2/3 -d '{"key":"1"}'
    {"_index":"test","_type":"t2","_id":"3","_version":1,"created":true}
    # curl -XPOST es.domain.com:9200/test/t2/4 -d '{"key":"abc"}'
    {"error":"RemoteTransportException[[10.10.10.10][inet[/10.10.10.10:9300]][indices:data/write/index]]; nested: MapperParsingException[failed to parse [key]]; nested: NumberFormatException[For input string: "abc"]; ","status":400}
    # curl -XGET es.domain.com:9200/test/_mapping
    {"test":{"mappings":{"t1":{"properties":{"key":{"type":"long"}}},"t2":{"properties":{"key":{"type":"double"}}}}}}
    結(jié)果出來了,在映射相互沖突以后,實際數(shù)據(jù)只要是 numeric detect 能通過的,就都通過了!
    BTW: kibana 4 中,已經(jīng)會對這種情況以黃色感嘆號圖標(biāo)做出提示;而根據(jù)官方消息,ES 未來會在 2.0 版正式杜絕這種可能。