数据测试实践:从一个bug开始的大数据引擎兼容性探索

数据测试,实践,一个,bug,开始,数据,引擎,兼容性,探索 · 浏览次数 : 130

小编点评

**BUG描述** 在数据测试实践中,在使用Spark引擎对年度账单汇总表中的小家电品类集合进行查询时,发现结果与手动计算结果不一致。 **缺陷分析** 1. **SQL语法差异:** Spark引擎使用 `collect_set()` 函数对集合进行分组聚合,而 HSQLD引擎使用 `collect()` 函数。由于两种引擎的聚合方法不同,结果可能有所不同。 2. **数据类型兼容性:** `collect_set()` 函数可能无法处理所有数据类型,尤其是字符串类型。当用户购买的商品类别是字符串类型时,`collect_set()` 可能无法正确处理这些数据。 **解决方法** 1. 使用 `array_agg()` 函数进行分组聚合: ```sql SELECT user_log_acct, array_agg(small_electrical_appliance_list) AS small_electrical_appliance FROM ... GROUP BY user_log_acct; ``` 2. 在使用 `collect_set()` 函数之前确保所有用户购买的商品类别都支持该数据类型。 **其他建议** 1. 仔细检查数据类型兼容性,确保所有数据类型都支持使用。 2. 在使用 `collect_set()` 函数时,使用 `array_agg()` 函数进行分组聚合以确保结果一致。 3. 如果数据类型支持,可以使用 `CAST()` 函数进行类型转换。

正文

作者:京东零售 李晓洁

我们常常忘记,天才也取决于其所能掌握的数据,即使阿基米德也无法设计出爱迪生的发明。——Ernest Dimnet

在大数据时代,精准而有效的数据对于每个致力于长期发展的组织来说都是重要资产之一,而数据测试更是不可或缺的一部分。数据测试不仅关注数据加工的代码逻辑,还要考虑大数据执行引擎带来的影响,因为各种引擎框架将对同一份数据产生不同的计算或检索结果。本文将从一个年度账单bug引入,讲解在数据测试实践中对大数据执行引擎兼容性差异的探索。

一、需求内容

京东-我的京东-年度账单是一年一次,以用户视角对在平台一年的消费情况进行总结。账单从购物,权益,服务等方面切入,帮助用户挖掘在自我难以认知的数据角度,通过这种方式让用户从账单中发掘打动内心的立意,并主动进行分享和传播。本次,我京年度账单以“2022购物印象”为主题,通过不同的数据维度组成村落故事线,用户以虚拟人物形象贯穿始终,用户浏览完故事线后,可生成购物印象。

年度账单其中一个报表为用户年度购买的小家电品类。该报表使用年度账单汇总表中的小家电品类集合字段,计算了2022年度某用户全年最后购买的两款小家电所在的品类。本文bug分享将围绕这个字段展开。

二、 缺陷描述

缺陷描述:在APP层用户年度账单汇总模型app_my_jd_user_bill_year_sum中,对于小家电品类集合字段,APP表结果与手动计算结果不一致。

以用户'Mercury'、'乐乐1024'、'活力少年'的购买数据为例,上游ADM层以array<string>类型存储用户每月购买的小家电相关品类,如下图所示:

• 根据小家电品类集合字段定义,APP层应取这三个用户全年最后购买的2个品类,即'Mercury'在2022年11月购买的VR头戴显示器、电炒锅,'乐乐1024'在2022年10月购买的冲牙器、空气净化器,'活力少年'在2022年10月购买的VR头戴显示器、电炒锅。因此,经手动计算,APP层正确计算结果应为:

• 而APP层年度账单汇总表中的小家电集合品类如下,结果错误,不符合预期结果。

三、 缺陷排查过程

1. 执行引擎兼容差异

测试排查中,首先发现了Hive和Spark引擎之间的语法兼容差异。

• 当使用APP层脚本中小家电品类集合口径构建SQL,手动对上游表执行查询时发现,Hive引擎得到的集合有序,执行结果正确:

• 使用Spark引擎执行查询时,集合乱序,执行结果错误:

2. 脚本梳理

缺陷原因为集合乱序导致的取数错误。每个用户在上游ADM存在12个数组对应12个月购买小家电品类的集合,需要集合函数(collect)将12个月分组数据倒序排序,汇合成1个列表,然后取列表前两个元素。

HQL提供两种分组聚合函数:collect_list()collect_set(),区别在于collect_set()会对列表元素去重。由于用户不同月购买的品类集合可能重复,因此脚本使用了collect_set()

然而collect_set()将导致集合乱序,集合中元素不再按月份倒序排列,取出List[0]和List[1]不是用户全年最后购买的两个小家电品类。

SELECT
    user_pin,
    small_electrical_appliance_list,
    concat_ws('|', small_electrical_appliance_list[0], small_electrical_appliance_list[1]) AS small_electrical_appliance
FROM(
    SELECT
        user_pin,
        collect_set(concat_ws(',', small_electrical_appliance_list_split)) AS small_electrical_appliance_list
    FROM(
    SELECT
        dt,
        user_pin,
        small_electrical_appliance_list,
        concat_ws(',', small_electrical_appliance_list) AS small_electrical_appliance
    FROM adm_my_jd_user_bill_month
    WHERE
        dt >= '2022-01'
        AND dt <= '2022-12'
    ORDER BY dt DESC) tmp 
    lateral VIEW explode(SPLIT(small_electrical_appliance, ',')) tmp AS small_electrical_appliance_list_split
    GROUP BY user_log_acct ) 



3. 结论

• 计算脚本逻辑错误,不应使用collect_set()聚合分组。

• 在原生Hive/Spark中,collect_set()函数均无法保证集合有序,而大数据平台Hive对集合计算有序。因此,该脚本在Hive引擎下可以达到生成全年最后购买两个小家电品类的预期目标,但spark引擎则无法得到正确结果。

• Hive执行效率较低,研发通常通过Spark引擎执行,最终导致结果错误。

四、大数据计算引擎兼容差异

1. collect_list()/collect_set() 在hive/spark和presto之间的区别

collect_set()collect_list()在Presto中无法兼容。

• 替代函数:array_agg() (https://prestodb.io/docs/current/functions/aggregate.html?highlight=array_agg#array_agg)

Hive/Spark Presto
collect_list() array_agg()
collect_set() array_distinct(array_agg())

2. 行转列函数在hive和presto之间的区别

• Hive使用lateral VIEW explode()执行行转列的操作,而Presto不支持该函数。这种单列的值转换成和student列一对多的行的值映射.

◦ Hive/Spark query:

lateral VIEW explode(SPLIT(small_electrical_appliance, ',')) tmp AS small_electrical_appliance_list_split

• Presto支持UNNEST来扩展array和map。文档:(https://prestodb.io/docs/current/migration/from-hive.html)

◦ Presto query:

CROSS JOIN UNNEST(SPLIT(small_electrical_appliance, ',')) AS small_electrical_appliance_list_split;

3. 隐式转换在引擎之间的区别

• Hive/Spark支持包括字符串类型到数字类型在内的多种隐式转换,如将字符串'07'转化为数字7,然后进行比较操作。

◦ Hive隐式转换规则:详见链接 Allowed Implicit Conversions

• 虽然Presto也有自己的一套隐式类型转换规则包含在public Optional<Type> coerceTypeBase(Type sourceType, String resultTypeBase)方法中,但对数据类型的要求更为严格。一些在Hive中常见的数字与字符串进行比较的查询语句,Presto会直接抛类型不一致的错误。

◦ 下图为Hive和Presto的隐式转换规则,蓝色区域是Presto和Hive都支持的类型转换,绿色区域是Presto不支持但是Hive支持的类型转换,红色区域是两者都不支持的类型转换。可以看到,hive的隐式转换更为广泛,而presto尤其在字符类型的隐式转换中更为严格。

• 隐式转换示例:

--Hive/Spark隐式转换
'07' >= 6 -- true (CAST('07' AS DOUBLE) >= CAST(6 AS DOUBLE))
'test' <> 1 -- NULL
'1' = 1.0 -- true

--Presto隐式转换
'07' >= 6 -- false (CAST('07' AS Varchar) >= CAST(6 AS Varchar))
'test' <> 1 -- true
'1' = 1.0 -- ERROR:io.prestosql.spi.PrestoException: Unexpected parameters (varchar(1), decimal(2,1)) for function $operator$equal. Expected: $operator$equal(T, T) T:comparable



与数据测试实践:从一个bug开始的大数据引擎兼容性探索相似的内容:

数据测试实践:从一个bug开始的大数据引擎兼容性探索

数据测试不仅关注数据加工的代码逻辑,还要考虑大数据执行引擎带来的影响,因为各种引擎框架将对同一份数据产生不同的计算或检索结果。本文将从一个年度账单bug引入,讲解在数据测试实践中对大数据执行引擎兼容性

[转帖]麒麟v10上部署TiDBv5.1.2生产环境的最佳实践

https://tidb.net/book/tidb-monthly/2022/2022-07/usercase/tidb-v5-1-2 前言​ 笔者最近在一个银行项目中做 PoC 测试,由于客户选择了使用 TiDB 数据库,于是笔者在 TiDB 中选择了一个相对稳定并且 bug 较少的版本:TiD

阿里DataX极简教程

目录简介工作流程核心架构核心模块介绍DataX调度流程支持的数据实践下载环境执行流程引用 简介 DataX是一个数据同步工具,可以将数据从一个地方读取出来并以极快的速度写入另外一个地方。常见的如将mysql中的数据同步到另外一个mysql中,或者另外一个mongodb中。 工作流程 read:设置一

MySQL主从复制原理剖析与应用实践

MySQL Replication(主从复制)是指数据变化可以从一个MySQL Server被复制到另一个或多个MySQL Server上,通过复制的功能,可以在单点服务的基础上扩充数据库的高可用性、可扩展性等。

一种KV存储的GC优化实践

从内部需求出发,我们基于TiKV设计了一款兼容Redis的KV存储。基于TiKV的数据存储机制,对于窗口数据的处理以及过期数据的GC问题却成为一个难题。本文希望基于从KV存储的设计开始讲解,到GC设计的逐层优化的过程,从问题的存在到不同层面的分析,可以给读者在类似的优化实践中提供一种参考思路。

[转帖]缓存与存储的一致性策略:从 CPU 到分布式系统

https://zhuanlan.zhihu.com/p/151745863 在计算机系统设计实践中,我们常常会遇到下图所示架构: 为了解决单个存储器读吞吐无法满足要求的问题,常常需要在存储器上面增加一个或多个缓存。但由于相同的数据被复制到一个或多个地方,就容易引发数据一致性问题。不一致的数据可能出

事务相关知识集锦

作者:李玉亮 引言 数据库事务与大多数后端软件开发人员的工作密不可分,本文从事务理论、事务技术、事务实践等方面对常用的相关事务知识进行整理总结,供大家参考。 事务理论介绍 事务定义 在数据库管理系统中,事务是单个逻辑或工作单元,有时由多个操作组成,在数据库中以一致模式完成的逻辑处理称为事务。一个例子

Pytorch DistributedDataParallel(DDP)教程二:快速入门实践篇

一、简要回顾DDP 在上一篇文章中,简单介绍了Pytorch分布式训练的一些基础原理和基本概念。简要回顾如下: 1,DDP采用Ring-All-Reduce架构,其核心思想为:所有的GPU设备安排在一个逻辑环中,每个GPU应该有一个左邻和一个右邻,设备从它的左邻居接收数据,并将数据汇总后发送给右邻。

国际财务系统基于ShardingSphere的数据分片和一主多从实践

作者:京东物流 张广治 1 背景 传统的将数据集中存储至单一数据节点的解决方案,在性能和可用性方面已经难于满足海量数据的场景,系统最大的瓶颈在于单个节点读写性能,许多的资源受到单机的限制,例如连接数、网络IO、磁盘IO等,从而导致它的并发能力不高,对于高并发的要求不满足。 每到月初国际财务系统压力巨

基于Spark的大规模日志分析

摘要:本篇文章将从一个实际项目出发,分享如何使用 Spark 进行大规模日志分析,并通过代码演示加深读者的理解。 本文分享自华为云社区《【实战经验分享】基于Spark的大规模日志分析【上进小菜猪大数据系列】》,作者:上进小菜猪。 随着互联网的普及和应用范围的扩大,越来越多的应用场景需要对海量数据进行