逆向思维,去重Cube计算优化新技巧

news2024/11/16 0:19:57

场景描述

在做数据汇总计算和统计分析时,最头疼的就是去重类指标计算(比如用户数、商家数等),尤其还要带多种维度的下钻分析,由于其不可累加的特性,几乎每换一种统计维度组合,都得重新计算。数据量小时可以暴力的用明细数据直接即时统计,但当数据量大时就不得不考虑提前进行计算了。
典型场景,省、市、区等维度下的支付宝日支付用户数(其中省、市、区为用户下单时所在的地点)。

存在一个情况,某用户早上在杭州市使用支付宝支付了一次,下午跑到绍兴市时又使用支付宝线下支付了一次。那么在统计省+市维度的日支付用户数,需要为杭州市、绍兴市各计1;但在省维度下,需要按用户去重,只能为浙江省计1。针对这种情况,通常就需要以Cube的方式完成数据预计算,同时每个维度组合都需要进行去重操作,因为不可累加。本文将此种场景简称为去重Cube。
在这里插入图片描述

这三种写法其实都类似,重点都在于对数据进行膨胀,再进行去重统计。其执行流程如下图所示,核心思路都是先把数据"膨胀"拆为多行,再按照“普通”的Distinct去重统计,因此性能上本身无太大差异,主要在于代码可维护性上。
在这里插入图片描述

在实际案例中,我们发现,去重Cube的计算过程中,80%+的计算成本消耗在数据膨胀和数据传输上。比如支付宝高管核心指标场景,需要计算各种组合维度下的支付用户数以支持决策分析。实际组合维度达20+,实际执行任务如下图所示,其中R3_2为核心的数据膨胀过程,数据膨胀近10倍,中间结果数据大小由100GB膨胀至1TB、数据量由120亿膨胀至近1300亿,大部分计算资源和计算耗时都花在数据膨胀和传输上了。若实际的组合维度进一步增加的话,数据膨胀大小也将进一步增加。

一种新的思路

首先对问题进行拆解下,去重Cube的计算过程核心分为两个部分,数据膨胀+数据去重。数据膨胀解决的是一行数据同时满足多种维度组合的计算,数据去重则是完成最终的去重统计,核心思路还是在于原始数据去匹配结果数据的需要。其中数据去重本身的计算量就较大,而数据膨胀会导致这一情况加剧,因为计算过程中需要拆解和在shuffle过程中传输大量的数据。数据计算过程中是先膨胀再聚合,加上本身数据内容的中英文字符串内容较大,所以才导致了大量的数据计算和传输成本。
而我们的核心想法是能否避免数据膨胀,同时进一步减少数据传输大小。因此我们联想到,是否可以采用类似于用户打标签的数据打标方案,先进行数据去重生成UID粒度的中间数据,同时让需要的结果维度组合反向附加到UID粒度的数据上,在此过程中并对结果维度进行编号,用更小的数据结构去存储,避免数据计算过程中的大量数据传输。整个数据计算过程中,数据量理论上是逐渐收敛的,不会因为统计维度组合的增加而增加。

核心思路

在这里插入图片描述

核心计算思路如上图,普通的数据膨胀计算cube的方法,中间需要对数据进行膨胀,再聚合,其中结果统计需要的组合维度数就是数据膨胀的倍数,比如上述的“省、省+市”共计两种维度组合,数据预计要膨胀2倍。
而新的数据聚合方法,通过一定的策略方法将维度组合拆解为维度小表并进行编号,然后将原本的订单明细数据聚合至用户粒度的中间过程数据,其中各类组合维度转换为数字标记录至用户维度的数据记录上,整个计算过程数据量是呈收敛聚合的,不会膨胀。

逻辑实现

在这里插入图片描述

明细数据准备:以用户线下支付数据为例,明细记录包含订单编号、用户ID、支付日期、所在省、所在市、支付金额。最终指标统计需求为统计包含省、市组合维度+支付用户数的多维Cube。
订单编号 用户ID 支付日期 所在省 所在市 支付金额
2023111101 U001 2023-11-11 浙江省 杭州市 1.11
2023111102 U001 2023-11-11 浙江省 绍兴市 2.22
2023111103 U002 2023-11-11 浙江省 杭州市 3.33
2023111104 U003 2023-11-11 江苏省 南京市 4.44
2023111105 U003 2023-11-11 浙江省 温州市 5.55
2023111106 U004 2023-11-11 江苏省 南京市 6.66
整体方案流程如下图。

  • STEP1:对明细数据进行所需的维度提取(即Group By对应字段),得到维度集合。
    所在省 所在市
    浙江省 杭州市
    浙江省 绍兴市
    浙江省 温州市
    江苏省 南京市

  • STEP2:对得到的维度集合生成Cube,并对Cube的行进行编码 (假设最终需要所在省、所在省+所在市 2种组合维度),可以用ODPS的Cube功能实现,再根据生成的Cube维度组合进行排序生成唯一编码。
    原始维度:所在省 原始维度:所在省 Cube 维度:所在省 Cube 维度:所在市 Cube行ID(可通过排序生成)
    浙江省 杭州市 浙江省 ALL 1
    浙江省 杭州市 浙江省 杭州市 2
    浙江省 绍兴市 浙江省 ALL 1
    浙江省 绍兴市 浙江省 绍兴市 3
    浙江省 温州市 浙江省 ALL 1
    浙江省 温州市 浙江省 温州市 4
    江苏省 南京市 江苏省 ALL 5
    江苏省 南京市 江苏省 南京市 6

  • STEP3:将Cube的行编码,根据映射关系回写到用户明细上,可用Mapjoin的方式实现。
    订单编号 用户ID 支付日期 所在省 所在市 汇总Cube ID
    2023111101 U001 2023-11-11 浙江省 杭州市 [1,2]
    2023111102 U001 2023-11-11 浙江省 绍兴市 [1,3]
    2023111103 U002 2023-11-11 浙江省 杭州市 [1,2]
    2023111104 U003 2023-11-11 江苏省 南京市 [5,6]
    2023111105 U003 2023-11-11 浙江省 温州市 [1,4]
    2023111106 U004 2023-11-11 江苏省 南京市 [5,6]

  • STEP4:汇总到用户维度,并对 Cube ID集合字段进行去重 (可以用ARRAY 的DISTINCT)
    用户ID 汇总Cube ID
    U001 [1,2,3]
    U002 [1,2]
    U003 [1,4,5,6]
    U004 [5,6]

  • STEP5:按照Cube ID进行计数计算(由于STEP4已经去重啦,因此这里不需要再进行去重);然后按照映射关系进行维度还原。
    Cube ID 下单用户数指标 Cube 维度还原:所在省 Cube 维度还原:所在市
    1 3 浙江省 ALL
    2 2 浙江省 杭州市
    3 1 浙江省 绍兴市
    4 1 浙江省 温州市
    5 2 江苏省 ALL
    6 2 江苏省 江苏省

代码实现


-- CREATE TABLE IF NOT EXISTS tmp_user_pay_order_detail
-- LIFECYCLE 30 AS 
-- SELECT     '2023111101' AS trade_no,'U001' AS payer_user_id,'20231111' AS gmt_pay,1.11 AS trade_pay_amt,'浙江省' AS province_name,
--     1 AS province_code,'杭州市' AS city_name, 11 AS city_code
-- UNION ALL SELECT '2023111102','U001','20231111',2.22,'浙江省',1,'绍兴市',12
-- UNION ALL SELECT '2023111103','U002','20231111',3.33,'浙江省',1,'杭州市',11
-- UNION ALL SELECT '2023111104','U003','20231111',4.44,'江苏省',2,'南京市',22
-- UNION ALL SELECT '2023111105','U003','20231111',5.55,'浙江省',1,'温州市',13
-- UNION ALL SELECT '2023111106','U004','20231111',6.66,'江苏省',2,'南京市',22

WITH dim_cube AS 
(
    SELECT
        *
        ,DENSE_RANK() OVER(PARTITION BY 1 ORDER BY cube_province_name,cube_city_name) AS cube_id 
    FROM 
    (
        SELECT 
            dim_key
            ,COALESCE(IF(GROUPING(province_name) = 0,province_name,'ALL'),'na') AS cube_province_name             
            ,COALESCE(IF(GROUPING(city_name    ) = 0,city_name    ,'ALL'),'na') AS cube_city_name 
        FROM 
        (
            SELECT  
                CONCAT('',COALESCE(province_name ,''),'#' ,COALESCE(city_name,''),'#' ) AS dim_key
                ,province_name
                ,city_name
            FROM tmp_user_pay_order_detail
            GROUP BY province_name,city_name
        )t 
        GROUP BY dim_key
                ,province_name
                ,city_name
        GROUPING SETS
        (
            (dim_key,province_name)
            ,(dim_key,province_name,city_name)
        )
    )
)

,detail_ext AS 
(
    SELECT 
        payer_user_id
        ,ARRAY_DISTINCT(SPLIT(WM_CONCAT(';',cube_ids),';')) as cube_id_arry
    FROM 
    (
        SELECT  
            /*+ MAPJOIN(dim_cube) */ 
            payer_user_id
            ,cube_ids
        FROM 
        (
            SELECT   
                payer_user_id
                ,CONCAT(''
                    ,COALESCE(province_name ,''),'#' 
                    ,COALESCE(city_name     ,''),'#' 
                ) AS dim_key
            FROM  tmp_user_pay_order_detail
        ) dwd_detail
        JOIN 
        (
            SELECT 
                dim_key
                ,WM_CONCAT(';',cube_id) AS cube_ids
            FROM dim_cube 
            GROUP BY dim_key
        ) dim_cube
        ON dwd_detail.dim_key = dim_cube.dim_key
    )
    GROUP BY payer_user_id
)
SELECT 
    cube_id
    ,MAX(province_name) AS province_name
    ,MAX(city_name    ) AS city_name
    ,MAX(uid_cnt      ) AS user_cnt
FROM 
(
    SELECT   cube_id                 AS cube_id
            ,COUNT(1)                AS uid_cnt
            ,CAST(NULL    AS STRING) AS province_name
            ,CAST(NULL    AS STRING) AS city_name
    FROM detail_ext
    LATERAL VIEW EXPLODE(cube_id_arry) arr AS cube_id
    GROUP BY cube_id

    UNION ALL 

    SELECT   CAST(cube_id AS STRING) AS cube_id
            ,CAST(NULL    AS BIGINT) AS uid_cnt
            ,cube_province_name AS province_name
            ,cube_city_name     AS city_name    
    FROM dim_cube
) base 
GROUP BY cube_id```









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

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

相关文章

匿名发送短信

匿名发送短信 匿名发送短信啦!不用程序猿,也能定制专属推送消息!每日小惊喜! 还可以领取课程资料!软考中级软件设计师,高级信息系统项目管理师! 先说在哪里 微信搜公众号:暮看云 微…

人工智能 | 自然语言处理的发展历程

github:https://github.com/MichaelBeechan CSDN:https://blog.csdn.net/u011344545 自然语言处理的发展 方向一:技术进步1. 基于规则的语法(1950-1990)2. 统计语言处理(1990-2010)3. 基于深度学…

ChatGPT正确打开方式与GPT-4.5的key最新获取方式

前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家:https://www.captainbed.cn/z ChatGPT体验地址 文章目录 前言4.5key价格泄漏ChatGPT4.0使用地址ChatGPT正确打开方式最新功能语音助手存档…

计算机网络-标准化工作及相关组织与性能指标(标准分类 标准化工作 RFC 速率 带宽 吞吐量 时延 时延带宽积 RTT 利用率)

文章目录 标准化工作及相关组织标准化工作标准分类RFC流程标准化的相关组织小结 性能指标速率带宽吞吐量时延发送时延传播时延排队时延与处理时延补充 高速链路 时延带宽积往返时间RTT利用率小结 标准化工作及相关组织 标准化工作 即需要统一标准,这样才能兼容 …

Linux 时间同步 - Chrony服务

Linux 时间同步 - Chrony服务 引言一、简单使用二、详解2.1 chrony.conf2.2 chronyd2.3 chronyc 引言 为什么需要时间同步? 其意义可参考秦朝统一度量衡,车同轨,书同文。核心就是方便协同工作。 Chrony能更精确、更快的同步时钟,传统ntp需要…

014集:python访问互联网:网络爬虫实例—python基础入门实例

以pycharm环境为例: 首先需要安装各种库(urllib:requests:Openssl-python等) python爬虫中需要用到的库,大致可分为:1、实现 HTTP 请求操作的请求库;2、从网页中提取信息的解析库;3、Python与…

代码随想录算法训练营day13|239.滑动窗口最大值、347.前K个高频元素

239. 滑动窗口最大值 347.前 K 个高频元素 239. 滑动窗口最大值 (一刷至少需要理解思路) 之前讲的都是栈的应用,这次该是队列的应用了。 本题算比较有难度的,需要自己去构造单调队列,建议先看视频来理解。 题目链接/文…

css3轮播图案例

轮播图案例 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><style>…

HTML111111111

在线编辑器 在线 HTML 空元素 没有内容的 HTML 元素被称为空元素。空元素是在开始标签中关闭的。 即使 在所有浏览器中都是有效的&#xff0c;但使用 其实是更长远的保障。 HTML 水平线 标签在 HTML 页面中创建水平线。 hr 元素可用于分隔内容。 HTML 折行 如果您希望…

mysql原理--事务的隔离级别与 MVCC

1.事前准备 为了故事的顺利发展&#xff0c;我们需要创建一个表&#xff1a; CREATE TABLE hero (number INT,name VARCHAR(100),country varchar(100),PRIMARY KEY (number) ) EngineInnoDB CHARSETutf8;然后向这个表里插入一条数据&#xff1a;INSERT INTO hero VALUES(1, 刘…

高效火情监测,科技助力森林防火【数字地球开放平台】

数字地球开放平台-以卫星遥感为核心的空天信息服务开放平台 (geovisearth.com) 2019年3月30日&#xff0c;四川省凉山州木里县爆发了一场森林火灾&#xff0c;火点位于海拔3800米左右&#xff0c;地形险峻、坡度陡峭、谷深难以抵挡火势。在扑救的过程中&#xff0c;27名森林消防…

扩散模型参数量降低87%,且提升生成质量;通过蒸馏实现一步采样扩散模型;VideoCrafter2视频生成;深度感知图像合成

本文首发于公众号&#xff1a;机器感知 扩散模型参数量降低87%&#xff0c;且提升生成质量&#xff1b;通过蒸馏实现一步采样扩散模型&#xff1b;VideoCrafter2视频生成&#xff1b;深度感知图像合成 One-Step Diffusion Distillation via Deep Equilibrium Models Diffusio…

恭贺丰果管道入围2024中国管道十大品牌

恭贺丰果管道入围2024中国管道十大品牌 丰果&#xff08;中国&#xff09;有限公司 丰果管道品牌创立于1999年&#xff0c;是国内最早从事PPR家装管道生产的品牌之一&#xff0c;在业内有着良好的口碑和市场美誉度&#xff0c;在全国的头部装企更是有相当高的市场占有率。2023…

java数据结构与算法刷题-----LeetCode209. 长度最小的子数组

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 解题思路 代码:时间复杂度O(n).空间复杂度O(1) class Solution {public in…

MyBatisPlus学习笔记二

接上&#xff1a;MyBatisPlus学习笔记一&#xff1a; MyBatisPlus学习笔记一-CSDN博客 1、条件构造器 MyBatisPlus支持各种复杂的where条件&#xff0c;可以满足日常开发的所有需求。 1.1、集成体系 1.2、实例 查询 lambda查询 更新 1.3、总结 2、自定义sql 我们可以利用MyB…

学会了不要瞎搞,刑不刑,不是你说了算

很多人在做自媒体上传视频的时候不知道该如何去消除视频里的人声或背景音乐&#xff0c;其实解决办法很简单&#xff0c;我们使用3个软件进行演示 第一个&#xff1a;智优影 快速入口&#xff1a;AI音视频画质修复工具 - 智优影https://www.onezlzyy.com/ 这是一个非常专业的…

【GitHub项目推荐--一键换脸】【转载】

FaceSwap 是一种利用深度学习算法来换掉图片和视频中的人脸的工具。基于 Tensorflow、Keras 和 Python&#xff0c;Faceswap 可以在 Windows、macOS 和 Linux 上运行。 安装了这个应用&#xff0c;你就能在你电脑上通过可视化交互的方式构建自己的换脸模型了。 地址&#xff…

JVM工作原理与实战(二十):直接内存

专栏导航 JVM工作原理与实战 RabbitMQ入门指南 从零开始了解大数据 目录 专栏导航 前言 一、直接内存 1.直接内存作用 二、在直接内存上创建数据 总结 前言 JVM作为Java程序的运行环境&#xff0c;其负责解释和执行字节码&#xff0c;管理内存&#xff0c;确保安全&…

静态路由高级特性(HCIA)

目录 一、静态路由高级特性 1、路由条目六要素 2、路由分类 3、静态路由配置命令 &#xff08;1&#xff09;静态路由中下一跳MA和P2P区别 4、静态路由加路由表条件 5、permanent特性 二、路由冗余和负载 1、控制层面control plane 2、数据层面data plane 路由操控精髓&#xf…

学习视频一些杂乱的东西

文章目录 ref获取dom元素监听深层的某个属性? 可选链操作符 和 ?? 双问号表达式v-slot 语法糖作用域插槽动态插槽 初始化数组骚操作数字滚动 -> gsapstyle妙招新奇的原型链 object.createB站笔记链接JS相关设计模式ajaxsvgvue3scsswebpack内存泄漏 ref获取dom元素 直接给…