ES 新增欄位預設值
ES 新增欄位支援過濾
業務背景
當我們在使用多維度檢索時,mysql顯然已經不能滿足我的的使用場景,尤其涉及到表之間的join且資料量較大時,mysql的查詢效能顯得捉襟見肘。
這時候ES的多維檢索功能就派上用場了。我們可以將兩張或者多張業務表,製作成一個比較寬的索引,監聽業務的binlog,並將資料儲存到ES中。
這樣就可以快速的支援業務檢索了。
業務需求
通常情況下,會使用ES的動態模板,之後新增其他的維度過濾會更加方便。
都知道ES底層儲存的是文件,當使用POST往動態模板中添加了欄位之後,之前的資料不會像mysql一樣可以設定預設值。
如果產品側又需要支援老資料的過濾時,這時候我們就涉及到刷ES索引的問題。
分析
按照資料的組織方式,將資料重新往ES插入一遍的方案肯定是不可行的,那麼我們有沒有命令可以類似mysql的set default值這樣的方式呢?
於是我去翻閱ES的官方文件,看到update是可以支援這種操作的。下面以 es動態索引中增加type型別為例演示解決過程。要實現的將es中原始的doc新增上type=0並支援索引。
現有文件數
GET index_test/_count?pretty { "count" : 2000, "_shards" : { "total" : 12, "successful" : 12, "skipped" : 0, "failed" : 0 } }
可以看到文件又2000條,
使用ES的term查詢:
term其實是分桶聚合查詢,可以理解為mysql的group by
POST index_test/_search?pretty
{
"size" : 0,
"aggs" : {
"aggType" : {
"terms" : {
"field" : "type"
}
}
}
}
查詢結果:
{ "took" : 2, "timed_out" : false, "_shards" : { "total" : 12, "successful" : 12, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 2000, "relation" : "gte" }, "max_score" : null, "hits" : [ ] }, "aggregations" : { "aggType" : { "doc_count_error_upper_bound" : 0, "sum_other_doc_count" : 0, "buckets" : [ { "key" : 1, "doc_count" : 5 }, { "key" : 2, "doc_count" : 4 }, { "key" : 3, "doc_count" : 4 } ] } } }
可以看到,type=0 的資料沒有,只有type = 1,2,3 的新生成的資料,分別為5,4,4條一共13條,與資料總條數2000
差了1987條,這些資料都是老資料,無法支援該欄位的檢索。
使用update更新
POST index_test/_update/1
{
"script": {
"lang": "painless",
"source": """
if (ctx._source.type == null) { ctx._source.type=0 }
"""
}
}
ES的更新支援script,這樣我們載更新文件時可以根據其他的一個或幾個欄位確認新增欄位的值,在這裡我使用
如果type 為空,將type賦值為預設值0。
更新完成後使用term查詢結果
```json
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 12,
"successful" : 12,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2000,
"relation" : "gte"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"aggType" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : 0,
"doc_count" : 1
}
{
"key" : 1,
"doc_count" : 5
},
{
"key" : 2,
"doc_count" : 4
},
{
"key" : 3,
"doc_count" : 4
}
]
}
}
}
發現只增加了一條,重複執行更新命令也不會再增加了,通過分析update語句發現,其命令列update後的1指的是doc
Id。這種方式顯然不能使用,我再猜想有沒有類似mysql中根據條件update的語句呀?檢視官方文件後,返現ES支援
update_by_query的操作
使用update_by_query
使用update_by_query語句,在這裡我刪除了script中的條件判斷,改成使用query
POST index_test/_update_by_query
{
"script": {
"lang": "painless",
"source": "ctx._source.type=0"
},
"query": {
"bool": {
"must_not": {
"exists": {
"field": "type"
}
}
}
}
}
其實使用scrpit的指令碼判斷要比query中使用must_not要慢。我理解使用script要access all 全表掃描。
如果使用了must_not 而且只有一個條件,我理解ES的執行引擎會使用倒排所以,查詢出有的,然後取反,把不存在該欄位的
doc ID返回。根據id去逐條更新,這樣判斷的次數從O(n)降到了理論的O(1)。
待結果返回後,重新使用term查詢結果:
POST index_test/_search?pretty
{
"size" : 0,
"aggs" : {
"aggType" : {
"terms" : {
"field" : "type"
}
}
}
}
查詢結果:
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 12,
"successful" : 12,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2000,
"relation" : "gte"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"aggType" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : 0,
"doc_count" : 1987
}
{
"key" : 1,
"doc_count" : 5
},
{
"key" : 2,
"doc_count" : 4
},
{
"key" : 3,
"doc_count" : 4
}
]
}
}
}
可以看到聚合結果中key=0的文件相較之前增加了好多,而且key = 0,1,2,3,4 的列舉加起來正好為2000.
證明更新成功了。
控制更新速度
在更新的過程中,如果要控制更新的速度,可以在更新的語句後新增引數,目前ES更新支援兩個方式
按照索引分片更新
POST index_test/_update_by_query?routing=1
其中routing為叢集的第幾個分片。
- 優點:單分片更新,如果分片被更新壞了,可以找運維刪除分片,副本分片會主動替換主分片,並重新
分片副本分片,在這期間索引的狀態可能是黃色。 - 缺點:更新不是原子的,而且需要清楚叢集有多少個主分片才可以操作。
按照分頁更新
POST index_test/_update_by_query?scroll_size=10000
其中scroll_size的最大值為叢集配置的允許的最大值,可以通過_settings命令查詢。
- 優點:可以控制叢集中資料的更新速度,降低修復資料時,叢集的負載。
- 缺點:需要判斷使用合理的分頁,一旦叢集崩潰就會影響線上環境。
觸類旁通
ES叢集使用的SSD的硬碟,而且對記憶體要求較高,
當叢集的儲存超過一半時(超過了一半ES就無法再實現段合併了,高併發寫入會產生較多分段Segment)
。一半情況下,業務的資料都是按照日期儲存的,這時候我們可以把較早的資料備份到HDFS系統上,然後
在ES的叢集上執行delete_by_query可以刪除部分歷史資料,這樣可以使得ES叢集一直處於比較好的效能區間。