案例分析 | 由Decimal操作计算引发的Spark数据丢失问题

转载自  案例分析 | 由Decimal操作计算引发的Spark数据丢失问题

供稿 | Hadoop Team

编辑 | 顾欣怡

本文3058字,预计阅读时间10分钟

导读

eBay的Hadoop集群上面每天运行着大量Spark计算任务。对于数据计算任务,其计算性能十分重要,数据质量也不可忽视,特别是对于金融数据,数据发生损坏将会产生严重后果。本文分享一次数据质量相关的问题以及我们排查该问题的过程和解决方案。

 

一、症状

一天,金融分析团队的同事报告了一个问题,他们发现在两个生产环境中(为了区分,命名为环境A和B), Spark大版本均为2.3。但是,当运行同样的SQL语句,对结果进行对比后,却发现两个环境中有一列数据并不一致

此处对数据进行脱敏,仅显示发生数据丢失那一列的数据,如下:

由此可见,在环境A中可以查询到该列数据,但是在环境B中却出现了部分数据缺失。

 

二、排查

上述两个查询中用的Spark大版本是一致的,团队的同事通过对比两个环境中的配置,发现有一个参数在最近进行了变更。该参数为:spark.sql.decimalOperations.allowPrecisionLoss, 默认为true。

在环境A中未设置此参数,所以为true,而在环境B下Spark client的spark-defaults.conf中,该参数设置为false。

该参数为PR SPARK-22036 引入,是为了控制在两个Decimal类型操作数做计算的时候,是否允许丢失精度。在本文中,我们就针对乘法这种计算类型做具体分析。

 

关于Decimal类型

在详细介绍该参数之前,先介绍一下Decimal。

Decimal是数据库中的一种数据类型,不属于浮点数类型,可以在定义时划定整数部分以及小数部分的位数。对于一个Decimal类型,scale表示其小数部分的位数,precision表示整数部分位数和小数部分位数之和。

一个Decimal类型表示为Decimal(precision, scale),在Spark中,precision和scale的上限都是38

一个double类型可以精确地表示小数点后15位,有效位数为16位

可见,Decimal类型则可以更加精确地表示,保证数据计算的精度。

例如一个Decimal(38, 24)类型可以精确表示小数点后23位,小数点后有效位数为24位。而其整数部分还剩下14位可以用来表示数据,所以整数部分可以表示的范围是-10^14+1~10^14-1。

 

关于精度和Overflow

关于精度的问题其实我们小学时候就涉及到了,比如求两个小数加减乘除的结果,然后保留小数点后若干有效位,这就是保留精度。

乘法操作我们都很清楚,如果一个n位小数乘以一个m位小数,那么结果一定是一个(n+m)位小数。

举个例子, 1.11 * 1.11精确的结果是 1.2321,如果我们只能保留小数点后两位有效位,那么结果就是1.23。

上面我们提到过,对于Decimal类型,由于其整数部分位数是(precision-scale),因此该类型能表示的范围是有限的,一旦超出这个范围,就会发生Overflow。而在Spark中,如果Decimal计算发生了Overflow,就会默认返回Null值。

举个例子,一个Decimal(3,2)类型代表小数点后用两位表示,整数部分用一位表示,因此该类型可表示的整数部分范围为-9~9。如果我们CAST(12.32 as Decimal(3,2)),那么将会发生Overflow。

下面介绍spark.sql.decimalOperations. allowPrecisionLoss参数。

当该参数为true(默认)时,表示允许Decimal计算丢失精度,并根据Hive行为和SQL ANSI 2011规范来决定结果的类型,即如果无法精确地表示,则舍入结果的小数部分。

当该参数为false时,代表不允许丢失精度,这样数据就会表示得更加精确。eBay的ETL部门在进行数据校验的时候,对数据精度有较高要求,因此我们引入了这个参数,并将其设置为false以满足ETL部门的生产需求。

设置这个参数的初衷是美好的,但是为什么会引发数据损坏呢?

用户的SQL数据非常长,通过查看相关SQL的执行计划,然后进行简化,得到一个可以复现的SQL语句,如下:

 

上面的select语句将会返回一个NULL。

我们将上述语句的执行计划打印出来。

 

执行计划很简单,里面有一个二元操作(乘法),左边的case when 是一个Decimal(34, 24)类型,右边是一个Literal(1)。

程序员都知道,在编程中,如果两个不同类型的操作数做计算,就会将低级别的类型向高级别的类型进行类型转换,Spark中也是如此。

一条SQL语句进入Spark-sql引擎之后,要经历Analysis->optimization->生成可执行物理计划的过程。而这个过程就是不同的Rule不断作用在Plan上面,然后Plan随之转化的过程。

在Spark-sql中有一系列关于类型转换的Rule,这些Rule作用在Analysis阶段的Resolution子阶段。

其中就有一个Rule叫做ImplicitTypeCasts,会对二元操作(加减乘除)的数据类型进行转换,如下图所示:

用文字解释一下,针对一个二元操作(加减乘除), 如果左边的数据类型和右边不一致,那么会寻找一个左右操作数的通用类型(common type), 然后将左右操作数都转换为通用类型。针对我们此案例中的 Decimal(34, 24) 和Literal(1), 它们的通用类型就是Decimal(34, 24),所以这里的Literal(1)将被转换为Decimal(34, 24)。

这样该二元操作的两边就都是Decimal类型。接下来这个二元操作会被Rule DecimalPrecision中的decimalAndDecimal方法处理。

在不允许精度丢失时,Spark会为该二元操作计算一个用来表达计算结果的Decimal类型,其precision和scale的计算公式如下表所示,这是参考了SQLServer的实现。

 

此处我们的操作数都已经是Decimal(34, 24)类型了,所以p1=p2=34, s1=s2=24。

如果不允许精度丢失,那么其结果类型就是 Decimal(p1+p2+1, s1+s2)。由于precision和scale都不能超过上限38,所以这里的结果类型是Decimal(38, 38), 也就是小数部分为38位。于是整数部分就只剩下0位来表示,也就是说如果整数部分非0,那么这个结果就会Overflow。在当前版本中,如果Decimal Operation 计算发生了Overflow,就会返回一个Null的结果。

这也解释了在前面的场景中,为什么使用环境B中Spark客户端跑的结果,非Null的结果中整数部分都是0,而小数部分精度更高(因为不允许精度丢失)。

好了,问题定位到这里结束,下面讲解决方案。

 

三、解决方案

01 合理处理操作数类型

通过观察Spark-sql中Decimal 相关的Rule,发现了Rule DecimalPrecision中的nondecimalAndDecimal方法,这个方法是用来处理非Decimal类型和Decimal类型操作数的二元操作。

此方法代码不多,作用就是前面提到的左右操作数类型转换,将两个操作数转换为一样的类型,如下图所示:

 

文字描述如下:

如果其中非Decimal类型的操作数是Literal类型, 那么使用DecimalType.fromLiteral方法将该Literal转换为Decimal。例如,如果是Literal(1),则转化为Decimal(1, 0);如果是Literal(100),则转化为Decimal(3, 0)。

如果其中非Decimal类型操作数是Integer类型,那么使用DecimalType.forType方法将Integer转换为Decimal类型。由于Integer.MAX_VALUE 为2147483647,小于3*10^9,所以将Integer转换为Decimal(10, 0)。当然此处省略了其他整数类型,例如,如果是Byte类型,则转换为Decimal(3,0);Short类型转换为Decimal(5,0);Long类型转换为Decimal(20,0)等等。

如果其中非Decimal类型的操作是float/double类型,则将Decimal类型转换为double类型(此为DB通用做法)。

因此,这里用DecimalPrecision Rule的nonDecimalAndDecimal方法处理一个Decimal类型和另一个非Decimal类型操作数的二元操作的做法要比前面提到的ImplicitTypeCasts规则处理更加合适。ImplicitTypeCasts 会将Literal(1) 转换为Decimal(34, 24), 而DecimalPrecision将Literal(1)转换为Decimal(1, 0) 。

经过DecimalPrecision Rule的nonDecimalAndDecimal处理之后的两个Decimal类型操作数会被DecimalPrecision中的decimalAndDecimal方法(上文提及过)继续处理。

上述提到的案例是一个乘法操作,其中,p1=34, s1=24, p2 =1, s2=0。

其结果类型为Decimal(36,24),也就是说24位表示小数部分, 12位表示整数部分,不容易发生Overflow。

前面提到过,Spark-sql中关于类型转换的Rule作用在Analysis阶段的Resolution子阶段。而Resolution子阶段会有一批Rule一直作用在一个Plan上,直到这个Plan到达一个不动点(Fixpoint),即Plan不再随Rule作用而改变。

因此,我们可以在ImplicitTypeCasts规则中对操作数类型进行判断。如果在一个二元操作中有Decimal类型的操作数,则此处跳过处理,这个二元操作后续会被DecimalPrecision规则中的nonDecimalAndDecimal方法和decimalAndDecimal方法继续处理,最终到达不动点。

我们向Spark社区提了一个PR SPARK-29000, 目前已经合入master分支。

 

02 用户可感知的Overflow

除此之外,默认的DecimalOperation如果发生了Overflow,那么其结果将返回为NULL值,这样的计算结果异常并不容易被用户感知到(此处非常感谢金融分析团队的同事帮我们检查到了这个问题)。

在SQL ANSI 2011标准中,当算术操作发生Overflow时,会抛出一个异常。这也是大多数数据库的做法(例如SQLService, DB2, TeraData)。

PR SPARK-23179 引入了参数spark.sql. decimalOperations.nullOnOverflow 用来控制在Decimal Operation 发生Overflow时候的处理方式。

默认是true,代表在Decimal Operation发生Overflow时返回NULL的结果。

如果设置为false,则会在Decimal Operation发生Overflow时候抛出一个异常。

因此,我们在上面的基础上合入该PR,引入spark.sql.decimalOperations.nullOnOverflow参数,设置为false, 以保证线上计算任务的数据质量。

 

四、总结

本文分析了一个Decimal操作计算时发生的数据质量问题。我们不仅修复了其不合适的类型转换问题,减小了其结果Overflow的几率,还引入了一个参数,以便在计算发生Overflow时抛出异常,让用户感知到计算中存在的问题,保证线上计算的数据质量。

在大数据计算场景中,我们不仅关心数据计算得快不快,更关心结果数据的质量高不高。这需要各个团队的密切配合,平台开发人员需要提供可靠稳定的计算平台,业务团队需要写出高质量的SQL,数据服务团队则要提供良好的调度和校验服务。相信在各个团队的共同努力下,eBay在大数据这条路上能走得更远、更宽阔。

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

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

相关文章

(十一)MyBatis的动态SQL:trim元素

trim标记是一个格式化的标记,可以完成select,update,insert语句的格式化操作。trim元素的主要功能有四个: (1)可以在包含的内容前加上某些前缀,与之对应的属性是prefix; &#xff08…

入门干货之Electron的.NET实现-Electron.NET

0x01、Electron.NET1、介绍Electron是由Github上的一支团队和一群活跃贡献者维护。用HTML,CSS和JavaScript来构建跨平台桌面应用程序的一个开源库。 Electron通过将Chromium和Node.Js合并到同一个运行时环境中,并将其打包为Mac,Windows和Linu…

P2514-[HAOI2010]工厂选址【贪心】

正题 题目链接:https://www.luogu.org/problemnew/show/P2514 题目大意 有一个厂,需要bbb吨煤炭,然后第jjj个矿运到该厂的运费为c0,jc_{0,j}c0,j​,运行总费用为运费之和加上h0h_0h0​ 现在在1∼n1\sim n1∼n挑选一个建一个新厂&#xff0…

Scala与Java差异(三)之函数

一、函数定义 (1)函数的定义与调用 在Scala中定义函数时,需要定义函数的函数名、参数、函数体。 第一个函数如下所示: def sayHello(name: String, age: Int) {if (age > 18) { printf("hi %s, you are a big boy\n&…

(十二)C3P0连接池使用教程

一般我们在项目中操作数据库时,都是每次需要操作数据库就建立一个连接,操作完成后释放连接。因为jdbc没有保持连接的能力,一旦超过一定时间没有使用(大约几百毫秒),连接就会被自动释放掉。而每次新建连接都…

通过Chocolatey软件包管理器安装.NET Core

在Linux的世界里,有了yum/apt-get百分之九十的软件都可以通过它来安装管理。但是在Windows系统上,装个软件还是挺折腾的。比如我要装个Chrome浏览器,我先得打开IE浏览器吧,我还打不开Chrome的官网吧,得百度吧&#xff…

P3100-[USACO14JAN]建造滑雪场【贪心,dp】

正题 题目链接:https://www.luogu.org/problemnew/show/P3100 题目大意 一个空矩阵,每次可以将B∗BB*BB∗B的矩阵覆盖为RRR或者BBB。 求BBB最大是多少使得可以覆盖使得原矩阵成为目标矩阵。 解题思路 我们考虑贪心,先分析一下性质。 假设答案为kkk&…

Scala与Java差异(四)之数组操作

一、数组操作之Array、ArrayBuffer以及遍历数组 (1)Array 在Scala中,Array代表的含义与Java中类似,也是长度不可改变的数组。此外,由于Scala与Java都是运行在JVM中,双方可以互相调用,因此Scal…

(十三)RabbitMQ使用详解

RabbitMQ是基于AMQP的一款消息管理系统。AMQP(Advanced Message Queuing Protocol),是一个提供消息服务的应用层标准高级消息队列协议,其中RabbitMQ就是基于这种协议的一种实现。 常见mq: ActiveMQ:基于JMSRabbitMQ:…

ASP.NET Core Web API下事件驱动型架构的实现(二):事件处理器中对象生命周期的管理

在ASP.NET Core Web API下事件驱动型架构的实现(一):一个简单的实现中,我介绍了事件驱动型架构的一种简单的实现,并演示了一个完整的事件派发、订阅和处理的流程。这种实现太简单了,百十行代码就展示了一个…

和某ZYC巨佬和XXY巨佬的随机挑战2总结

前言 一切的起点在那个炎热的酷暑,菜的一批的WYCWYCWYC坐在最容易被∗*∗的左下角。这时他永远都想不到,他与巨佬之间的挑战,即将开始。 正题 规则 随机跳333到蓝题,然后写完。 完成记录 题目博客 T1:P3100−[USACO14JAN]T1:P31…

(十四)消息中间件MQ详解及四大MQ比较

一、消息中间件相关知识 1、概述 消息队列已经逐渐成为企业IT系统内部通信的核心手段。它具有低耦合、可靠投递、广播、流量控制、最终一致性等一系列功能,成为异步RPC的主要手段之一。当今市面上有很多主流的消息中间件,如老牌的ActiveMQ、RabbitMQ&a…

jzoj3301-[集训队互测2013]家族【并查集,暴力】

正题 题目大意 一个图每个边有不同的频率,对于大小为xxx的联通可以共享价值wxw_xwx​。现在要去保留一段频率内的边,使得剩下的联通分量价值之和至少为KKK。 求最小的保留频率宽度。 解题思路 首先将频率进行排序,然后愉快的发现不满足二分…

g4e基础篇#3 Git安装与配置

现在你已经对Git有了最基本的了解,现在让我们开始动手开始安装和配置Git环境。Git工具包括Git命令行工具,图形化工具和服务器环境;在我们这个教程中,我们会使用以下软件配置我们的环境:• Windows 操作系统&#xff08…

Scala与Java差异(五)之Map与Tuple

一、 创建Map (1)创建Map // 创建一个不可变的Map val ages Map("Leo" -> 30, "Jen" -> 25, "Jack" -> 23) ages("Leo") 31 // 创建一个可变的Map val ages scala.collection.mutable.Map("…

jzoj3302-[集训队互测2013]供电网络【上下界网络流,费用流,动态加边】

正题 题目大意 若干个城市一些城市有一定的电,有些城市需要一定的电。对于第iii个城市购买一个电需要iniin_iini​,送出电需要outiout_iouti​。当然城市之间也可以相互传输电。 对于一条电缆(x,y,a,b,l,r)(x,y,a,b,l,r)(x,y,a,b,l,r)表示x−>yx-&g…

Mybatis生成器插件扩展,生成findInSet方法

Mybatis生成器插件扩展,生成findInSet方法 public Criteria andNameFindInSet(String value) {addCriterionPattern("find_in_set($value, name)", value, "name");return (Criteria) this;}public Criteria andNamePattern(String pattern, S…

[认证授权] 6.Permission Based Access Control

在前面5篇博客中介绍了OAuth2和OIDC(OpenId Connect),其作用是授权和认证。那么当我们得到OAuth2的Access Token或者OIDC的Id Token之后,我们的资源服务如何来验证这些token是否有权限来执行对资源的某一项操作呢?比如…

P4841,jzoj3303-城市规划【NTT,多项式求逆,dp】

正题 题目链接:https://www.luogu.org/problemnew/show/P4841 题目大意 求nnn个点的简单联通无向图的个数。 解题思路 首先考虑n2n^2n2,我们设gig_igi​表示iii个点的简单无向图的个数,显然gi2n(n−1)2g_i2^{\frac{n(n-1)}{2}}gi​22n(n−1)​ 然后我…

微软发布PowerShell Core第一个版本:支持多平台开发

微软旗下的PowerShell团队正式宣布推出PowerShell Core 6.0,非常诡异的是这明明是Core的第一个版本,但是却用了一个6.0后缀的版本号。“这是我们对PowerShell做出的最大最重要的改变!”微软技术研究员兼PowerShell创始人Jeffrey Snover在Twit…