【翻译】Flink Table Api & SQL — 性能调优 — 流式聚合
本文翻译自官网:Streaming Aggregation https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/table/tuning/streaming_aggregation_optimization.html
SQL是用于数据分析的最广泛使用的语言。Flink的Table API和SQL使用户能够以更少的时间和精力定义高效的流分析应用程序。而且,Flink Table API和SQL得到了有效的优化,它集成了许多查询优化和优化的运算符实现。但是并非默认情况下会启用所有优化,因此对于某些工作负载,可以通过打开某些选项来提高性能。
在此页面中,我们将介绍一些有用的优化选项以及流聚合的内部原理,这将在某些情况下带来很大的改进。
注意:当前,仅Blink计划程序支持此页面中提到的优化选项。
注意:当前,仅对无边界聚合支持流聚合优化。将来将支持窗口聚合的优化。
默认情况下,无界聚合运算符一个一个地处理输入记录,即(1)从状态读取累加器,(2)将记录累加/缩回到累加器,(3)将累加器写回到状态,(4)下一条记录将从(1)重新进行处理。此处理模式可能会增加StateBackend的开销(尤其是对于RocksDB StateBackend)。此外,生产中非常常见的数据偏斜会使问题恶化,并使工作容易承受背压情况。
小批量聚合
小型批处理聚合的核心思想是将一组输入缓存在聚合运算符内部的缓冲区中。当触发输入以进行处理时,每个键只需一个操作即可访问状态。这样可以大大减少状态开销并获得更好的吞吐量。但是,这可能会增加一些延迟,因为它会缓冲一些记录而不是立即处理它们。这是吞吐量和延迟之间的权衡。
下图说明了小批量聚合如何减少状态操作。

MiniBatch 优化默认情况下处于禁用状态。为了使这种优化,您应该设置 table.exec.mini-batch.enabled,table.exec.mini-batch.allow-latency和table.exec.mini-batch.size。请参阅配置页面以获取更多详细信息。
以下示例显示如何启用这些选项。
// instantiate table environment
val tEnv: TableEnvironment = ... // access flink configuration
val configuration = tEnv.getConfig().getConfiguration()
// set low-level key-value options
configuration.setString("table.exec.mini-batch.enabled", "true") // enable mini-batch optimization
configuration.setString("table.exec.mini-batch.allow-latency", "5 s") // use 5 seconds to buffer input records
configuration.setString("table.exec.mini-batch.size", "5000") // the maximum number of records can be buffered by each aggregate operator task
局部全局聚合
提出将局部聚合分为两个阶段来解决数据倾斜问题,即先在上游进行局部聚合,然后在下游进行全局聚合,这类似于MapReduce中的Combine + Reduce模式。例如,考虑以下SQL:
SELECT color, sum(id)
FROM T
GROUP BY color
数据流中的记录可能会倾斜,因此聚合运算符的某些实例会比其他实例处理更多的记录,这会导致热点。本地聚合可以帮助将具有相同密钥的一定数量的输入累加到单个累加器中。全局汇总将仅接收减少的累加器,而不是大量的原始输入。这可以大大减少网络改组和状态访问的成本。每次本地聚合累积的输入数量基于最小批处理间隔。这意味着本地-全局聚合取决于启用了小批量优化。
下图显示了本地全局聚合如何提高性能。

以下示例显示了如何启用本地全局聚合。
// instantiate table environment
val tEnv: TableEnvironment = ... // access flink configuration
val configuration = tEnv.getConfig().getConfiguration()
// set low-level key-value options
configuration.setString("table.exec.mini-batch.enabled", "true") // local-global aggregation depends on mini-batch is enabled
configuration.setString("table.exec.mini-batch.allow-latency", "5 s")
configuration.setString("table.exec.mini-batch.size", "5000")
configuration.setString("table.optimizer.agg-phase-strategy", "TWO_PHASE") // enable two-phase, i.e. local-global aggregation
分割不同的聚合
局部全局优化可有效消除常规聚合的数据偏斜,例如SUM,COUNT,MAX,MIN,AVG。但是,在处理不同的聚合时,其性能并不令人满意。
例如,如果我们要分析今天有多少唯一用户登录。我们可能有以下查询:
SELECT day, COUNT(DISTINCT user_id)
FROM T
GROUP BY day
如果distinct key(即user_id)的值稀疏,则COUNT DISTINCT不能减少记录。即使启用了局部全局优化,它也无济于事。因为累加器仍包含几乎所有原始记录,并且全局聚合将成为瓶颈(大多数繁重的累加器由一项任务处理,即在同一天)。
此优化的想法是将不同的聚合(例如COUNT(DISTINCT col))分为两个级别。第一次聚合由组密钥和其他存储桶密钥混洗。使用来计算存储桶密钥HASH_CODE(distinct_key) % BUCKET_NUM。BUCKET_NUM默认为1024,可以通过table.optimizer.distinct-agg.split.bucket-num选项配置。第二次聚合由原始组密钥改组,并用于SUM聚合来自不同存储桶的COUNT DISTINCT值。由于相同的唯一键将仅在同一存储桶中计算,因此转换是等效的。存储桶密钥充当附加组密钥的角色,以分担组密钥中的热点负担。存储桶关键字使工作具有可伸缩性,以解决不同聚合中的数据偏斜/热点。
拆分非重复聚合后,上述查询将自动重写为以下查询:
SELECT day, SUM(cnt)
FROM (
SELECT day, COUNT(DISTINCT user_id) as cnt
FROM T
GROUP BY day, MOD(HASH_CODE(user_id), 1024)
)
GROUP BY day
下图显示了拆分的非重复聚合如何提高性能(假设颜色代表天,字母代表user_id)。

注意:上面是最简单的示例,可以从此优化中受益。除此之外,Flink 支持分裂更复杂的聚集查询,例如,一个以上的具有不同的不同密钥(例如不同的集合COUNT(DISTINCT a), SUM(DISTINCT b)),与其他非重复的聚合工作(例如SUM,MAX,MIN,COUNT)。
注意:但是,当前,拆分优化不支持包含用户定义的AggregateFunction的聚合。
以下示例显示如何启用拆分非重复聚合优化。
// instantiate table environment
val tEnv: TableEnvironment = ... tEnv.getConfig // access high-level configuration
.getConfiguration // set low-level key-value options
.setString("table.optimizer.distinct-agg.split.enabled", "true") // enable distinct agg split
在不同的聚合上使用FILTER修饰符
在某些情况下,用户可能需要从不同维度计算UV(唯一访客)的数量,例如Android的UV,iPhone的UV,Web的UV和总UV。许多用户将选择CASE WHEN支持此功能,例如:
SELECT
day,
COUNT(DISTINCT user_id) AS total_uv,
COUNT(DISTINCT CASE WHEN flag IN ('android', 'iphone') THEN user_id ELSE NULL END) AS app_uv,
COUNT(DISTINCT CASE WHEN flag IN ('wap', 'other') THEN user_id ELSE NULL END) AS web_uv
FROM T
GROUP BY day
但是,在这种情况下,建议使用 FILTER 语法而不是CASE WHEN。因为FILTER它更符合SQL标准,并且将获得更多的性能改进。 FILTER是用于聚合函数的修饰符,用于限制聚合中使用的值。将上面的示例替换为FILTER修饰符,如下所示:
SELECT
day,
COUNT(DISTINCT user_id) AS total_uv,
COUNT(DISTINCT user_id) FILTER (WHERE flag IN ('android', 'iphone')) AS app_uv,
COUNT(DISTINCT user_id) FILTER (WHERE flag IN ('wap', 'other')) AS web_uv
FROM T
GROUP BY day
Flink SQL优化器可以识别同一唯一键上的不同过滤器参数。例如,在上面的示例中,所有三个COUNT DISTINCT都在user_id列上。然后Flink可以只使用一个共享状态实例,而不是三个状态实例,以减少状态访问和状态大小。在某些工作负载中,这可以显着提高性能。
欢迎关注Flink菜鸟公众号,会不定期更新Flink(开发技术)相关的推文

【翻译】Flink Table Api & SQL — 性能调优 — 流式聚合的更多相关文章
- Flink Table Api & SQL 翻译目录
Flink 官网 Table Api & SQL 相关文档的翻译终于完成,这里整理一个安装官网目录顺序一样的目录 [翻译]Flink Table Api & SQL —— Overv ...
- 【翻译】Flink Table Api & SQL — 配置
本文翻译自官网:Configuration https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/table/config.h ...
- 【翻译】Flink Table Api & SQL — 用户定义函数
本文翻译自官网:User-defined Functions https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/tabl ...
- 【翻译】Flink Table Api & SQL —— 连接到外部系统
本文翻译自官网:Connect to External Systems https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev ...
- 【翻译】Flink Table Api & SQL — 内置函数
本文翻译自官网:Built-In Functions https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/table/fu ...
- 【翻译】Flink Table Api & SQL — 流概念
本文翻译自官网:Streaming Concepts https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/table/st ...
- 【翻译】Flink Table Api & SQL — Hive —— 在 scala shell 中使用 Hive 连接器
本文翻译自官网:Use Hive connector in scala shell https://ci.apache.org/projects/flink/flink-docs-release-1 ...
- 【翻译】Flink Table Api & SQL — Hive —— Hive 函数
本文翻译自官网:Hive Functions https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/table/hive/h ...
- 【翻译】Flink Table Api & SQL — SQL客户端Beta 版
本文翻译自官网:SQL Client Beta https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/table/sqlCl ...
随机推荐
- Show which git tag you are on?
git查看当前代码是在那个tag? reference: https://stackoverflow.com/questions/3404936/show-which-git-tag-you-are- ...
- GitHub使用方法与三步教程
git下载地址:https://git-scm.com/downloads 下一步下一步就好了 在cmd输入 git --version 在桌面空白右键 Git Bash Here命令行 输入 git ...
- django小知识(2)
昨日内容回顾 1.choices参数 class Userinfo(models.Model): username = ... age = ... gender_choice = ( (1,'male ...
- day007-python函数之课后作业讲解
作业一: 需求:写函数,计算传入字符串中的[数字].[字母].[空格]以及[其他]的个数 #!/usr/bin/env python # -*- coding:utf-8 -*- #先定义一个函数 d ...
- python函数 | 生成器
生成器本质上是迭代器,包含__iter__和__next__功能 生成器的产生方式: 1,生成器函数构造. 2,生成器推导式构造. 3,数据类型的转化. 通过构造生成器函数,就是将函数中的return ...
- node.js封装数据库增删改查
数据库增删改查的封装 小编不容易 const sql = { insert: function (Collection, insertData) { return new Promise((resol ...
- haproxy 2.0 dataplaneapi rest api 几个方便的问题排查接口
在使用haproxy 2.0 dataplaneapi的时候,刚开始的时候我们可能需要进行调试,保证我们的配置在我们的系统环境中 是可以使用的,以下是自己在当前学习中为了排查问题会使用的几个api 创 ...
- vmvare ESXi使用
新建主机,选择系统,自定义配置,选择ios镜像,完成,打开电源,开启配置
- 8-ESP8266 SDK开发基础入门篇--编写串口上位机软件
https://www.cnblogs.com/yangfengwu/p/11087558.html 咱用这个编写 ,版本都无所谓哈,只要自己有就可以,不同版本怎么打开 https://www.cnb ...
- 少女NULL中
NULL inline void read (int &now) { register ; ; !isdigit (word); word = getchar ()) ; + word - ' ...