- 錯誤提示
{
"statusCode": 429,
"error": "Too Many Requests",
"message": "[circuit_breaking_exception]
[parent] Data too large, data for [<http_request>] would be [2087772160/1.9gb],
which is larger than the limit of [1503238553/1.3gb],
real usage: [2087772160/1.9gb],
new bytes reserved: [0/0b],
usages [request=0/0b, fielddata=1219/1.1kb, in_flight_requests=0/0b, accounting=605971/591.7kb],
with { bytes_wanted=2087772160 & bytes_limit=1503238553 & durability=\"PERMANENT\" }"
}
重要解決辦法
關閉circuit檢查:
indices.breaker.type: none
集群config/jvm.options設置如下
-Xms2g
-Xmx2g
#-XX:+UseConcMarkSweepGC
-XX:+UseG1GC
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly
以下這些都不用看了
再嘗試其他查詢也是如此。經排查,原來是ES默認的緩存設置讓緩存區只進不出引起的,具體分析一下。
- ES緩存區概述
ES在查詢時,會將索引數據緩存在內存(JVM)中:
上圖是ES的JVM Heap中的狀況,可以看到有兩條界限:驅逐線 和 斷路器。當緩存數據到達驅逐線時,會自動驅逐掉部分數據,把緩存保持在安全的范圍內。
當用戶准備執行某個查詢操作時,斷路器就起作用了,緩存數據+當前查詢需要緩存的數據量到達斷路器限制時,會返回Data too large錯誤,阻止用戶進行這個查詢操作。
ES把緩存數據分成兩類,FieldData和其他數據,我們接下來詳細看FieldData,它是造成我們這次異常的“元凶”。
-
FieldData
ES配置中提到的FieldData指的是字段數據。當排序(sort),統計(aggs)時,ES把涉及到的字段數據全部讀取到內存(JVM Heap)中進行操作。相當於進行了數據緩存,提升查詢效率。 -
監控FieldData
仔細監控fielddata使用了多少內存以及是否有數據被驅逐是非常重要的。
ielddata緩存使用可以通過下面的方式來監控
# 對於單個索引使用 {ref}indices-stats.html[indices-stats API]
GET /_stats/fielddata?fields=*
# 對於單個節點使用 {ref}cluster-nodes-stats.html[nodes-stats API]
GET /_nodes/stats/indices/fielddata?fields=*
#或者甚至單個節點單個索引
GET /_nodes/stats/indices/fielddata?level=indices&fields=*
# 通過設置 ?fields=* 內存使用按照每個字段分解了
fielddata中的memory_size_in_bytes表示已使用的內存總數,而evictions(驅逐)為0。且經過一段時間觀察,字段所占內存大小都沒有變化。由此推斷,當下的緩存處於無法有效驅逐的狀態。
- Cache配置
indices.fielddata.cache.size 配置fieldData的Cache大小,可以配百分比也可以配一個准確的數值。cache到達約定的內存大小時會自動清理,驅逐一部分FieldData數據以便容納新數據。默認值為unbounded無限。
indices.fielddata.cache.expire用於約定多久沒有訪問到的數據會被驅逐,默認值為-1,即無限。expire配置不推薦使用,按時間驅逐數據會大量消耗性能。而且這個設置在不久之后的版本中將會廢棄。
看來,Data too large異常就是由於fielddata.cache的默認值為unbounded導致的了。
- FieldData格式
除了緩存取大小之外,我們還可以控制字段數據緩存到內存中的格式。
在mapping中,我們可以這樣設置:
{
"tag": {
"type": "string",
"fielddata": {
"format": "fst"
}
}
}
對於String類型,format有以下幾種:
paged_bytes (默認):使用大量的內存來存儲這個字段的terms和索引。
fst:用FST
的形式來存儲terms。這在terms有較多共同前綴的情況下可以節約使用的內存,但訪問速度上比paged_bytes 要慢。
doc_values:fieldData始終存放在disk中,不加載進內存。訪問速度最慢且只有在index:no/not_analyzed的情況適用。
對於數字和地理數據也有可選的format,但相對String更為簡單,具體可在api中查看。
從上面我們可以得知一個信息:我們除了配置緩存區大小以外,還可以對不是特別重要卻量很大的String類型字段選擇使用fst緩存類型來壓縮大小。
- 斷路器
fieldData的緩存配置中,有一個點會引起我們的疑問:fielddata的大小是在數據被加載之后才校驗的。假如下一個查詢准備加載進來的fieldData讓緩存區超過可用堆大小會發生什么?很遺憾的是,它將產生一個OOM異常。
斷路器就是用來控制cache加載的,它預估當前查詢申請使用內存的量,並加以限制。斷路器的配置如下:
indices.breaker.fielddata.limit:這個 fielddata 斷路器限制fielddata的大小,默認情況下為堆大小的60%。
indices.breaker.request.limit:這個 request 斷路器估算完成查詢的其他部分要求的結構的大小, 默認情況下限制它們到堆大小的40%。
indices.breaker.total.limit:這個 total 斷路器封裝了 request 和 fielddata 斷路器去確保默認情況下這2個部分使用的總內存不超過堆大小的70%。
查詢
/_cluster/settings
設置
PUT /_cluster/settings
{
"persistent": {
"indices.breaker.fielddata.limit": "60%"
}
}
PUT /_cluster/settings
{
"persistent": {
"indices.breaker.request.limit": "40%"
}
}
PUT /_cluster/settings
{
"persistent": {
"indices.breaker.total.limit": "70%"
}
}
斷路器限制可以通過文件 config/elasticsearch.yml 指定,也可以在集群上動態更新:
PUT /_cluster/settings
{
"persistent" : {
"indices.breaker.fielddata.limit" : 40%
}
}
當緩存區大小到達斷路器所配置的大小時會發生什么事呢?答案是:會返回開頭我們說的Data too large異常。這個設定是希望引起用戶對ES服務的反思,我們的配置有問題嗎?是不是查詢語句的形式不對,一條查詢語句需要使用這么多緩存嗎?
在文件 config/elasticsearch.yml 文件中設置緩存使用回收
indices.fielddata.cache.size: 40%
- 總結
1.這次Data too large異常是ES默認配置的一個坑,我們沒有配置indices.fielddata.cache.size,它就不回收緩存了。緩存到達限制大小,無法往里插入數據。個人感覺這個默認配置不友好,不知ES是否在未來版本有所改進。
2. 當前fieldData緩存區大小 < indices.fielddata.cache.size
當前fieldData緩存區大小+下一個查詢加載進來的fieldData < indices.breaker.fielddata.limit
fielddata.limit的配置需要比fielddata.cache.size稍大。而fieldData緩存到達fielddata.cache.size的時候就會啟動自動清理機制。expire配置不建議使用。
3.indices.breaker.request.limit限制查詢的其他部分需要用的內存大小。indices.breaker.total.limit限制總(fieldData+其他部分)大小。
4.創建mapping時,可以設置fieldData format控制緩存數據格式。