{"type":"doc","content":[{"type":"heading","attrs":{"id":"93c236be-452b-43e1-8803-365d88c5f58f","textAlign":"inherit","indent":0,"level":2,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"bold"}],"text":"说明"}]},{"type":"paragraph","attrs":{"id":"0a208c75-e5e8-4e02-9caa-2ed0e51d2a0e","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","text":"本文描述问题及解决方法同样适用于 "},{"type":"text","marks":[{"type":"link","attrs":{"href":"https://cloud.tencent.com/document/product/845?from=20421&from_column=20421","target":"_blank","rel":"noopener noreferrer nofollow","class":""}}],"text":"腾讯云 Elasticsearch Service(ES)"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"id":"a344a37d-7b89-4447-9f10-3826d009d432","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","text":"本文延续上一篇:"},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"rgb(0, 0, 0)","background":""}}],"text":"https://cloud.tencent.com/developer/article/1806854"}]},{"type":"paragraph","attrs":{"id":"7c6584ca-d0d4-41e8-b358-e55d4080f509","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false}},{"type":"heading","attrs":{"id":"5e2a9a35-e0c1-4524-a258-bef32374efb6","textAlign":"inherit","indent":0,"level":2,"isHoverDragHandle":false},"content":[{"type":"text","text":"前言"}]},{"type":"paragraph","attrs":{"id":"60985db9-e6bd-4402-ab44-b2d6a03b4030","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"前几年在使用 Elasticsearch 的直方图聚合(Histogram Aggregation)时,遇到了一个有意思的问题:明明设置了"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"code"}],"text":"extended_bounds"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"来限制数据范围,但返回的结果却出现了越界的情况。这个问题看似简单,但背后涉及到 ES 聚合的一些设计细节。多年以后的今天,我们就来回顾一下这个问题,顺便看看源码是怎么处理的。"}]},{"type":"paragraph","attrs":{"id":"e4525f8e-e981-4678-806d-04c3df89b3df","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false}},{"type":"heading","attrs":{"id":"7a189237-d9ec-42d9-9a3e-34de2007a740","textAlign":"inherit","indent":0,"level":2,"isHoverDragHandle":false},"content":[{"type":"text","text":"问题复现"}]},{"type":"paragraph","attrs":{"id":"79b280ae-8ae0-4722-a7bd-becbe7dfd840","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"先来看一个实际的查询例子:"}]},{"type":"codeBlock","attrs":{"id":"f6f95735-cc5e-4460-9228-02cf5793e593","language":"javascript","theme":"atom-one-dark","runtimes":0,"isHoverDragHandle":false,"key":""},"content":[{"type":"text","text":"GET robot_msg_202012/_search\n{\n \"size\": 0,\n \"query\": {\n \"bool\": {\n \"must\": [\n {\n \"term\": {\n \"kfuin\": {\n \"value\": \"2852199391\"\n }\n }\n },\n {\n \"range\": {\n \"msg_time\": {\n \"gte\": 1607529600000000,\n \"lte\": 1608134399000000\n }\n }\n }\n ]\n }\n },\n \"aggs\": {\n \"time_range_aggs\": {\n \"histogram\": {\n \"field\": \"msg_time\",\n \"interval\": 86400000000,\n \"min_doc_count\": 0,\n \"extended_bounds\": {\n \"min\": 1607529600000000,\n \"max\": 1608134399000000\n }\n }\n }\n }\n}\n"}]},{"type":"paragraph","attrs":{"id":"6ec51ae6-8556-450c-81b5-b19266c94bee","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"这个查询的意图很明确:"}]},{"type":"bulletList","attrs":{"id":"a5b86c75-fefd-47e1-b07c-c040b8b4ba97","isHoverDragHandle":false},"content":[{"type":"listItem","attrs":{"id":"4a2823f9-0621-4057-a175-95d7e7dd1804"},"content":[{"type":"paragraph","attrs":{"id":"85fd4a40-960e-4193-97fa-bfad9d7dfd81","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","text":"查询时间范围:"},{"type":"text","marks":[{"type":"code"}],"text":"1607529600000000"},{"type":"text","text":" 到 "},{"type":"text","marks":[{"type":"code"}],"text":"1608134399000000"},{"type":"text","text":"(微秒级时间戳)"}]}]},{"type":"listItem","attrs":{"id":"9d7ace0d-1afc-41e4-beb3-8ba77cf9b7af"},"content":[{"type":"paragraph","attrs":{"id":"d5c5a275-3ded-4bf4-ba1f-d85d0f52377f","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","text":"聚合区间:"},{"type":"text","marks":[{"type":"code"}],"text":"86400000000"},{"type":"text","text":" 微秒(即 1 天)"}]}]},{"type":"listItem","attrs":{"id":"986fb579-23ac-4dde-840c-7a4d7ba49faf"},"content":[{"type":"paragraph","attrs":{"id":"f26b78db-2d30-4dba-9e39-ad3fcd875938","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","text":"使用 "},{"type":"text","marks":[{"type":"code"}],"text":"extended_bounds"},{"type":"text","text":" 限制聚合结果的范围"}]}]}]},{"type":"paragraph","attrs":{"id":"ea1c213b-aa2f-45ac-8559-a122cbad1fad","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"按理说,返回的桶(bucket)的 key 应该都在"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"code"}],"text":"extended_bounds"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"指定的范围内。但实际返回的结果却是这样的:"}]},{"type":"codeBlock","attrs":{"id":"8a07dd88-765f-41a6-aa52-65423bb2feba","language":"javascript","theme":"atom-one-dark","runtimes":0,"isHoverDragHandle":false,"key":""},"content":[{"type":"text","text":"{\n \"aggregations\": {\n \"time_range_aggs\": {\n \"buckets\": [\n {\n \"key\": 1607472000000000, // 这个值小于 min!\n \"doc_count\": 0\n },\n {\n \"key\": 1607558400000000,\n \"doc_count\": 1\n },\n // ... 更多桶\n ]\n }\n }\n}\n"}]},{"type":"paragraph","attrs":{"id":"65b7185f-1949-46b7-aae6-da4072d1e899","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"第一个桶的 key 是"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"code"}],"text":"1607472000000000"},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":",明显小于我们设置的"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"code"}],"text":"min: 1607529600000000"},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"。这就是所谓的"},{"type":"text","text":"\""},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"越界"},{"type":"text","text":"\""},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"问题。"}]},{"type":"paragraph","attrs":{"id":"6832a1da-fed0-4dd4-ae3e-d7391dbeaf91","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false}},{"type":"heading","attrs":{"id":"d15d4293-5a2c-4e10-8008-933c4cc7dc83","textAlign":"inherit","indent":0,"level":2,"isHoverDragHandle":false},"content":[{"type":"text","text":"问题分析"}]},{"type":"heading","attrs":{"id":"095048b5-66c2-436f-8c33-b75c5e756e2d","textAlign":"inherit","indent":0,"level":3,"isHoverDragHandle":false},"content":[{"type":"text","text":"extended_bounds 的真正作用"}]},{"type":"paragraph","attrs":{"id":"c6d77278-1876-40d2-b764-4efb67d1b7a0","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"首先要明确一点:"},{"type":"text","marks":[{"type":"code"}],"text":"extended_bounds"},{"type":"text","marks":[{"type":"bold"}],"text":" 并不是用来过滤数据的"},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"。它的作用是:"}]},{"type":"orderedList","attrs":{"id":"6cc1bc09-8424-4f6e-b9ec-bb641712b91a","start":1,"isHoverDragHandle":false},"content":[{"type":"listItem","attrs":{"id":"d0451fd0-eccd-45e1-8884-9d07945b59af"},"content":[{"type":"paragraph","attrs":{"id":"85a67b97-1461-404d-894e-74c65c1a4356","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","text":"当实际数据范围小于 "},{"type":"text","marks":[{"type":"code"}],"text":"extended_bounds"},{"type":"text","text":" 时,会在两端补充空桶"}]}]},{"type":"listItem","attrs":{"id":"f97a0b5b-a7c5-4595-85cd-bc5b84f5bcf2"},"content":[{"type":"paragraph","attrs":{"id":"e4141864-d0b7-421d-ab17-749577159fde","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","text":"但它"},{"type":"text","marks":[{"type":"bold"}],"text":"不会"},{"type":"text","text":"过滤掉超出范围的实际数据"}]}]}]},{"type":"paragraph","attrs":{"id":"219cc8a9-0b55-46e6-b1c4-8ae97b7fb9a7","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"官方文档也明确说明了这一点:"}]},{"type":"blockquote","attrs":{"id":"235a383a-4a70-44fe-a15c-7572200efb1a","textAlign":"inherit","isHoverDragHandle":false},"content":[{"type":"paragraph","attrs":{"id":"b3c33b19-a338-434a-b80d-4c0e35f29d17","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"Note that"},{"type":"text","text":" ("},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"as the name suggests"},{"type":"text","text":") "},{"type":"text","marks":[{"type":"code"}],"text":"extended_bounds"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"is"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"bold"}],"text":"not"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"filtering buckets."},{"type":"text","text":" "},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"Meaning,"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"if the"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"code"}],"text":"extended_bounds.min"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"is higher than the values in the documents,"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"the documents will still dictate what the first bucket will be."}]}]},{"type":"heading","attrs":{"id":"3d1c47b3-348c-4e47-9e77-ed7a45e6b5aa","textAlign":"inherit","indent":0,"level":3,"isHoverDragHandle":false},"content":[{"type":"text","text":"问题的根源:offset 的影响"}]},{"type":"paragraph","attrs":{"id":"15466f3a-32a3-4a9f-9c7a-206ddd43ec0f","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"那为什么会出现越界呢?关键在于 ES 内部的"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"bold"}],"text":"offset"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"机制。让我们看看源码是怎么处理的。"}]},{"type":"paragraph","attrs":{"id":"42ea8a66-cb89-4569-806b-5878d4470d0a","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"在"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"code"}],"text":"InternalDateHistogram.java"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"中,有一个关键的方法"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"code"}],"text":"iterateEmptyBuckets"},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":":"}]},{"type":"codeBlock","attrs":{"id":"e8230de5-f69e-45a3-97eb-7c61f25b136e","language":"javascript","theme":"atom-one-dark","runtimes":0,"isHoverDragHandle":false,"key":""},"content":[{"type":"text","text":"private void iterateEmptyBuckets(List<Bucket> list, ListIterator<Bucket> iter, LongConsumer onBucket) {\n LongBounds bounds = emptyBucketInfo.bounds;\n\n // 首先添加实际数据之前的空桶(基于用户请求的 extended_bounds.min)\n if (bounds != null) {\n Bucket firstBucket = iter.hasNext() ? list.get(iter.nextIndex()) : null;\n if (firstBucket == null) {\n if (bounds.getMin() != null && bounds.getMax() != null) {\n long key = bounds.getMin() + offset; // 注意这里加了 offset\n long max = bounds.getMax() + offset;\n while (key <= max) {\n onBucket.accept(key);\n key = nextKey(key).longValue();\n }\n }\n } else {\n if (bounds.getMin() != null) {\n long key = bounds.getMin() + offset; // 这里也加了 offset\n if (key < firstBucket.key) {\n while (key < firstBucket.key) {\n onBucket.accept(key);\n key = nextKey(key).longValue();\n }\n }\n }\n }\n }\n // ... 后续代码\n}\n"}]},{"type":"paragraph","attrs":{"id":"c26fcea4-1c35-49cb-bd4a-38bcf63af249","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"看到问题了吗?在生成空桶时,ES 会将"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"code"}],"text":"extended_bounds.min"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"加上"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"code"}],"text":"offset"},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":",然后从这个位置开始生成桶。"}]},{"type":"heading","attrs":{"id":"d5ae451a-162a-4996-b51f-408f2f2a96d3","textAlign":"inherit","indent":0,"level":3,"isHoverDragHandle":false},"content":[{"type":"text","text":"offset 是什么?"}]},{"type":"paragraph","attrs":{"id":"da05e314-6e34-4852-8ab5-1814eb73ca65","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"code"}],"text":"offset"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"是用来调整时间桶对齐的参数。比如你想让每天的统计从早上 8 点开始而不是 0 点,就可以设置 offset。"}]},{"type":"paragraph","attrs":{"id":"eea28934-a64b-482a-b7b2-768b8776bce4","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"但这里有个问题:"},{"type":"text","marks":[{"type":"bold"}],"text":"即使你没有显式设置 offset,ES 内部也可能会有一个隐式的 offset 值"},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"。这个 offset 是根据时区、rounding 等因素计算出来的。"}]},{"type":"heading","attrs":{"id":"c79865ec-aa17-44a1-9a9c-205ab80ae662","textAlign":"inherit","indent":0,"level":3,"isHoverDragHandle":false},"content":[{"type":"text","text":"为什么会越界?"}]},{"type":"paragraph","attrs":{"id":"3b6b4901-d9cb-400f-bd6c-0fc460a6bee7","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"让我们用实际数字来计算一下:"}]},{"type":"orderedList","attrs":{"id":"24ba93ab-0cb0-4f56-a66e-50a8f4a354b8","start":1,"isHoverDragHandle":false},"content":[{"type":"listItem","attrs":{"id":"b71f49d4-4fb7-4b64-95fb-949d01b78999"},"content":[{"type":"paragraph","attrs":{"id":"c9efa230-c3c9-4f58-a3bd-f335263d9755","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","text":"用户设置的 "},{"type":"text","marks":[{"type":"code"}],"text":"extended_bounds.min"},{"type":"text","text":" = "},{"type":"text","marks":[{"type":"code"}],"text":"1607529600000000"}]}]},{"type":"listItem","attrs":{"id":"07824390-f642-4f2e-81ad-d3da06ab378a"},"content":[{"type":"paragraph","attrs":{"id":"a9b48958-3fd9-4036-af9a-5b64b9ed4b11","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","text":"ES 内部计算出的 "},{"type":"text","marks":[{"type":"code"}],"text":"offset"},{"type":"text","text":" = 某个值(比如可能是负数)"}]}]},{"type":"listItem","attrs":{"id":"b7405884-ca03-4206-bc0b-4c9fdb21ad2f"},"content":[{"type":"paragraph","attrs":{"id":"e559e2a7-8059-4435-b507-5189a8e5b44d","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","text":"实际生成桶的起始位置 = "},{"type":"text","marks":[{"type":"code"}],"text":"1607529600000000 + offset"}]}]},{"type":"listItem","attrs":{"id":"9f9105c0-f946-4812-91e1-02eecdd2333b"},"content":[{"type":"paragraph","attrs":{"id":"ce2e3a40-1bf2-4625-8774-5693605831c9","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","text":"如果 offset 是负数,那么起始位置就会小于用户设置的 min"}]}]}]},{"type":"paragraph","attrs":{"id":"3bfe4c7d-0c2a-4111-b60c-6a83d41b5eb4","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"这就是越界的根本原因!"}]},{"type":"paragraph","attrs":{"id":"501f3684-47ae-4aa5-8006-45d182820761","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false}},{"type":"heading","attrs":{"id":"77697e7e-9bf6-4b72-b2fa-757a5fbe2680","textAlign":"inherit","indent":0,"level":2,"isHoverDragHandle":false},"content":[{"type":"text","text":"源码深入分析"}]},{"type":"paragraph","attrs":{"id":"6c918be7-b1e6-41e7-9e35-9caecf1bf756","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"让我们画个流程图来理解整个过程:"}]},{"type":"image","attrs":{"id":"c4d6caa5-bed8-4b4d-8196-b1bd35993310","src":"https://developer.qcloudimg.com/http-save/audit-7348459/1a515a8ccdc863794ef8d372772edcb2.png","extension":"png","align":"left","alt":"","showAlt":false,"href":"","boxShadow":"","width":579,"aspectRatio":"0.618545","status":"success","showText":true,"isPercentage":false,"percentage":0,"isHoverDragHandle":false}},{"type":"heading","attrs":{"id":"816384dc-289b-4648-b64e-a5df6bd23482","textAlign":"inherit","indent":0,"level":3,"isHoverDragHandle":false},"content":[{"type":"text","text":"关键代码分析"}]},{"type":"heading","attrs":{"id":"f62f107e-dd67-437c-a1a7-35b98279f0d8","textAlign":"inherit","indent":0,"level":4,"isHoverDragHandle":false},"content":[{"type":"text","text":"1. LongBounds 的 round 方法"}]},{"type":"paragraph","attrs":{"id":"ceccf212-c47c-41ed-9d13-113b28d50bb7","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"在"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"code"}],"text":"LongBounds.java"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"中有一个重要的方法:"}]},{"type":"codeBlock","attrs":{"id":"e47da11b-520e-4da0-bf8a-60ef8f6a7263","language":"javascript","theme":"atom-one-dark","runtimes":0,"isHoverDragHandle":false,"key":""},"content":[{"type":"text","text":"LongBounds round(Rounding rounding) {\n // Extended bounds shouldn't be affected by the offset\n Rounding effectiveRounding = rounding.withoutOffset();\n return new LongBounds(\n min != null ? effectiveRounding.round(min) : null,\n max != null ? effectiveRounding.round(max) : null);\n}\n"}]},{"type":"paragraph","attrs":{"id":"9de43c5d-8d4f-4e78-bfdb-bd23c1cf0852","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"注释说得很清楚:"},{"type":"text","marks":[{"type":"bold"}],"text":"Extended bounds 不应该受 offset 影响"},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"。这个方法会移除 offset 后再进行 rounding。"}]},{"type":"heading","attrs":{"id":"0b8318dc-f8fd-49db-9aaf-22977b1eaac3","textAlign":"inherit","indent":0,"level":4,"isHoverDragHandle":false},"content":[{"type":"text","text":"2. 但是在生成空桶时"}]},{"type":"paragraph","attrs":{"id":"8fcd8e8e-fe60-4fa5-b461-6f0c99b53687","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"然而在"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"code"}],"text":"InternalDateHistogram"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"的"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"code"}],"text":"iterateEmptyBuckets"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"方法中,却又把 offset 加回来了:"}]},{"type":"codeBlock","attrs":{"id":"82aeb03d-348c-49b5-819c-723a31732504","language":"javascript","theme":"atom-one-dark","runtimes":0,"isHoverDragHandle":false,"key":""},"content":[{"type":"text","text":"long key = bounds.getMin() + offset;\n"}]},{"type":"paragraph","attrs":{"id":"77362128-b011-4b72-9ab3-5279cb5dc3de","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"这就造成了矛盾:虽然在 round 的时候移除了 offset,但在实际生成桶的时候又加回来了。"}]},{"type":"heading","attrs":{"id":"336f1be7-5630-4223-a5fb-f45fdde9d9ec","textAlign":"inherit","indent":0,"level":4,"isHoverDragHandle":false},"content":[{"type":"text","text":"3. 为什么要这样设计?"}]},{"type":"paragraph","attrs":{"id":"944f269f-8dbc-4994-bec3-e21904455660","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"这其实是为了保证桶的对齐。ES 需要确保:"}]},{"type":"bulletList","attrs":{"id":"c6973cf8-fc53-44a9-bb69-4d98a83f46fd","isHoverDragHandle":false},"content":[{"type":"listItem","attrs":{"id":"9b7f1c2e-b2ee-4580-9312-943cfce656aa"},"content":[{"type":"paragraph","attrs":{"id":"e678a4d7-29f1-4c2e-9186-6457ae0d3b2e","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","text":"所有的桶都按照相同的 offset 对齐"}]}]},{"type":"listItem","attrs":{"id":"a136f780-cf5e-4275-bc26-977706839fc3"},"content":[{"type":"paragraph","attrs":{"id":"8bbe13b1-e40a-44b3-874d-efec4c894b76","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","text":"即使是 extended_bounds 生成的空桶,也要和实际数据的桶对齐"}]}]}]},{"type":"paragraph","attrs":{"id":"8819802e-71fe-4060-b57d-9ff7c791ad9b","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"但这样做的副作用就是:"},{"type":"text","marks":[{"type":"bold"}],"text":"extended_bounds 指定的边界可能会被\"调整\""},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"。"}]},{"type":"paragraph","attrs":{"id":"1e10b094-942d-4f8b-bfe5-f21e547fe5de","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false}},{"type":"heading","attrs":{"id":"ad97ffcb-1d0c-4779-a163-ff0acf258511","textAlign":"inherit","indent":0,"level":2,"isHoverDragHandle":false},"content":[{"type":"text","text":"解决方案"}]},{"type":"paragraph","attrs":{"id":"f2065a8f-550a-4abb-95b9-92bb3ed1d69a","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"了解了问题的本质,我们就知道该怎么处理了:"}]},{"type":"heading","attrs":{"id":"7a36c779-04c0-4cd2-902d-76200d030886","textAlign":"inherit","indent":0,"level":3,"isHoverDragHandle":false},"content":[{"type":"text","text":"方案 1:使用 hard_bounds(推荐)"}]},{"type":"paragraph","attrs":{"id":"ba68444a-94a6-40e3-a620-21324f369d03","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"ES 提供了另一个参数"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"code"}],"text":"hard_bounds"},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":",它是真正的硬边界,会过滤掉超出范围的数据:"}]},{"type":"codeBlock","attrs":{"id":"5d81acd5-a81f-48cc-b862-80f93239dcaa","language":"javascript","theme":"atom-one-dark","runtimes":0,"isHoverDragHandle":false,"key":""},"content":[{"type":"text","text":"{\n \"aggs\": {\n \"time_range_aggs\": {\n \"histogram\": {\n \"field\": \"msg_time\",\n \"interval\": 86400000000,\n \"min_doc_count\": 0,\n \"hard_bounds\": {\n \"min\": 1607529600000000,\n \"max\": 1608134399000000\n }\n }\n }\n }\n}\n"}]},{"type":"paragraph","attrs":{"id":"112ae4fe-1564-475d-be53-9ba87aec20d1","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"code"}],"text":"hard_bounds"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"和"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"code"}],"text":"extended_bounds"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"的区别:"}]},{"type":"bulletList","attrs":{"id":"c053b67b-6aae-4956-b7e8-46974908e1ba","isHoverDragHandle":false},"content":[{"type":"listItem","attrs":{"id":"6becf40a-1b0d-4d2b-b3ae-aca0659a4a7e"},"content":[{"type":"paragraph","attrs":{"id":"06691acc-a35e-4386-95d6-0065f5aa5125","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"code"}],"text":"extended_bounds"},{"type":"text","text":":软边界,用于扩展结果范围,不过滤数据"}]}]},{"type":"listItem","attrs":{"id":"84225870-7d80-423b-8352-6564c1002248"},"content":[{"type":"paragraph","attrs":{"id":"c9560ff2-2f60-44bf-a078-1a2a89415a79","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"code"}],"text":"hard_bounds"},{"type":"text","text":":硬边界,会真正过滤数据,超出范围的桶不会生成"}]}]}]},{"type":"heading","attrs":{"id":"5c93c86e-dd38-4646-b7a7-0fe4d5e0d1c1","textAlign":"inherit","indent":0,"level":3,"isHoverDragHandle":false},"content":[{"type":"text","text":"方案 2:调整 extended_bounds 的值"}]},{"type":"paragraph","attrs":{"id":"21fc8b0b-5efc-4deb-8117-04045b9af9e1","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"如果你必须使用"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"code"}],"text":"extended_bounds"},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":",可以适当放宽边界值,预留一些空间给 offset:"}]},{"type":"codeBlock","attrs":{"id":"3c3c980c-c622-4ded-8613-79f9beb96653","language":"javascript","theme":"atom-one-dark","runtimes":0,"isHoverDragHandle":false,"key":""},"content":[{"type":"text","text":"{\n \"extended_bounds\": {\n \"min\": 1607529600000000 - 86400000000, // 往前多留一天\n \"max\": 1608134399000000 + 86400000000 // 往后多留一天\n }\n}\n"}]},{"type":"heading","attrs":{"id":"7ba90fd8-5e36-4062-aca0-ade3c87408ad","textAlign":"inherit","indent":0,"level":3,"isHoverDragHandle":false},"content":[{"type":"text","text":"方案 3:在应用层过滤"}]},{"type":"paragraph","attrs":{"id":"e37ef011-b75b-4be2-b77d-8030f0b5fe3e","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"最简单粗暴的方法:在拿到 ES 返回的结果后,在应用层再过滤一次:"}]},{"type":"codeBlock","attrs":{"id":"2026f7cc-040b-4959-a266-5a18377482a1","language":"javascript","theme":"atom-one-dark","runtimes":0,"isHoverDragHandle":false,"key":""},"content":[{"type":"text","text":"const filteredBuckets = response.aggregations.time_range_aggs.buckets.filter(bucket => {\n return bucket.key >= minBound && bucket.key <= maxBound;\n});\n"}]},{"type":"paragraph","attrs":{"id":"3ffb61af-2fd9-492d-a974-54110acdca83","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false}},{"type":"heading","attrs":{"id":"baf6d79c-e0e1-4b6e-ae98-3f2ff15da751","textAlign":"inherit","indent":0,"level":2,"isHoverDragHandle":false},"content":[{"type":"text","text":"总结"}]},{"type":"paragraph","attrs":{"id":"3dbc1e14-e2f8-4f9d-bdbb-2cd9109a332f","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"这个问题看似是个 bug,实际上是 ES 设计上的一个权衡:"}]},{"type":"orderedList","attrs":{"id":"9eab3076-7f46-40c1-9386-ae207caeefb7","start":1,"isHoverDragHandle":false},"content":[{"type":"listItem","attrs":{"id":"e1cf220b-d395-4278-9cdb-8b3f9fe9b892"},"content":[{"type":"paragraph","attrs":{"id":"75c888f2-55c9-4c58-a78d-2f804f9e7c1c","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"bold"}],"text":"extended_bounds 的设计初衷"},{"type":"text","text":":是为了在数据稀疏时补充空桶,而不是用来过滤数据"}]}]},{"type":"listItem","attrs":{"id":"5ee88b24-ffa2-4065-8d06-9e4017e37db1"},"content":[{"type":"paragraph","attrs":{"id":"5d2da232-9d73-4bd8-9d0e-6877d016f197","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"bold"}],"text":"offset 的必要性"},{"type":"text","text":":为了保证时间桶的正确对齐,offset 是必需的"}]}]},{"type":"listItem","attrs":{"id":"04421ae9-1680-4fd4-9aea-b909e945fe48"},"content":[{"type":"paragraph","attrs":{"id":"a9919a6d-1a74-465f-9922-f106de25b3aa","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"bold"}],"text":"两者的冲突"},{"type":"text","text":":当 extended_bounds 遇到 offset 时,就会出现\"越界\"现象"}]}]}]},{"type":"paragraph","attrs":{"id":"888b882b-09c7-42e4-bb31-9b2f29ca4ce8","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"理解了这个原理,我们就知道:"}]},{"type":"bulletList","attrs":{"id":"c91bd9b6-970d-49d4-8123-0373fdf906ba","isHoverDragHandle":false},"content":[{"type":"listItem","attrs":{"id":"99c06e0e-ec17-4a9f-b9a2-6e363b921006"},"content":[{"type":"paragraph","attrs":{"id":"d2ff3f71-3614-4491-b2d7-afdfd60c5490","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","text":"如果需要严格的边界控制,使用 "},{"type":"text","marks":[{"type":"code"}],"text":"hard_bounds"}]}]},{"type":"listItem","attrs":{"id":"2ccaaf93-8f4a-4931-8f36-87bc10ed7a22"},"content":[{"type":"paragraph","attrs":{"id":"d5883479-8e65-4637-9ce8-0bce61d834c7","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","text":"如果只是想补充空桶,使用 "},{"type":"text","marks":[{"type":"code"}],"text":"extended_bounds"},{"type":"text","text":" 并接受可能的\"越界\""}]}]},{"type":"listItem","attrs":{"id":"ee3c4063-d54d-49d9-920a-85ac63dfcc31"},"content":[{"type":"paragraph","attrs":{"id":"23693c2d-cd60-47fb-95e0-748d918cd5e7","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","text":"或者在应用层做二次过滤"}]}]}]},{"type":"paragraph","attrs":{"id":"6547801e-b7fd-4dd7-8275-300ff052d7be","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"最后提醒一句:"},{"type":"text","marks":[{"type":"bold"}],"text":"阅读官方文档很重要"},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":",很多"},{"type":"text","text":"\""},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"问题"},{"type":"text","text":"\""},{"type":"text","marks":[{"type":"textStyle","attrs":{"color":"","background":""}}],"text":"其实在文档里都有说明,只是我们容易忽略那些细节。"}]},{"type":"paragraph","attrs":{"id":"7e8eedde-9c69-4077-bc2c-3ce36ed6c623","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false}},{"type":"heading","attrs":{"id":"c37d7d0a-dbc8-46c6-b7e9-c1f5dadbe9ee","textAlign":"inherit","indent":0,"level":2,"isHoverDragHandle":false},"content":[{"type":"text","text":"参考资料"}]},{"type":"bulletList","attrs":{"id":"ef3bf8c0-474c-4278-a3e0-325d01571cc0","isHoverDragHandle":false},"content":[{"type":"listItem","attrs":{"id":"2b8c66ec-748e-4dae-be0f-ec2c3445fcc7"},"content":[{"type":"paragraph","attrs":{"id":"123f18ef-6ef4-464e-b383-5f83ef75e487","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"link","attrs":{"href":"https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-histogram-aggregation.html","target":"_blank","rel":"noopener noreferrer nofollow","class":null}}],"text":"Elasticsearch Histogram Aggregation 官方文档"}]}]},{"type":"listItem","attrs":{"id":"6d31d3d3-80a1-4e00-9caf-5d9abfdefc26"},"content":[{"type":"paragraph","attrs":{"id":"fa73e392-00d4-4c20-9b06-4fc2dc57b5b3","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","text":"Elasticsearch 源码:"}]},{"type":"bulletList","attrs":{"id":"9fe09e9c-d6f3-4615-a2b0-853b243f2a9e","isHoverDragHandle":false},"content":[{"type":"listItem","attrs":{"id":"5418bcbe-314c-450e-802b-fef4229a629f"},"content":[{"type":"paragraph","attrs":{"id":"7103f516-dc14-43bb-ac7f-aba6efed3c98","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"code"}],"text":"InternalDateHistogram.java"}]}]},{"type":"listItem","attrs":{"id":"bb1c4fb4-48cc-484b-b9ef-5bbcf184653b"},"content":[{"type":"paragraph","attrs":{"id":"edb21135-e4af-419e-b7f3-9379b92186a1","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"code"}],"text":"InternalHistogram.java"}]}]},{"type":"listItem","attrs":{"id":"2865c23e-1625-4109-b72a-ba07297247a3"},"content":[{"type":"paragraph","attrs":{"id":"069dabc2-74b5-4b9d-a1fa-d9d91a62b5b0","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"code"}],"text":"LongBounds.java"}]}]},{"type":"listItem","attrs":{"id":"3177eccd-a58c-4b75-981d-64b40b1d0476"},"content":[{"type":"paragraph","attrs":{"id":"0a59dee1-7d23-43b1-ba29-79794a73adf3","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false},"content":[{"type":"text","marks":[{"type":"code"}],"text":"DoubleBounds.java"}]}]}]}]}]},{"type":"paragraph","attrs":{"id":"c940e0b2-7d3e-459b-8470-72d2c76d9f8e","textAlign":"inherit","indent":0,"color":null,"background":null,"isHoverDragHandle":false}}]}