ElasticSearch - 深入解析 Elasticsearch Composite Aggregation 的分页与去重机制

文章目录

  • Pre
  • 概述
  • 什么是 `composite aggregation`?
  • 基本结构
  • `after` 参数的作用
    • 问题背景:传统分页的重复问题
    • `after` 的设计理念
    • 响应示例
  • `after` 如何确保数据不重复
    • 核心机制
    • Example
      • 步骤 1: 创建测试数据
        • 创建索引
        • 插入测试数据
      • 步骤 2: 查询第一页结果
        • 查询第一页
        • 返回结果
      • 步骤 3: 查询第二页结果
        • 查询第二页
        • 返回结果
      • 步骤 4: 查询第三页结果
        • 查询第三页
        • 返回结果
      • 步骤 5: 查询第四页结果
        • 查询第四页
        • 返回结果
      • 验证
      • 小结
  • 总结

在这里插入图片描述


Pre

ElasticSearch - 使用 Composite Aggregation 实现桶的分页查询

概述

在 Elasticsearch 中,composite aggregation 提供了一种高效的分页聚合方式,尤其适用于数据量较大的场景。为了避免传统分页中常见的重复数据问题,composite aggregation 引入了 after 参数。本文将详细探讨 after 参数的机制,以及它如何确保数据不重复。


什么是 composite aggregation

composite aggregation 是一种支持多字段分组的聚合类型,其独特之处在于可以实现分页功能。这种分页能力通过 size 参数控制每次返回的桶数量,并通过 after 参数获取下一页的结果。

基本结构

一个典型的 composite aggregation 查询:

GET /your_index_name/_search
{"size": 0,"aggs": {"my_composite_agg": {"composite": {"size": 10,"sources": [{"field1": {"terms": {"field": "your_field_name1"}}},{"field2": {"terms": {"field": "your_field_name2"}}}]}}}
}

在以上查询中:

  • sources 定义了按哪些字段分组,字段顺序决定了分组键(bucket key)的生成顺序。
  • size 定义每页的桶数量。
  • 响应结果中的 after_key 用于获取下一页数据。

after 参数的作用

问题背景:传统分页的重复问题

在使用基于偏移量的分页(如 fromsize 参数)时,数据更新可能导致页码错乱或重复。例如:

  • 如果在分页过程中有新文档插入或更新,数据偏移可能导致某些文档重复出现在多页结果中。

after 的设计理念

after 参数是 composite aggregation 特有的,它记录了上一页最后一个桶的键值(after_key),并以此为起点获取下一页数据。这种方式基于排序键,确保分页过程始终连续、无重复。

响应示例

以下是一个分页查询的响应:

{"aggregations": {"my_composite_agg": {"buckets": [{ "key": { "field1": "value1", "field2": "value2" }, "doc_count": 10 },{ "key": { "field1": "value3", "field2": "value4" }, "doc_count": 8 }],"after_key": { "field1": "value3", "field2": "value4" }}}
}

在下一页查询中,可以使用 after_key 作为起点:

GET /your_index_name/_search
{"size": 0,"aggs": {"my_composite_agg": {"composite": {"size": 10,"after": { "field1": "value3", "field2": "value4" },"sources": [{"field1": {"terms": {"field": "your_field_name1"}}},{"field2": {"terms": {"field": "your_field_name2"}}}]}}}
}

after 如何确保数据不重复

核心机制

  1. 排序保证一致性

    • composite aggregation 内部按照 sources 中定义的字段顺序生成桶键,并进行字典序排序。
    • 每次查询的结果顺序是固定的,即使数据发生变动,也不会影响之前已返回的桶键。
  2. 分页起点记录

    • 每次查询都会返回 after_key,表示当前页最后一个桶的键值。
    • 在下一页查询中,Elasticsearch 从该键值开始,获取后续的桶。
  3. 跳过已处理的桶

    • Elasticsearch 在执行查询时,会严格按照 after_key 跳过已处理的桶,确保每个桶仅返回一次。
  4. 游标精准定位

    • after_key 明确表示从上次分页结果的最后一个桶之后开始读取,而不会重新读取已经返回的桶。
    • 查询总是基于 key 的排序位置,按顺序依次获取后续的桶。
  5. 无偏移计算

    • 不使用 fromsize 等偏移量参数,避免了由于数据插入或删除导致的分页偏移问题。
  6. 全局一致性排序

    • 所有桶的排序是全局确定的,即使数据分布在多个分片中,也能按照统一的顺序返回。
    • Elasticsearch 会在多个分片中进行合并排序,从而确保每次分页的桶是唯一且无重复的。

Example

步骤 1: 创建测试数据

我们创建一个名为 test_index 的索引,并插入一些测试数据。数据包含一个字段 category,我们将根据这个字段进行聚合分页。

创建索引
PUT /test_index
{"mappings": {"properties": {"category": {"type": "keyword"},"value": {"type": "integer"}}}
}
插入测试数据
POST /test_index/_bulk
{ "index": {} }
{ "category": "A", "value": 10 }
{ "index": {} }
{ "category": "A", "value": 20 }
{ "index": {} }
{ "category": "A", "value": 30 }
{ "index": {} }
{ "category": "B", "value": 40 }
{ "index": {} }
{ "category": "B", "value": 50 }
{ "index": {} }
{ "category": "B", "value": 60 }
{ "index": {} }
{ "category": "C", "value": 70 }
{ "index": {} }
{ "category": "C", "value": 80 }
{ "index": {} }
{ "category": "C", "value": 90 }
{ "index": {} }
{ "category": "D", "value": 100 }
{ "index": {} }
{ "category": "D", "value": 110 }
{ "index": {} }
{ "category": "D", "value": 120 }
{ "index": {} }
{ "category": "E", "value": 130 }
{ "index": {} }
{ "category": "E", "value": 140 }
{ "index": {} }
{ "category": "E", "value": 150 }
{ "index": {} }
{ "category": "F", "value": 160 }
{ "index": {} }
{ "category": "F", "value": 170 }
{ "index": {} }
{ "category": "F", "value": 180 }
{ "index": {} }
{ "category": "G", "value": 190 }
{ "index": {} }
{ "category": "G", "value": 200 }
{ "index": {} }
{ "category": "G", "value": 210 }
{ "index": {} }
{ "category": "H", "value": 220 }
{ "index": {} }
{ "category": "H", "value": 230 }
{ "index": {} }
{ "category": "H", "value": 240 }
{ "index": {} }
{ "category": "I", "value": 250 }
{ "index": {} }
{ "category": "I", "value": 260 }
{ "index": {} }
{ "category": "I", "value": 270 }
{ "index": {} }
{ "category": "J", "value": 280 }
{ "index": {} }
{ "category": "J", "value": 290 }
{ "index": {} }
{ "category": "J", "value": 300 }
{ "index": {} }
{ "category": "K", "value": 310 }
{ "index": {} }
{ "category": "K", "value": 320 }
{ "index": {} }
{ "category": "K", "value": 330 }
{ "index": {} }
{ "category": "L", "value": 340 }
{ "index": {} }
{ "category": "L", "value": 350 }
{ "index": {} }
{ "category": "L", "value": 360 }
{ "index": {} }
{ "category": "M", "value": 370 }
{ "index": {} }
{ "category": "M", "value": 380 }
{ "index": {} }
{ "category": "M", "value": 390 }
{ "index": {} }
{ "category": "N", "value": 400 }
{ "index": {} }
{ "category": "N", "value": 410 }
{ "index": {} }
{ "category": "N", "value": 420 }
{ "index": {} }
{ "category": "O", "value": 430 }
{ "index": {} }
{ "category": "O", "value": 440 }
{ "index": {} }
{ "category": "O", "value": 450 }
{ "index": {} }
{ "category": "P", "value": 460 }
{ "index": {} }
{ "category": "P", "value": 470 }
{ "index": {} }
{ "category": "P", "value": 480 }
{ "index": {} }
{ "category": "Q", "value": 490 }
{ "index": {} }
{ "category": "Q", "value": 500 }
{ "index": {} }
{ "category": "Q", "value": 510 }
{ "index": {} }
{ "category": "R", "value": 520 }
{ "index": {} }
{ "category": "R", "value": 530 }
{ "index": {} }
{ "category": "R", "value": 540 }
{ "index": {} }
{ "category": "S", "value": 550 }
{ "index": {} }
{ "category": "S", "value": 560 }
{ "index": {} }
{ "category": "S", "value": 570 }
{ "index": {} }
{ "category": "T", "value": 580 }
{ "index": {} }
{ "category": "T", "value": 590 }
{ "index": {} }
{ "category": "T", "value": 600 }
{ "index": {} }
{ "category": "U", "value": 610 }
{ "index": {} }
{ "category": "U", "value": 620 }
{ "index": {} }
{ "category": "U", "value": 630 }
{ "index": {} }
{ "category": "V", "value": 640 }
{ "index": {} }
{ "category": "V", "value": 650 }
{ "index": {} }
{ "category": "V", "value": 660 }
{ "index": {} }
{ "category": "W", "value": 670 }
{ "index": {} }
{ "category": "W", "value": 680 }
{ "index": {} }
{ "category": "W", "value": 690 }
{ "index": {} }
{ "category": "X", "value": 700 }
{ "index": {} }
{ "category": "X", "value": 710 }
{ "index": {} }
{ "category": "X", "value": 720 }
{ "index": {} }
{ "category": "Y", "value": 730 }
{ "index": {} }
{ "category": "Y", "value": 740 }
{ "index": {} }
{ "category": "Y", "value": 750 }
{ "index": {} }
{ "category": "Z", "value": 760 }
{ "index": {} }
{ "category": "Z", "value": 770 }
{ "index": {} }
{ "category": "Z", "value": 780 }

步骤 2: 查询第一页结果

我们使用 composite aggregation 查询第一页,设置每页返回 3 个桶。

查询第一页
GET /test_index/_search
{"size": 0,"aggs": {"composite_agg": {"composite": {"size": 10,"sources": [{ "category": { "terms": { "field": "category" } } }]}}}
}
返回结果
 {"took" : 11,"timed_out" : false,"_shards" : {"total" : 1,"successful" : 1,"skipped" : 0,"failed" : 0},"hits" : {"total" : {"value" : 78,"relation" : "eq"},"max_score" : null,"hits" : [ ]},"aggregations" : {"composite_agg" : {"after_key" : {"category" : "J"},"buckets" : [{"key" : {"category" : "A"},"doc_count" : 3},{"key" : {"category" : "B"},"doc_count" : 3},{"key" : {"category" : "C"},"doc_count" : 3},{"key" : {"category" : "D"},"doc_count" : 3},{"key" : {"category" : "E"},"doc_count" : 3},{"key" : {"category" : "F"},"doc_count" : 3},{"key" : {"category" : "G"},"doc_count" : 3},{"key" : {"category" : "H"},"doc_count" : 3},{"key" : {"category" : "I"},"doc_count" : 3},{"key" : {"category" : "J"},"doc_count" : 3}]}}
}

步骤 3: 查询第二页结果

我们使用第一页返回的 after_key{ "category": "J" } 来查询第二页。

查询第二页
GET /test_index/_search
{"size": 0,"aggs": {"composite_agg": {"composite": {"size": 10,"after": { "category": "J" },"sources": [{ "category": { "terms": { "field": "category" } } }]}}}
}
返回结果

在这里插入图片描述


步骤 4: 查询第三页结果

使用第二页返回的 after_key{ "category": "T" } 查询第三页。

查询第三页
GET /test_index/_search
{"size": 0,"aggs": {"composite_agg": {"composite": {"size": 10,"after": { "category": "T" },"sources": [{ "category": { "terms": { "field": "category" } } }]}}}
}
返回结果

在这里插入图片描述


步骤 5: 查询第四页结果

使用第三页返回的 after_key{ "category": "Z" } 查询第三页。

查询第四页
GET /test_index/_search
{"size": 0,"aggs": {"composite_agg": {"composite": {"size": 10,"after": { "category": "Z" },"sources": [{ "category": { "terms": { "field": "category" } } }]}}}
}
返回结果

在这里插入图片描述


验证

通过四次分页查询,我们验证以下几点:

  1. 结果无重复

    • 每页的结果是唯一的,没有重复桶。例如:
      • 第 1 页返回桶:A, B, CJ
      • 第 2 页返回桶:K, L, MT
      • 第 3 页返回桶:U, VZ
      • 第 4 页返回桶:已到最后
  2. 顺序一致

    • 所有结果按照 category 字段值排序,顺序为 A, B, C, …, Z
  3. after_key 确保正确游标定位

    • 使用 after_key 明确标识分页起点,每次从上页的最后一个桶的 key 开始查询,没有遗漏或重复。

小结

  • composite aggregation 使用基于 after_key 的游标机制,可以确保分页查询中数据无重复、无遗漏。
  • composite aggregation 的设计特别适合大规模数据的聚合和分页,是传统 from + size 分页方法的高效替代方案。

通过 after_key 的分页,可以看到每页数据互不重叠,且严格按照 category 字段排序。


总结

传统分页 (from + size)Composite Aggregation (游标)
基于偏移计算,容易因数据变动重复基于游标,桶的顺序和定位稳定无重复
数据量大时性能下降明显高效处理大数据,避免偏移的性能开销
不支持跨分片排序跨分片排序一致性,返回结果无重复或遗漏
  • composite aggregation 使用基于 after_key 的游标机制,可以确保分页查询中数据无重复、无遗漏。
  • composite aggregation 的设计特别适合大规模数据的聚合和分页,是传统 from + size 分页方法的高效替代方案。

composite aggregation 的设计通过排序和 after_key 机制,确保分页查询的每页数据互不重复,且顺序一致。这种特性使其在大数据量的分页聚合中表现出色。如果应用场景需要可靠的分页聚合,可以尝试 composite aggregation

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/65283.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

两分钟掌握 TDengine 全部写入方式

1. 背景 TDengine 写入过程会涉及很多概念,这些概念目前你是不是还一团乱,参数绑定写入、无模式写入、websocket 写入、RESTFUL 写入 、各种连接器写入等等一堆的写入,都是做什么的,不明白,这里花两分钟时间给你彻底整…

快速理解24种设计模式

简单工厂模式 建立产品接口类,规定好要实现方法。 建立工厂类,根据传入的参数,实例化所需的类,实例化的类必须实现指定的产品类接口 创建型 单例模式Singleton 保证一个类只有一个实例,并提供一个访问他它的全局…

数据可视化echarts学习笔记

目录,介绍 知识储备 一端操作,多端联动的效果(开启了多个网页,操作一端,多个网页的效果会跟着改变) cmd命令控制面板返回上一级或上上级 在当前目录打开文件: cd 文件名 在Windows命令提示符&am…

OpenCV相机标定与3D重建(30)过滤二值图像中的小斑点函数filterSpeckles()的使用

操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在视差图中过滤掉小的噪声斑点(speckles)。 cv::filterSpeckles 是 OpenCV 库中的一个函数,用于过滤图像或视…

C语言期末复习笔记(中)

目录 五、选择控制结构 1.算法中的概念及描述方法 2.关系运算符和逻辑表达式 3.条件运算符和条件表达式 4.两种多分支if 5.switch语句 6.逻辑运算符和逻辑表达式 六、循环控制结构 1.控制循环的方式 2.控制非法输入 3.选择三种循环的一般原则 4.猜数游戏 5.嵌套循环…

利用Gurobi追溯模型不可行原因的四种方案及详细案例

文章目录 1. 引言2. 追溯不可行集的四种方法2.1 通过约束增减进行判断2.2 通过computeIIS函数获得冲突集2.3 利用 feasRelaxS() 或 feasRelax() 函数辅助排查2.4 利用 IIS Force 属性1. 引言 模型不可行是一个让工程师头疼的问题,对于复杂模型而言,导致模型不可行的原因可能…

MySQL和HBase的对比

Mysql :关系型数据库,主要面向 OLTP ,支持事务,支持二级索引,支持 sql ,支持主从、 Group Replication 架构模型(此处以 Innodb 为例,不涉及别的存储引擎)。 HBase &am…

mybatis-plus自动填充时间的配置类实现

mybatis-plus自动填充时间的配置类实现 在实际操作过程中,我们并不希望创建时间、修改时间这些来手动进行,而是希望通过自动化来完成,而mybatis-plus则也提供了自动填充功能来实现这一操作,接下来,就来了解一下mybatis…

【软件工程】十万字知识点梳理 | 期末复习专用

原创文章,禁止转载。 文章目录 图CRC卡片用例图类图状态图活动图泳道图软件质量因素自顶向下集成自底向上集成人员与工作量之间的关系时序图关键路径软件结构基本路径测试判定表数据流图(DFD)体系结构设计问题数据字典挣值分析等价划分程序流程图PAD | N-S燃尽图甘特图对象模…

STM32完全学习——FLASH上FATFS文件管理系统

一、需要移植的接口 我们通过看官网的手册,可以看到我们只要完成下面函数的实现,就可以完成移植。我们这里只移植前5个函数,获取时间的函数我们不在这里移植。 二、移植接口函数 DSTATUS disk_status (BYTE pdrv /* Physical drive nmuber…

Redis - Token JWT 概念解析及双token实现分布式session存储实战

Token 定义:令牌,访问资源接口(API)时所需要的资源凭证 一、Access Token 定义:访问资源接口(API)时所需要的资源凭证,存储在客户端 组成 组成部分说明uid用户唯一的身份标识time…

软体机器人研究报告:设计方法、材料与驱动、感知与控制

软体机器人因其出色的可变形性和高适应性受到了广泛关注,这些特性使其在医疗、救援、探测等复杂场景中展现出独特的优势和巨大的应用潜力。研究人员对软体机器人的设计方法、材料与驱动技术、感知与控制策略等方面进行深入研究,取得了一系列成果。 本文汇…

imgproxy图像处理的高效与安全

摘要 imgproxy作为一个高效且安全的独立服务器,为图像处理提供了全新的解决方案。它不仅简化了图像调整和转换的过程,还极大地提升了处理速度,确保了整个流程的安全性。通过集成imgproxy,用户可以轻松优化网页上的图像,提高加载速度,改善用户体验。本文将深入探讨imgpro…

要查询 `user` 表中 `we_chat_subscribe` 和 `we_chat_union_id` 列不为空的用户数量

文章目录 1、we_chat_subscribe2、we_chat_union_id 1、we_chat_subscribe 要查询 user 表中 we_chat_subscribe 列不为空的用户数量,你可以使用以下 SQL 查询语句: SELECT COUNT(*) FROM user WHERE we_chat_subscribe IS NOT NULL;解释: …

RTMW:实时多人2D和3D 全人体姿态估计

单位:上海AI实验室 代码:mmpose/tree/main/projects/rtmpose 系列文章目录 RTMO: 面向高性能单阶段的实时多人姿态估计 目录 系列文章目录摘要一、背景二、相关工作2.1 自上而下的方法。2.2 坐标分类。2.3 3D Pose 3 实验方法3.1.1 任务限制3.1.3训练技…

香橙派5Plus启动报错bug: spinlock bad magic on cpu#6, systemd-udevd/443

一、问题 如图: 接上调试串口,每次启动都会报错。不过使用过程中没有发现有什么影响。 百度查阅,有一位博主提到,但是没有细说解决方案: spinlock变量没有初始化_spinlock bad magic on-CSDN博客https://blog.csdn.n…

FPGA自学之路:到底有多崎岖?

FPGA,即现场可编程门阵列,被誉为硬件世界的“瑞士军刀”,其灵活性和可编程性让无数开发者为之倾倒。但谈及FPGA的学习难度,不少人望而却步。那么,FPGA自学之路到底有多崎岖呢? 几座大山那么高?…

【KLEE】源码阅读笔记----KLEE执行流程

本文架构 1. 动机2.KLEE简介3.KLEE的代码工程结构4. 从KLEE主函数入手main函数step1: 初始化step2:加载.bc文件进行符号执行 读取测试用例输出日志信息 1. 动机 最近准备对KLEE进行修改使其符合我的需要,因此免不了需要对源码进行修改。读懂源码是对在其…

CS 144 check7: putting it all together

Exercises 经验:两边的TCP连接建立得尽快,如果服务器端启动了,客户端没有紧接着启动就连不上。 这是什么神奇的bug呢? 和我之前给域控刷SOC的版本一样。如果域控启动了,在我本地的电脑没有马上和域控的SOC通上信&…

Suno Api V4模型无水印开发「综合实战开发自己的音乐网站」 —— 「Suno Api系列」第14篇

历史文章 Suno AI API接入 - 将AI音乐接入到自己的产品中,支持120并发任务 Suno Api V4模型无水印开发「灵感模式」 —— 「Suno Api系列」第1篇 Suno Api V4模型无水印开发「自定义模式」 —— 「Suno Api系列」第2篇 Suno Api V4模型无水印开发「AI生成歌词」…