本章介绍了几种管理数据管道中数据集质量的技术。我们将介绍 Delta Live Tables(DLT)中的期望,这是一种在将数据合并到下游表之前,对到达的数据强制执行某些数据质量约束的方法。稍后,我们还将探讨更高级的技术,例如将不良数据隔离以便人工干预。接下来,我们还将展示如何解耦约束,使其可以由组织中的非技术人员单独管理。到本章结束时,你应该对如何采取措施确保湖仓中数据集的数据完整性,并对不符合预期标准的数据采取适当的行动有一个清晰的了解。
在本章中,我们将涵盖以下主题:
- 在 Delta Lake 中定义数据约束
- 使用临时数据集验证数据处理
- 期望的简介
- 实践练习:编写你的第一个数据质量期望
- 对未通过期望的数据采取行动
- 应用多个数据质量期望
- 解耦期望与 DLT 管道
- 实践练习------隔离低质量数据以进行修正
技术要求
为了跟随本章的内容,建议拥有 Databricks 工作区权限,以创建一个通用集群和一个使用集群策略的 DLT 管道。还建议拥有 Unity Catalog 权限,以创建和使用目录、架构和表。所有代码示例可以从本章的 GitHub 仓库中下载,地址为:github.com/PacktPublis...。我们将使用 NYC 黄出租车数据集,该数据集可以在 Databricks 文件系统中的 /databricks-datasets/nyctaxi/tripdata/yellow
路径找到。本章将创建并运行多个新的笔记本和 DLT 管道,使用的是高级产品版本。因此,预计这些管道将消耗大约 10-20 个 Databricks 单位(DBUs)。
在 Delta Lake 中定义数据约束
数据约束是定义传入数据必须满足的条件,以便在插入 Delta 表之前进行验证的有效方法。约束是针对 Delta 表中的每一列定义的,并作为附加的表元数据存储。
在 Databricks 数据智能平台中,有四种不同类型的约束:
- NOT NULL:确保表中特定列的数据不能为空。NOT NULL 约束最早在 Apache Spark 的 StructField 类定义中引入。
- CHECK:一个布尔表达式,必须在每一行插入前评估为 True。检查约束允许数据工程师强制执行复杂的验证逻辑,确保特定列满足条件。
- PRIMARY KEY:在表中的所有行之间为特定列建立唯一性。PRIMARY KEY 约束是一种特殊类型的约束,因为它纯粹是用于信息传递,不会强制执行对传入数据的约束。正如我们在下面的示例中看到的,NOT NULL 约束必须与 PRIMARY KEY 约束一起使用。
- FOREIGN KEY:在特定列和另一个表之间建立关系。与 PRIMARY KEY 约束类似,FOREIGN KEY 约束也仅用于信息传递。
此外,只有 NOT NULL 和 CHECK 约束会在传入数据上强制执行。
约束类型 | 强制执行 | 仅供参考 |
---|---|---|
NOT NULL | ✔️ | ✖️ |
CHECK | ✔️ | ✖️ |
PRIMARY KEY | ✖️ | ✔️ |
FOREIGN KEY | ✖️ | ✔️ |
表 3.1 -- 数据质量约束可以在 Databricks 数据智能平台上被强制执行或仅供参考
重要提示
PRIMARY KEY 约束和 FOREIGN KEY 约束要求 Delta 表存储在 Unity Catalog 中,否则将抛出运行时错误。
让我们看看如何使用约束在湖仓中的两个 Delta 表之间定义层次关系。首先,在 Databricks 笔记本中创建一个基于 SQL 的新笔记本。我们首先定义一个子表,该表包含关于出租车司机的数据,称为 drivers
,并在 driver_id
列上定义主键。将以下代码片段添加到新笔记本单元:
sql
%sql
CREATE CATALOG IF NOT EXISTS yellow_taxi_catalog;
CREATE SCHEMA IF NOT EXISTS yellow_taxi_catalog.yellow_taxi;
CREATE TABLE yellow_taxi_catalog.yellow_taxi.drivers(
driver_id INTEGER NOT NULL,
first_name STRING,
last_name STRING,
CONSTRAINT drivers_pk PRIMARY KEY(driver_id));
接下来,我们定义一个父表 rides
,在 ride_id
列上定义主键,并有一个外键引用 drivers
表。将以下代码片段添加到第一个笔记本单元下方:
sql
%sql
CREATE TABLE yellow_taxi_catalog.yellow_taxi.rides(
ride_id INTEGER NOT NULL,
driver_id INTEGER,
passenger_count INTEGER,
total_amount DOUBLE,
CONSTRAINT rides_pk PRIMARY KEY (ride_id),
CONSTRAINT drivers_fk FOREIGN KEY (driver_id)
REFERENCES yellow_taxi_catalog.yellow_taxi.drivers);
将新创建的笔记本附加到一个通用集群,并执行笔记本单元以创建父表和子表。最后,让我们导航到 Catalog Explorer 中的新定义表,并直接从 Databricks 数据智能平台生成实体关系图(ERD)。从我们的 Databricks 工作区,点击左侧边栏的 Catalog Explorer。导航到 Unity Catalog 中的 yellow_taxi_catalog
目录,如前面的示例所示。点击定义的架构,最后点击父表。一个侧边窗格将展开,显示有关我们 Delta 表的元数据。点击名为"View Relationships"的按钮以查看 ERD。
如前所述,主键和外键约束纯粹是信息性的,并未强制执行对传入数据的约束。相反,建议实施额外的保护措施,以确保 Delta 表中主键列的数据完整性。让我们来看几种有效的策略,可以用来保持在湖仓表中定义的主键列的完整性。
使用临时数据集验证数据处理
正如我们将在本节中看到的,创建视图是一种有效的方法,用于验证主键列的唯一性。此外,我们还可以在 Databricks 数据智能平台中定义警报,通知数据管理员潜在的数据质量问题,以便他们采取适当的措施来纠正数据完整性。
我们可以利用视图来验证主键列的唯一性。回顾一下我们在上一节中定义的 rides
和 drivers
表。在这个示例中,我们将定义一个视图,用于验证 rides
Delta 表中的主键列的唯一性。首先,在 Databricks 中创建一个新查询,返回到工作区并右键点击打开对话框。选择 New | Query 打开一个新的查询编辑器。接下来,将查询重命名为有意义的名称,例如 rides_pk_validation_vw
。最后,将以下查询文本添加到打开的查询中,并点击 Run 按钮以验证查询是否按预期运行:
sql
CREATE VIEW yellow_taxi_catalog.yellow_taxi.rides_pk_validation_vw AS
SELECT *
FROM (
SELECT count(*) AS num_occurrences
FROM yellow_taxi_catalog.yellow_taxi.rides
GROUP BY ride_id
) WHERE num_occurrences > 1
事实证明,主键的唯一性对于黄色出租车公司的下游报告至关重要。让我们在 Databricks 数据智能平台中创建一个新的警报,以提醒我们的数据管理员可能的数据损坏问题,这样他们就可以在插入重复的主键时采取适当的措施。
首先,让我们创建一个查询,将由警报执行。从侧边栏中点击 Queries 按钮,点击 Create query 按钮,这将带我们进入 Databricks 数据智能平台中的查询编辑器。将查询重命名为有意义的名称,例如 Rides Primary Key Uniqueness
。将以下 SQL 文本输入为查询主体,点击 Save 按钮,并选择一个工作区文件夹来保存查询。点击 Run 按钮,确保查询成功运行:
sql
SELECT count(*) AS num_invalid_pks
FROM yellow_taxi_catalog.yellow_taxi.rides_pk_validation_vw;
接下来,从侧边栏中点击 Alerts 按钮,进入 Alerts UI。然后,点击 Create alert 按钮开始创建新的警报,在 Alert name 文本框中输入描述性名称,例如 Invalid Primary Key on Rides Table
。在 Query 下拉菜单中选择我们刚创建的查询。勾选 Send notification 复选框,并通过点击 Create alert 按钮接受默认设置。在实际场景中,这可能是一个用于接收警报的电子邮件链,或者其他常见的通知目的地,如 Slack 或 Microsoft Teams。
这个示例在实际数据管道中非常实用。然而,视图需要每次运行管道时计算最新的表状态,还需要配置和维护通知警报的开销。这需要大量配置,这在我们向管道中添加更多表时无法扩展。那么,是否有更简单的方法将数据质量声明作为 DLT 管道声明的一部分呢?
期望简介
期望是与数据集定义一起在 DLT 管道中定义的数据质量规则。数据质量规则是一个布尔表达式,应用于通过特定数据集定义的每一条记录。该表达式必须评估为 True,才能将该记录标记为通过,否则该记录将被标记为失败,表示该记录未通过数据质量验证。
此外,DLT 管道将记录数据管道中每一行处理的 数据质量指标。例如,DLT 会记录通过数据质量验证的记录数量,以及未通过验证的记录数量。
期望的组成
每个期望由三个主要组件组成:描述、布尔表达式和采取的行动。
期望是使用 DLT 函数装饰器声明的。函数装饰器指定当特定约束或一组约束评估为 False 时,应该采取的行动。此外,函数装饰器接受两个输入参数,一个简短的描述,用于描述数据质量约束,以及一个布尔表达式,必须评估为 True 才能将某一行标记为通过验证。
实战练习------编写你的第一个数据质量期望
为了熟悉 DLT 语法,让我们通过编写一个现实世界的示例来练习,目标是为纽约市的 Yellow Taxi Corporation 编写一个数据管道。我们将编写一个简单的数据管道,强制应用一个数据质量约束,该约束适用于我们传入的 NYC 出租车数据,并在出现不符合数据质量规范的记录时发出警告。在这个场景中,我们希望确保传入的行程数据中没有任何负总金额的行程,因为出租车司机不可能欠乘客任何钱。
生成出租车行程数据
首先,登录到你的 Databricks 工作区。在本练习中,你需要使用随章节提供的 NYC Yellow Taxi 行程数据生成器,可以从本章的 GitHub 仓库下载。你可以将数据生成器笔记本导入到 Databricks 工作区,或创建一个新的 Python 笔记本,并使用以下代码片段。
首先,我们需要下载 dbldatagen
Python 库,它将帮助我们随机生成新的出租车行程数据。将以下代码片段添加到你的笔记本中,使用 %pip
魔法命令下载该库:
ini
%pip install dbldatagen==0.4.0
库安装完成后,让我们定义一个 Python 函数,根据我们的架构生成新的出租车行程数据。我们将为典型的出租车行程细节指定列,包括乘客人数、费用金额、行程距离等:
python
def generate_taxi_trip_data():
"""生成随机出租车行程数据"""
import dbldatagen as dg
from pyspark.sql.types import (
IntegerType, StringType, FloatType, DateType
)
ds = (
dg.DataGenerator(spark, name="random_taxi_trip_dataset",
rows=100000, partitions=8)
.withColumn("trip_id", IntegerType(),
minValue=1000000, maxValue=2000000)
.withColumn("taxi_number", IntegerType(),
uniqueValues=10000, random=True)
.withColumn("passenger_count", IntegerType(),
minValue=1, maxValue=4)
.withColumn("trip_amount", FloatType(), minValue=-100.0,
maxValue=1000.0, random=True)
.withColumn("trip_distance", FloatType(),
minValue=0.1, maxValue=1000.0)
.withColumn("trip_date", DateType(),
uniqueValues=300, random=True))
return ds.build()
现在我们已经定义了一个生成新行程数据的方式,我们需要定义一个位置来存储这些新数据,以便它可以被 DLT 管道处理。在新的笔记本单元中,让我们在 Databricks 文件系统(DBFS)上创建一个空目录,用于存储我们的行程数据:
arduino
dbutils.fs.mkdirs("/tmp/chp_03/taxi_data")
最后,我们需要一个方法将所有内容结合起来。在新的笔记本单元中,添加以下 for
循环,它将调用 generate_taxi_trip_data
函数并将数据写入 DBFS 位置:
ini
import random
max_num_files = 100
for i in range(int(max_num_files)):
df = generate_taxi_trip_data()
file_name = f"/tmp/chp_03/taxi_data/taxi_data_{random.randint(1, 1000000)}.json"
df.write.mode("append").json(file_name)
接下来,创建一个通用集群来执行行程数据生成器笔记本。一旦创建了通用集群,导航到新的笔记本并点击 Databricks 数据智能平台顶部导航栏中的集群下拉菜单。选择你创建的集群名称,并选择 Attach 将行程数据生成器笔记本附加到集群,并执行所有单元。出租车行程数据生成器将把几个包含随机生成的行程数据的 JSON 文件追加到 DBFS 位置。
创建新的 DLT 管道定义
现在我们已经生成了新数据,接下来让我们为我们的 DLT 管道定义创建另一个新笔记本。导航到侧边栏中的工作区选项卡,进入你的用户主目录,通过右键点击并选择 Add Notebook 创建一个新的笔记本。
为新的笔记本命名一个有意义的名称,例如 "Chapter 3 -- Enforcing Data Quality"。首先,导入 DLT Python 模块和 PySpark 函数:
javascript
import dlt
from pyspark.sql.functions import *
接下来,让我们定义一个 bronze 表 yellow_taxi_raw
,该表将摄取我们的出租车行程数据,这些数据已通过出租车行程数据生成器写入 DBFS 位置:
less
@dlt.table(
comment="The randomly generated taxi trip dataset"
)
def yellow_taxi_raw():
path = "/tmp/chp_03/taxi_data"
schema = "trip_id INT, taxi_number INT, passenger_count INT, trip_amount FLOAT, trip_distance FLOAT, trip_date DATE"
return (spark.readStream
.schema(schema)
.format("json")
.load(path))
对于我们数据管道的下一层,我们组织中的利益相关者要求我们提供一种方式,让他们的业务能够实时报告我们传入的行程数据的财务分析。因此,让我们添加一个 silver 表,该表将转换传入的行程数据流,计算我们出租车公司 Yellow Taxi Corporation 的预期利润和损失。在这个示例中,我们将使用乘客支付的总金额,并开始计算这些钱如何分配到公司的不同部分,以及潜在的利润。
让我们定义我们的 silver 表定义 trip_data_financials
。表定义与任何普通的流表定义类似。我们首先定义一个返回流表的 Python 函数。接下来,我们使用 DLT 函数注解声明这个函数为一个流表,带有一个可选的名称 trip_data_financials
,并附上描述性文本的注释。创建一个新的笔记本单元,添加以下 DLT 数据集定义以定义 silver 表:
less
@dlt.table(name="trip_data_financials",
comment="Financial information from incoming taxi trips.")
@dlt.expect("valid_total_amount", "trip_amount > 0.0")
def trip_data_financials():
return (dlt.readStream("yellow_taxi_raw")
.withColumn("driver_payment",
expr("trip_amount * 0.40"))
.withColumn("vehicle_maintenance_fee",
expr("trip_amount * 0.05"))
.withColumn("adminstrative_fee",
expr("trip_amount * 0.1"))
.withColumn("potential_profits",
expr("trip_amount * 0.45")))
在我们的 silver 表声明中,你可能注意到一个新的函数装饰器,用于强制执行数据质量约束。在这个例子中,我们希望确保我们行程数据中报告的总金额大于零。
当我们的数据管道触发运行并更新 bronze 和 silver 数据集时,DLT 系统将检查每一行并评估数据质量约束的布尔表达式是否对该行评估为 True:
kotlin
@dlt.expect("valid_total_amount", "trip_amount > 0.0")
在函数定义的主体部分,我们使用 PySpark 内置的 withColumn()
和 expr()
函数,向我们 bronze 表的输出中添加了四个新列------driver_payment
、vehicle_maintenance_fee
、adminstrative_fee
和 potential_profits
。这些列是通过取原始 trip_amount
列的百分比来计算的。从商业角度来看,我们将从乘客收取的总金额拆分成司机的付款、公司运营所需的费用和公司潜在的利润。
在接下来的部分,我们将探讨 DLT 系统在期望的布尔表达式评估为 False 时将采取的不同类型的行动。默认情况下,DLT 系统将仅记录该行未通过布尔表达式的情况,并将数据质量指标记录在系统日志中。在我们的 silver 表声明中,假设默认行为是记录一个警告消息。
运行数据管道
让我们从笔记本中的数据集声明创建一个新的数据管道。执行笔记本单元,确保没有语法错误。接下来,Databricks 数据智能平台将提示你创建一个新的数据管道。点击 Create pipeline 按钮以创建一个新的 DLT 数据管道。接下来,在 Destination 设置下,选择一个目录和 Unity Catalog 中的架构,用于存储管道数据集。在 Compute 设置下,将 Min workers 设置为 1,Max workers 设置为 2。通过点击 Create 按钮接受默认设置。最后,点击 Start 按钮以执行数据管道。你将进入数据流图的可视化表示。
在后台,DLT 系统将首先创建并初始化一个新的 Databricks 集群,并开始解析我们笔记本中的数据集定义,生成数据流图。正如你所看到的,DLT 系统将从我们的 DBFS 位置摄取原始行程数据文件到流表 yellow_taxi_raw
。接下来,系统会检测到我们 silver 表 trip_data_financials
的依赖关系,并立即开始计算 silver 表中的四个附加列。在这个过程中,我们的数据质量约束会实时地评估传入的数据。
让我们查看实时的数据质量。点击 silver 表,DLT UI 会在右侧展开一个窗格,概述该 silver 表。点击 Data quality 标签页,查看数据质量指标。注意,随着数据处理的进行,图表正在实时更新。在所有已处理的数据中,你会注意到大约 10% 的数据未通过 valid_total_amount
期望------这是预期中的情况。数据生成器笔记本会故意发布一些具有负总金额的记录到我们的云存储位置。我们可以轻松地看到有多少数据通过了我们定义的数据质量标准,多少数据没有通过。
恭喜!你已经编写了第一个 Delta Live Tables 数据质量约束
到现在为止,你应该能清楚地看到 DLT 框架是多么简单又强大。只需几行代码,我们就能对传入数据强制执行数据质量约束,并实时监控数据质量。这为数据工程团队提供了更多对数据管道的控制。
在下一节中,我们将看到数据工程团队如何利用 DLT 期望在数据质量问题导致潜在数据损坏之前做出反应。
处理失败的期望
当某一记录违反了在 DLT 数据集上定义的数据约束时,DLT 可以采取三种不同的行动:
- Warn(警告) :当 DLT 遇到表达式违规时,该记录将作为指标记录,并继续写入下游目标数据集。
- Drop(丢弃) :当 DLT 遇到表达式违规时,该记录将作为指标记录,并被阻止进入下游目标数据集。
- Fail(失败) :当 DLT 遇到表达式违规时,管道更新将完全失败,直到数据工程团队成员可以调查并修正数据违规或可能的数据损坏。
你应根据具体用例选择其中一种行动,并决定如何处理不符合数据质量规则的数据。例如,有时数据可能不符合定义的数据质量约束,但记录违规行并监控数据质量可能满足特定用例的要求。另一方面,也可能存在某些数据质量约束必须得到满足,否则传入数据将破坏下游流程。在这种情况下,更具攻击性的行动,如使数据管道运行失败并回滚事务,才是适当的行为。在这两种情况下,Delta Live Tables 框架为数据工程团队提供了完全的控制权,以决定违规行的命运,并定义系统应该如何反应。
实战示例------因数据质量差而导致管道运行失败
在某些情况下,你可能希望立即停止数据管道更新的执行,以便进行干预并修正数据。例如,在这种情况下,DLT 期望提供了使用 @dlt.expect_or_fail()
函数装饰器立即使数据管道运行失败的能力。
如果操作是表更新,事务将立即回滚,以防止污染不良数据。此外,DLT 将跟踪关于处理记录的附加元数据,以便数据工程团队能够准确定位是数据集中的哪条记录导致了失败。
让我们看看如何更新之前的 Yellow Taxi Corporation 数据管道示例。在这种情况下,负总金额会破坏下游的财务报告。因此,与其简单地记录违反期望的行,我们希望使管道运行失败,以便我们的数据工程团队可以调查数据中的潜在问题,并采取适当的措施,如手动修正数据。
在 Delta Live Tables 框架中,调整数据管道的行为就像更新 silver 表定义的函数装饰器一样简单。让我们将期望更新为 expect_or_fail
行动:
less
@dlt.expect_or_fail("valid_total_amount", "trip_amount > 0.0")
trip_data_financials
silver 表的完整数据集定义应如下所示:
less
@dlt.table(
name="trip_data_financials",
comment="Financial information from completed taxi trips."
)
@dlt.expect_or_fail("valid_total_amount", "trip_amount > 0.0")
def trip_data_financials():
return (
dlt.readStream("yellow_taxi_raw")
.withColumn("driver_payment",
expr("trip_amount*0.40"))
.withColumn("vehicle_maintenance_fee",
expr("trip_amount*0.05"))
.withColumn("adminstrative_fee",
expr("trip_amount*0.1"))
.withColumn("potential_profits",
expr("trip_amount*0.45")))
接下来,让我们重新运行行程数据生成器,将额外的文件追加到 Databricks 文件系统中的原始落地区域。一旦行程数据生成器完成,返回到之前创建的 Yellow Taxi Corporation 数据管道,点击 Start 按钮以触发管道的另一次执行。在本章的示例中,行程数据生成器将随机生成带有负总金额的行程数据。
你应该会观察到,这次数据管道运行因错误状态而失败。
展开失败消息后,你可以看到管道失败的原因是违反了期望约束。
应用多个数据质量期望
有时,数据集作者可能希望对每一行数据应用多个业务规则或数据质量约束。在这种情况下,DLT 提供了一组特殊的函数装饰器,用于指定多个数据质量约束定义。
@dlt.expect_all()
函数装饰器可用于将多个数据质量约束结合起来,适用于特定数据集。同样,expect_all_or_drop()
可以指定当传入数据未能满足数据质量约束集合中的所有标准时,应该将数据丢弃,不允许进入目标表。最后,expect_all_or_fail()
如果传入数据没有满足数据质量约束集合中的任何标准,则会使数据管道的运行失败。
让我们看看当某些无效的出租车行程数据条目不符合验证标准时,如何将其从管道中的下游数据集中丢弃:
python
assertions = {
"total_amount_constraint": "trip_amount > 0.0",
"passenger_count": "passenger_count >= 1"
}
@dlt.table(
name="yellow_taxi_validated",
comment="A dataset containing trip data that has been validated.")
@dlt.expect_all_or_drop(assertions)
def yellow_taxi_validated():
return (
dlt.readStream("yellow_taxi_raw")
.withColumn("nyc_congestion_tax",
expr("trip_amount * 0.05")))
在上面的示例中,我们使用期望函数装饰器定义了一组数据约束,并将它们共同应用于传入的数据。假设丢失一些出租车行程数据记录不会对下游流程造成威胁。因此,我们决定丢弃那些未通过验证步骤的记录。通过仅增加几行配置,我们的数据管道就可以对传入数据强制执行数据质量约束,并自动对不符合定义标准的数据作出反应。
虽然我们仅在 DLT 数据管道的上下文中查看了数据,但接下来我们将看到 DLT 框架如何在多个数据系统中验证数据。
将期望与 DLT 管道解耦
到目前为止,我们只在表定义中定义了数据质量约束。然而,可能会有一些场景,您希望将数据质量约束与数据管道定义解耦,这样数据工程团队和数据分析团队可以分别工作。这在非技术人员确定数据质量标准时尤其有用。此外,这种设计还提供了更多的灵活性,使得随着业务变化,可以更轻松地维护和更改业务规则。例如,一个现实世界的例子是验证随时间变化的季节性折扣码。
假设我们有一组非技术的业务分析师,他们希望使用浏览器窗口中的 Web 门户等 UI 来与数据质量约束进行交互。在这种情况下,我们可以将数据质量约束加载并保存到一个独立的 Delta 表中,然后在运行时动态加载这些数据质量约束。
让我们首先定义一个数据质量规则表。我们将引入三列:一列用于规则名称、一列定义数据质量规则表达式、以及一列标识数据集名称------这些都是使用 DLT 创建期望所需的内容:
sql
%sql
CREATE TABLE IF NOT EXISTS <catalog_name>.<schema_name>.data_quality_rules
(rule_name STRING, rule_expression STRING, dataset_name STRING)
USING DELTA
让我们回顾一下之前使用 Python 字典指定多个期望的例子。在那个例子中,我们定义了一个叫 assertions
的字典数据结构。在这个例子中,让我们将其转换为表格格式,将条目插入到我们的 Delta 表中。在新的笔记本单元中添加以下 SQL 语句:
sql
%sql
INSERT INTO
data_quality_rules
VALUES
(
'valid_total_amount',
'trip_amount > 0.0',
'yellow_taxi_raw'
),(
'valid_passenger_count',
'passenger_count > 0',
'yellow_taxi_raw'
);
接下来,在数据管道笔记本中,我们可以创建一个辅助函数,直接从我们的数据质量规则表读取并将每一行转换为 DLT 期望可以解释的格式:
python
def compile_data_quality_rules(rules_table_name, dataset_name):
"""一个辅助函数,读取 data_quality_rules 表并转换为 DLT 期望可以解释的格式。"""
rules = spark.sql(f"""SELECT * FROM {rules_table_name} WHERE dataset_name='{dataset_name}'""").collect()
rules_dict = {}
# 如果没有找到规则,则直接跳过
if len(rules) == 0:
raise Exception(f"No rules found for dataset '{dataset_name}'")
for rule in rules:
rules_dict[rule.rule_name] = rule.rule_expression
return rules_dict
现在我们有了一个 Delta 表,非技术的业务分析师可以使用独立的 UI 来更新它,同时我们也有一个辅助函数,可以从 Delta 表中读取并将条目转换为 DLT 期望可以解释的格式。让我们看看这些元素如何结合在一起,在我们的管道中动态加载数据质量要求来创建新的数据集:
python
import dlt
from pyspark.sql.functions import *
RULES_TABLE = "<catalog_name>.<schema_name>.data_quality_rules"
DATASET_NAME = "yellow_taxi_raw"
@dlt.table(
comment="Randomly generated taxi trip data."
)
def yellow_taxi_raw():
path = "/tmp/chp_03/taxi_data"
schema = "trip_id INT, taxi_number INT, passenger_count INT, trip_amount FLOAT, trip_distance FLOAT, trip_date DATE"
return (spark.readStream
.schema(schema)
.format("json")
.load(path))
@dlt.table(
name="yellow_taxi_validated",
comment="A dataset containing trip data that has been validated.")
@dlt.expect_all(compile_data_quality_rules(RULES_TABLE, DATASET_NAME))
def yellow_taxi_validated():
return (
dlt.readStream("yellow_taxi_raw")
.withColumn("nyc_congestion_tax",
expr("trip_amount * 0.05"))
)
这种设计模式提供了灵活性,将数据质量规则与数据管道定义分开维护,以便非技术人员来确定数据质量标准。但如果我们有一组技术人员希望继续参与数据管道中的数据质量管理呢?而且,假如这组人员需要在数据质量差时收到通知,以便他们可以进行干预,甚至手动修正数据,使得下游流程能够正常运行?接下来,让我们看看如何在下一个实战练习中实现这样的恢复过程。
实战练习------将不良数据隔离以便修正
在这个例子中,我们将构建一个条件数据流,用于处理不符合我们数据质量要求的数据。这将允许我们隔离违反数据质量规则的数据,以便稍后采取适当的行动,甚至对违反数据质量约束的数据进行报告。
我们将使用相同的 Yellow Taxi Corporation 示例来说明构建数据隔离区的概念。我们从一个 bronze 表开始,该表摄取由行程数据生成器写入 DBFS 位置的原始 JSON 数据:
python
%py
import dlt
from pyspark.sql.functions import *
@dlt.table(
name="yellow_taxi_raw",
comment="The randomly generated taxi trip dataset"
)
def yellow_taxi_raw():
path = "/tmp/chp_03/taxi_data"
schema = "trip_id INT, taxi_number INT, passenger_count INT, trip_amount FLOAT, trip_distance FLOAT, trip_date DATE"
return (spark.readStream
.schema(schema)
.format("json")
.load(path))
接下来,我们开始为传入的数据定义一些数据质量规则。确保发布到我们的 DBFS 位置的行程数据是合理的。我们将确保总票价大于 0,并且行程至少有 1 个乘客,否则,我们将把行程数据隔离以供进一步审查:
makefile
data_quality_rules = {
"total_amount_assertion": "trip_amount > 0.0",
"passenger_count": "passenger_count >= 1"
}
现在,让我们通过创建另一个数据集并添加一个计算列 is_valid
,将这两条数据质量规则应用到传入数据。这个列将包含每一行的数据显示质量规则评估结果:
less
@dlt.table(
name="yellow_taxi_validated",
comment="Validation table that applies data quality rules to the incoming data"
)
def yellow_taxi_validated():
return (
dlt.readStream("yellow_taxi_raw")
.withColumn("is_valid",
when(expr(" AND ".join(data_quality_rules.values())),
lit(True)).otherwise(lit(False)))
)
最后,我们可以使用计算列 is_valid
来将流表拆分成两个数据流------一个数据流用于所有通过数据质量断言的传入数据,另一个数据流用于那些没有通过数据质量断言的传入数据。
让我们在数据管道中定义一个隔离表,它将根据评估的数据质量规则路由数据:
less
@dlt.table(
name="yellow_taxi_quarantine",
comment="A quarantine table for incoming data that has not met the validation criteria"
)
def yellow_taxi_quarantine():
return (
dlt.readStream("yellow_taxi_validated")
.where(expr("is_valid == False"))
)
@dlt.table(
name="yellow_taxi_passing"
)
def yellow_taxi_passing():
return (
dlt.readStream("yellow_taxi_validated")
.where(expr("is_valid == True"))
)
最后,使用新笔记本作为源创建一个新的 DLT 管道。为管道提供一个有意义的名称,例如 Chapter 3 Quarantining Invalid Data 。选择 Core 作为产品版本,Triggered 作为执行模式。接下来,选择 Unity Catalog 中的目标目录和架构,用于存储管道数据集。接受剩余的默认值,点击 Create 按钮创建新 DLT 管道。最后,点击 Start 按钮触发新管道的执行运行。注意,数据被拆分到两个下游表中------一个表包含通过数据质量规则的行,另一个隔离表包含未通过数据质量规则的行。
通过实现一个隔离表,我们可以报告实时指标,以便组织中的利益相关者能够随时了解传入数据的质量。此外,我们湖仓的数据管理员可以审查未通过验证逻辑的数据,并采取适当的措施,如手动修正无效数据。
总结
在本章中,我们讨论了许多与湖仓中数据质量相关的话题。我们学习了如何使用 NOT NULL 和 CHECK 约束来强制执行表的完整性。我们还使用 PRIMARY KEY 和 FOREIGN KEY 约束定义了表之间的关系。接下来,我们看到了如何通过使用视图验证表中的数据来强制执行主键唯一性。我们还看到了,当传入行违反数据质量约束时,如何轻松地更新数据管道的行为,使数据工程团队能够对可能因数据质量差而导致下游流程中断的情况做出反应。最后,我们看到了一个实际的例子,展示了如何使用期望在我们的管道中创建条件数据流,使数据管理员能够隔离并修正不符合预期数据质量的数据。
在下一章中,我们将深入探讨生产环境中数据管道的维护的更高级话题。我们将看到如何调整数据管道的许多不同方面,以便扩展到大量数据并满足实时流处理的需求,如高吞吐量和低延迟。