一、bucket与metric
1、bucket相当于mysql的group by。
2、metric:对一个数据分组执行的统计,比如说求平均值,求最大值,求最小值
二、实战
1、例1:查询参数及结果说明
GET /tvs/sales/_search
{
"size" : 0,
"aggs" : {
"popular_colors" : {
"terms" : {
"field" : "color"
}
}
}
}
size:只获取聚合结果,而不要执行聚合的原始数据
aggs:固定语法,要对一份数据执行分组聚合操作
popular_colors:就是对每个aggs,都要起一个名字,这个名字是随机的,你随便取什么都ok
terms:根据字段的值进行分组
field:根据指定的字段的值进行分组
结果:
hits.hits:我们指定了size是0,所以hits.hits就是空的,否则会把执行聚合的那些原始数据给你返回回来
aggregations:聚合结果
popular_color:我们指定的某个聚合的名称
buckets:根据我们指定的field划分出的buckets
key:每个bucket对应的那个值
doc_count:这个bucket分组内,有多少个数据
2、例2:对bucket执行metric聚合操作
GET /tvs/sales/_search
{
"size" : 0,
"aggs": {------------------>先进行bucket,再进行metric操作
"colors": {
"terms": {---------->bucket操作
"field": "color"
},
"aggs": { ------------------->先进行bucket,再进行metric操作
"avg_price": {
"avg": {----------------->metric操作
"field": "price"
}
}
}
}
}
}
3、例3:多层下钻(分组再分组)
GET /tvs/sales/_search
{
"size": 0,
"aggs": {
"group_by_color": {
"terms": {------------------------>分组1
"field": "color"
},
"aggs": {
"color_avg_price": {
"avg": {
"field": "price"
}
},
"group_by_brand": {
"terms": {------------------------>分组2
"field": "brand"
},
"aggs": {
"brand_avg_price": {
"avg": {----------------------->求平均数
"field": "price"
}
}
}
}
}
}
}
}
4、例4:常见的数据分析的操作-->count,avg,max,min,sum
GET /tvs/sales/_search
{
"size" : 0,
"aggs": {
"colors": {
"terms": {
"field": "color"
},
"aggs": {
"avg_price": { "avg": { "field": "price" } },
"min_price" : { "min": { "field": "price"} },
"max_price" : { "max": { "field": "price"} },
"sum_price" : { "sum": { "field": "price" } }
}
}
}
}
5、例5:histogram
histogram:类似于terms,也是进行bucket分组操作
GET /tvs/sales/_search
{
"size" : 0,
"aggs":{
"price":{----------------->聚和名称
"histogram":{ ----------------->相当于bucke,分组操作
"field": "price",
"interval": 2000---------------->按区间进行分组
},
"aggs":{
"revenue": {----------------->聚和名称
"sum": {
"field" : "price"
}
}
}
}
}
}
6、例6:按照日期进行分组:date_histogram
GET /tvs/sales/_search
{
"size" : 0,
"aggs": {
"sales": {---->聚合名称
"date_histogram": {
"field": "sold_date",
"interval": "month",
"format": "yyyy-MM-dd",
"min_doc_count" : 0, ---->即使某个日期interval,2017-01-01~2017-01-31中,一条数据都没有,那么这个区间也是要展现的,不然默认是会过滤掉这个区间的
"extended_bounds" : { ----->划分bucket的时候,会限定在这个起始日期,和截止日期内
"min" : "2016-01-01",
"max" : "2017-12-31"
}
}
}
}
}
7、例7:搜索+聚合
scope:任何的聚合,都必须在搜索出来的结果数据中之行,搜索结果,就是聚合分析操作的scope
GET /tvs/sales/_search
{
"size": 0,
"query": {
"term": {
"brand": {
"value": "小米"
}
}
},
"aggs": {
"group_by_color": {
"terms": {
"field": "color"
}
}
}
}
8、例:出来两个结果,一个结果,是基于query搜索结果来聚合的; 一个结果,是对所有数据执行聚合的
GET /tvs/sales/_search
{
"size": 0,
"query": {
"term": {
"brand": {
"value": "长虹"
}
}
},
"aggs": {
"single_brand_avg_price": {
"avg": {
"field": "price"
}
},
"all": {
"global": {},--------->global:就是global bucket,就是将所有数据纳入聚合的scope,而不管之前的query
"aggs": {
"all_brand_avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
9、过滤+聚合 (原理同搜索+聚合)
GET /tvs/sales/_search
{
"size": 0,
"query": {
"constant_score": {
"filter": {
"range": {
"price": {
"gte": 1200
}
}
}
}
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
10、bucket_filter:(局部filter),对不同的bucket下的aggs,进行filter
GET /tvs/sales/_search
{
"size": 0,
"query": {
"term": {
"brand": {
"value": "长虹"
}
}
},
"aggs": {
"recent_150d": {
"filter": {
"range": {
"sold_date": {
"gte": "now-150d"
}
}
},
"aggs": {
"recent_150d_avg_price": {
"avg": {
"field": "price"
}
}
}
},
"recent_140d": {
"filter": {
"range": {
"sold_date": {
"gte": "now-140d"
}
}
},
"aggs": {
"recent_140d_avg_price": {
"avg": {
"field": "price"
}
}
}
},
"recent_130d": {
"filter": {
"range": {
"sold_date": {
"gte": "now-130d"
}
}
},
"aggs": {
"recent_130d_avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
11、排序:默认按bucket的doc_count降序排,指定聚合数排序(metric)
GET /tvs/sales/_search
{
"size": 0,
"aggs": {
"group_by_color": {
"terms": {
"field": "color",
"order": {
"avg_price": "asc"
}
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
三、易并行算法+近似聚合算法+三角选择原则
1、易并行聚合算法:max
很容易就可以并行的
2、近似聚合算法:distinct count
es会采取近似聚合的方式,就是采用在每个node上进行近估计的方式,得到最终的结论,cuont(distcint),100万,1050万/95万 --> 5%左右的错误率
近似估计后的结果,不完全准确,但是速度会很快,一般会达到完全精准的算法的性能的数十倍
3、三角选择原则
精准+实时+大数据 --> 选择2个
(1)精准+实时: 没有大数据,数据量很小,那么一般就是单击跑,随便你则么玩儿就可以
(2)精准+大数据:hadoop,批处理,非实时,可以处理海量数据,保证精准,可能会跑几个小时
(3)大数据+实时:es,不精准,近似估计,可能会有百分之几的错误率
四、cartinality metric
1、说明:es,去重,cartinality metric,对每个bucket中的指定的field进行去重,取去重后的count,类似于count(distcint)。5%的错误率,性能在100ms左右
2、例:
、GET /tvs/sales/_search
{
"size" : 0,
"aggs" : {
"months" : {
"date_histogram": {
"field": "sold_date",
"interval": "month"
},
"aggs": {
"distinct_colors" : {
"cardinality" : {
"field" : "brand"
}
}
}
}
}
}
3、precision_threshold优化准确率和内存开销
GET /tvs/sales/_search
{
"size" : 0,
"aggs" : {
"distinct_brand" : {
"cardinality" : {
"field" : "brand",
"precision_threshold" : 100 --->如果field的unique value,在100个以内,就将precision_threshold设置为100。cardinality算法,会占用precision_threshold * 8 byte 内存消耗,100 * 8 = 800个字节
}
}
}
}
precision_threshold:估计有多少个unique field-value就将precision_threshold设置为几,es会占用precision_threshold * 8 byte 内存消耗
4、HyperLogLog++ (HLL)算法性能优化
cardinality底层算法:HLL算法
会对所有的uqniue value取hash值,通过hash值近似去求distcint count
默认情况下,发送一个cardinality请求的时候,会动态地对所有的field value,取hash值;
优化:将取hash值的操作,前移到建立索引的时候
PUT /tvs/
{
"mappings": {
"sales": {
"properties": {
"brand": {
"type": "text",
"fields": {
"hash": {
"type": "murmur3"
}
}
}
}
}
}
}
GET /tvs/sales/_search
{
"size" : 0,
"aggs" : {
"distinct_brand" : {
"cardinality" : {
"field" : "brand.hash",
"precision_threshold" : 100
}
}
}
}
五、percentiles && percentile_ranks
1、常用于 网站的延时统计(tp50,tp99等)
tp50:50%的请求的耗时最长在多长时间
tp90:90%的请求的耗时最长在多长时间
tp99:99%的请求的耗时最长在多长时间
SLA:就是你提供的服务的标准
网站的提供的访问延时的SLA,大公司内,一般都是要求100%在200ms以内
2、percentiles&percents:统计百分之多少的field最大值
例如:
GET /website/logs/_search
{
"size": 0,
"aggs": {
"latency_percentiles": {
"percentiles": {
"field": "latency",
"percents": [
50,
95,
99
]
}
},
"latency_avg": {
"avg": {
"field": "latency"
}
}
}
}
3、percentile_ranks:统计一个field的值在[x,y]范围内的百分比
例如:
GET /website/logs/_search
{
"size": 0,
"aggs": {
"group_by_province": {
"terms": {
"field": "province"
},
"aggs": {
"latency_percentile_ranks": {
"percentile_ranks": {
"field": "latency",
"values": [
200,
1000
]
}
}
}
}
}
}
六、doc_value 正排索引
doc_vlaue就是正排索引的数据结构,聚合操作会使用到正排索引
1、doc value原理
a、建立索引的时候默认就会生成正排索引
b、正排索引,也会写入磁盘文件中,然后呢,os cache先进行缓存,以提升访问doc value正排索引的性能。如果os cache内存大小不足够放得下整个正排索引,doc value,就会将doc value的数据写入磁盘文件中
c、性能问题:给jvm更少内存,64g服务器,给jvm最多16g
es官方是建议,es大量是基于os cache来进行缓存和提升性能的,不建议用jvm内存来进行缓存,那样会导致一定的gc开销和oom问题
给jvm更少的内存,给os cache更大的内存
例如:64g服务器,给jvm最多16g,几十个g的内存给os cache
2、column压缩
doc1: 550
doc2: 550
doc3: 500
合并相同值,550,doc1和doc2都保留一个550的标识即可
(1)所有值相同,直接保留单值
(2)少于256个值,使用table encoding模式:一种压缩方式
(3)大于256个值,看有没有最大公约数,有就除以最大公约数,然后保留这个最大公约数
doc1: 36
doc2: 24
12 --> doc1: 3, doc2: 2 --> 保留一个最大公约数12的标识,12也保存起来
(4)如果没有最大公约数,采取offset结合压缩的方式:
3、disable doc value
doc_vlaue就是正排索引的数据结构,聚合操作会使用到正排索引,如果没有聚合操作,可将其禁用。
如果的确不需要doc value,比如聚合等操作,那么可以禁用,减少磁盘空间占用
PUT my_index
{
"mappings": {
"my_type": {
"properties": {
"my_field": {
"type": "keyword"
"doc_values": false
}
}
}
}
}
七、string field 与fieldData
1、对于string或text等分词类型的field进行聚合操作时,需要设置:fieldData = true, 否则会报错,如下:
"reason": "Fielddata is disabled on text fields by default. Set fielddata=true on [test_field] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory."
2、例如:
POST /test_index/_mapping/test_type
{
"properties": {
"test_field": {
"type": "text",
"fielddata": true
}
}
}
{
"test_index": {
"mappings": {
"test_type": {
"properties": {
"test_field": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
},
"fielddata": true
}
}
}
}
}
}
3、fieldData与doc_value的区别
fieldData: 分词field,是没有doc value。对于分词field,必须打开和使用fielddata=true,且是懒加载(query-time时加载)完全存在于纯内存中。
doc_value:如果field不分词,那么在index-time,就会自动生成doc value --> 针对这些不分词的field执行聚合操作的时候,自动就会用doc value来执行
八、fieldData内存
1、lazy load
fielddata加载到内存的过程是lazy加载的,对一个analzyed field执行聚合时,才会加载,query-time时加载。
2、fielddata内存限制
indices.fielddata.cache.size: 20%。fielddata占用的内存超出了这个比例的限制,那么就清除掉内存中已有的fielddata数据
默认无限制。
限制内存使用,但是会导致频繁evict和reload,大量IO性能损耗,以及内存碎片和gc。
3、监控fielddata内存使用
GET /_stats/fielddata?fields=* ---------->每个分片(share)中的每个索引中的fieldData内存使用情况
GET /_nodes/stats/indices/fielddata?fields=*--------->每个个node中fieldData内存使用情况
GET /_nodes/stats/indices/fielddata?level=indices&fields=*------->每个node中的每个索引中的fieldData内存使用情况
4、circuit breaker
如果一次query load的feilddata超过总内存,就会oom --> 内存溢出
circuit breaker会估算query要加载的fielddata大小,如果超出总内存,就短路,query直接失败,如下:
indices.breaker.fielddata.limit:fielddata的内存限制,默认60%
indices.breaker.request.limit:执行聚合的内存限制,默认40%
indices.breaker.total.limit:综合上面两个,限制在70%以内
九、fieldData- filter
1、使用:
POST /test_index/_mapping/my_type
{
"properties": {
"my_field": {
"type": "text",
"fielddata": {
"filter": {
"frequency": {
"min": 0.01,
"min_segment_size": 500
}
}
}
}
}
}
2、说明:
min:仅仅加载至少在1%的doc中出现过的term对应的fielddata
比如说某个值,hello,总共有1000个doc,hello必须在10个doc中出现,那么这个hello对应的fielddata才会加载到内存中来
min_segment_size:少于500 doc的segment不加载fielddata
加载fielddata的时候,也是按照segment去进行加载的,某个segment里面的doc数量少于500个,那么这个segment的fielddata就不加载
十、fieldData预加载
如果真的要对分词的field执行聚合,那么每次都在query-time现场生产fielddata并加载到内存中来,速度可能会比较慢
1、fielddata预加载
POST /test_index/_mapping/test_type
{
"properties": {
"test_field": {
"type": "string",
"fielddata": {
"loading" : "eager"
}
}
}
}
query-time的fielddata生成和加载到内存,变为index-time,建立倒排索引的时候,会同步生成fielddata并且加载到内存中来,这样的话,对分词field的聚合性能当然会大幅度增强
2、序号标记预加载
global ordinal原理解释
doc1: status1
doc2: status2
doc3: status2
doc4: status1
有很多重复值的情况,会进行global ordinal标记
status1 --> 0
status2 --> 1
doc1: 0
doc2: 1
doc3: 1
doc4: 0
建立的fielddata也会是这个样子的,这样的好处就是减少重复字符串的出现的次数,减少内存的消耗,如下:
POST /test_index/_mapping/test_type
{
"properties": {
"test_field": {
"type": "string",
"fielddata": {
"loading" : "eager_global_ordinals"
}
}
}
}
十一、下钻聚合操作的深度优先和广度优先
1、区别
深度优先:默认就是深度优先,树状结构
广度优先:先水平搜索
2、使用:
{
"aggs" : {
"actors" : {
"terms" : {
"field" : "actors",
"size" : 10,
"collect_mode" : "breadth_first" --------->广度优先
},
"aggs" : {
"costars" : {
"terms" : {
"field" : "films",
"size" : 5
}
}
}
}
}
}