Delta Lake权威指南——深入 Delta Lake 生态系统

277 阅读16分钟

在过去的几章中,我们已经从 Spark 生态系统的角度探讨了 Delta Lake。然而,Delta 协议不仅在底层表格式之间提供丰富的互操作性,而且在计算环境内也具备强大的互操作性。这为我们利用单一的表数据源推动湖仓应用开辟了广阔的可能性。是时候跳出框框,了解一下连接器生态系统了。

连接器生态系统是一个不断扩展的框架、服务和社区驱动的集成集合,使得 Delta 能够从几乎任何地方被利用。Delta 的互操作性承诺使我们能够充分利用不断发展的开源社区所提供的辛勤工作和努力,同时不需要牺牲我们在 Spark 生态系统之外的技术积累。

在本章中,我们将探索一些更流行的 Delta 连接器,并学习如何在传统的 Spark 生态系统之外管理基于 Delta 的数据应用。对于那些没有深入接触过 Apache Spark 的读者来说,你们是幸运的,因为本章将详细介绍如何在没有 Apache Spark 的情况下使用 Delta Lake,以及连接器生态系统的工作原理。

我们将介绍以下集成:

  1. Flink DataStream 连接器
  2. Kafka Delta Ingest
  3. Trino 连接器

除了本章讨论的四个核心连接器,当前还支持 Apache Pulsar、ClickHouse、FINOS Legend、Hopsworks、Delta Rust、Presto、StarRocks 以及一般 SQL 导入到 Delta 的功能。

那么,连接器是什么呢?我们接下来将详细了解。

连接器

我们作为人类,不喜欢为自己设限。有些人更富冒险精神,喜欢思考未来的无限可能;而另一些人则倾向于采取更为狭窄、直白的生活方式。无论我们各自的态度如何,我们都因追求冒险、寻找新奇以及希望为自己做决定而紧密联系在一起。没有什么比被束缚住、无法逃脱更糟糕了。从数据实践者的角度来看,知道我们今天依赖的技术可以在明天继续使用,而无需担心合同重新谈判,这是一件令人欣慰的事!

尽管 Delta Lake 不是一个人,但开源社区已经回应了广泛社区的各种需求,健康的生态系统已经兴起,确保没有人需要直接依赖 Apache Spark 生态系统、JVM,甚至是传统的数据编程语言(如 Python、Scala 和 Java)。

连接器生态系统的使命是确保与 Delta 协议的无缝互操作性。然而,随着时间的推移,当前(delta < 3.0)连接器生态系统的碎片化导致了多个独立实现 Delta 协议,并使当前的连接器之间出现了差异。为了简化 Delta 生态系统未来的支持,Delta Kernel 被引入,以提供一个共同的接口和预期,从而简化 Delta 生态系统内的真正互操作性。

注意
Kernel 提供了一套无缝的读写 API,确保操作的正确性,并为连接器 API 实现提供表达自由。这意味着所有连接器之间的行为将利用相同的操作集,具有相同的输入和输出,同时确保每个连接器能够快速实现新特性,而无需漫长的开发周期或对底层 Delta 协议的处理差异。Delta Kernel 在第 1 章中介绍。

有大量的连接器和集成支持与 Delta 表格格式和协议的互操作性,无论我们从何处触发操作。互操作性和统一性是 Delta 项目的核心原则之一,并推动了 UniForm(与 Delta 3.0 一起引入)的推进,它为 Delta、Iceberg 和 Hudi 提供了跨表支持。

接下来的部分,我们将探讨最流行的连接器,包括 Apache Flink、Trino 和 Kafka Delta Ingest。学习如何在你最喜欢的框架中使用 Delta,只有几步之遥。

Apache Flink

Apache Flink 是“一种框架和分布式处理引擎,用于对无界和有界数据流进行有状态计算……[它]设计用于运行在所有常见的集群环境中,[并且]以内存速度和任意规模执行计算。”换句话说,Flink 可以大规模扩展,在分布式方式下处理不断增加的负载时仍能高效运行,并且在流处理时,即使在运行时发生故障或中断情况下(如果指定了 CheckpointingMode),也能遵循“精确一次”语义。

提示
如果你之前没有使用过 Flink,但想要学习,可以参考 Fabian Hueske 和 Vasiliki Kalavri 编写的《Stream Processing with Apache Flink》(O'Reilly),这本书能让你迅速掌握基础知识。

从这里开始,我们假设读者已经具备以下条件之一:

  1. 足够了解 Flink,能够编译一个应用程序。
  2. 愿意边学习边跟随内容操作。

接下来,让我们看看如何将 delta-flink 连接器添加到 Flink 应用中。


Flink DataStream 连接器

Flink/Delta 连接器构建于 Delta Standalone 库之上,为使用 Flink 原语(如 DataStream 和 Table API)读取和写入 Delta 表提供了无缝抽象。实际上,由于 Delta Lake 使用 Parquet 作为其通用数据格式,因此除了 Delta Standalone 库引入的功能外,操作 Delta 表并没有什么特别的要求。

Standalone 库提供了使用 DeltaLog 对象读取 Delta 表元数据的基本 Java API。这使我们能够读取某个表的完整当前版本,或从特定版本开始读取,或者根据提供的 ISO-8601 时间戳找到表的近似版本。在接下来的部分中,我们将学习如何使用 DeltaSourceDeltaSink,并覆盖 Standalone 库的基本功能。

提示
以下部分提到的完整 Java 应用程序位于本书 Git 仓库的 /ch04/flink/dldg-flink-delta-app/ 路径下。
对好奇的读者来说,该应用程序的单元测试展示了如何使用 Delta Standalone API。你可以在 /src/test/ 路径下找到这些测试。


安装连接器

一切都从连接器开始。通过 Maven、Gradle 或 sbt 将 delta-flink 连接器添加到数据应用中。以下示例展示了如何在 Maven 项目中包含 delta-flink 连接器依赖:

<dependency>
  <groupId>io.delta</groupId>
  <artifactId>delta-flink</artifactId>
  <version>${delta-connectors-version}</version>
</dependency>

警告
需要注意的是,Apache Flink 官方已经不再支持 Scala 编程语言。本章内容基于 Flink 1.17.1 编写,该版本正式停止了发布 Scala API。虽然你仍然可以使用 Scala 编写 Flink 应用,但 Java 和 Python 将是 Flink 2.0 及以后版本中唯一支持的语言。因此,本书中的所有示例以及 GitHub 仓库中的应用代码均使用 Java 编写。


连接器附带了用于读取和写入 Delta Lake 的类。读取操作由 DeltaSource API 处理,写入操作由 DeltaSink API 处理。我们将从 DeltaSource API 开始,然后介绍 DeltaSink API,最后讨论一个端到端的应用程序。

注意
delta-connectors-version 属性的值会随着新版本的发布而变化。为了简化流程,从 Delta 3.0 版本开始,所有支持的连接器都被正式纳入主 Delta 仓库中。

DeltaSource API

DeltaSource API 提供了静态构建器,可以轻松构建用于有界或无界(连续)数据流的源。两者的主要区别在于对源 Delta 表的有界(批处理)或无界(流式处理)操作。这类似于 Apache Spark 中的批处理或微批(无界)处理。尽管这两种处理模式的行为有所不同,但它们的配置参数差异非常小。我们将首先查看有界源,并在最后讨论无界源,因为后者有更多的配置选项。

有界模式

为了创建 DeltaSource 对象,我们将使用 DeltaSource 类的静态方法 forBoundedRowData。这个构建器需要传入 Delta 表的路径以及应用程序的 Hadoop 配置实例,如示例 4-1 所示。

示例 4-1:创建 DeltaSource 有界构建器

% Path sourceTable = new Path("s3://bucket/delta/table_name")
Configuration hadoopConf = new Configuration()
var builder: RowDataBoundedDeltaSourceBuilder = DeltaSource.forBoundedRowData(
  sourceTable,
  hadoopConf);

在示例 4-1 中返回的对象是一个构建器。通过该构建器的各种选项,我们可以指定如何从 Delta 表中读取数据,包括减慢读取速率、筛选读取的列集等。

构建器选项

以下选项可以直接应用于构建器:

  • columnNames (string ...)
    这个选项允许我们指定要读取的表的列名,忽略其他列。这个功能对于包含很多列的宽表非常有用,可以帮助减少那些最终不会使用的列对内存的压力:

    % builder.columnNames("event_time", "event_type", "brand", "price");
    builder.columnNames(Arrays.asList("event_time", "event_type", "brand", "price"));
    
  • startingVersion (long)
    这个选项允许我们指定从 Delta 表的某个特定事务版本开始读取(以数字 Long 的形式)。该选项与 startingTimestamp 选项是互斥的,因为它们都提供了一种方式来提供 Delta 表的游标(或事务起点):

    % builder.startingVersion(100L);
    
  • startingTimestamp (string)
    这个选项允许我们指定一个大致的时间戳,以 ISO-8601 字符串的形式,从该时间戳开始读取。此选项将触发 Delta 事务历史的扫描,查找在给定时间戳之后生成的表版本。如果整个表的时间都晚于提供的时间戳,则整个表将被读取:

    % builder.startingTimestamp("2023-09-10T09:55:00.001Z");
    

    时间戳字符串可以表示低精度的时间——例如,简单的日期 "2023-09-10"——或者像上例那样精确到毫秒。在这两种情况下,操作都会导致 Delta 表从表时间的特定点开始读取。

  • parquetBatchSize (int)
    该选项接受一个整数,控制每个内部批次中返回的行数,或者 Flink 引擎中生成的分片大小:

    % builder.option("parquetBatchSize", 5000);
    
生成有界源

完成向构建器提供选项后,我们通过调用 build 方法生成 DeltaSource 实例:

% final DeltaSource<RowData> source = builder.build();

构建好有界源后,我们就可以从 Delta Lake 表中读取批量数据了。但如果我们想要持续处理新到达的记录呢?在这种情况下,我们只需使用无界模式构建器!

无界模式

为了创建这种变体的 DeltaSource 对象,我们将使用 DeltaSource 类中的静态方法 forContinuousRowData。构建器如示例 4-2 所示,我们提供与 forBoundedRowData 构建器相同的基本参数,这使得从批处理切换到流式处理变得非常简单。

示例 4-2:创建 DeltaSource 无界构建器

% var builder = DeltaSource.forContinuousRowData(
    sourceTable,
    hadoopConf);

在示例 4-2 中返回的对象是 RowDataContinuousDeltaSourceBuilder 的实例,和有界模式一样,它使我们能够根据 startingVersionstartingTimestamp 控制 Delta 表中的初始读取位置,还提供了一些附加选项,用于控制 Flink 检查表中是否有新条目的频率。

构建器选项

以下选项可以直接应用于无界构建器;此外,有界构建器的所有选项(columnNamesstartingVersionparquetBatchSizestartingTimestamp)也适用于无界构建器:

  • updateCheckIntervalMillis (long)
    这个选项接受一个数字类型的 Long 值,表示检查 Delta 表更新的频率,默认值为 5000 毫秒:

    % builder.updateCheckIntervalMillis(60000L);
    

    如果我们知道读取的表只会周期性更新,那么可以通过使用此设置减少不必要的 I/O 操作。例如,如果我们知道新数据每分钟才会写入一次,那么我们可以设置每分钟检查一次,避免频繁检查。如果需要更快或更慢的处理速度,可以根据上游 Delta 表的行为修改该设置。

  • ignoreDeletes (boolean)
    设置此选项允许我们忽略已删除的行。如果我们的流式应用程序永远不需要知道过去的数据被删除了,那么我们可以忽略删除操作。如果我们在实时处理数据,并且将数据源表视为仅附加模式(append-only),那么我们只关心表的最新部分,安全地忽略数据过期后的尾部变化。

  • ignoreChanges (boolean)
    设置此选项允许我们忽略表的上游更改,包括删除的行和对物理表数据或逻辑表元数据的其他修改。除非表的模式被覆盖,否则我们可以继续处理数据,并忽略表结构的更改。

生成无界源

配置完构建器后,我们通过调用 build 方法生成 DeltaSource 实例:

% final DeltaSource<RowData> source = builder.build();

我们已经了解了如何构建 DeltaSource 对象,并且熟悉了连接器的配置选项,但表的模式或分区列是如何发现的呢?幸运的是,我们无需过多关注这些,因为这两项都是通过表的元数据自动发现的。

表模式发现

Flink 连接器使用 Delta 表的元数据来解析所有列及其类型。例如,如果我们在源定义中没有指定任何列,则会读取底层 Delta 表中的所有列。然而,如果我们使用 DeltaSource 构建器方法(columnNames)指定了一组列名,则只会读取该子集的列。在这两种情况下,DeltaSource 连接器将自动发现 Delta 表的列类型,并将其转换为相应的 Flink 类型。这一从内部 Delta 表数据(Parquet 行)到外部数据表示(Java 类型)的转换过程,提供了一种无缝的方式来处理我们的数据集。

使用 DeltaSource

在构建了 DeltaSource 对象(无界或有界)后,我们可以通过 StreamingExecutionEnvironment 实例将源添加到 DataStream 的流图中。

示例 4-3 创建了一个简单的执行环境实例,并通过 fromSource 添加了我们的流的源(DeltaSource)。在构建 StreamExecutionEnvironment 实例时,我们提供了一个 WatermarkStrategy。Flink 中的水印与 Spark Structured Streaming 中的水印概念类似:它们使得迟到的数据可以在特定时间内被处理,而不会被认为太晚而被丢弃(忽略)。

示例 4-3:为我们的 DeltaSource 创建 StreamExecutionEnvironment

% final StreamExecutionEnvironment env =
      StreamExecutionEnvironment.getExecutionEnvironment();
  env.setRuntimeMode(RuntimeExecutionMode.AUTOMATIC);
  env.enableCheckpointing(2000, CheckpointingMode.EXACTLY_ONCE);
  
  DeltaSource<RowData> source = ...
  env.fromSource(source, WatermarkStrategy.noWatermarks(), "delta table source")

现在,我们已经为 Flink 作业创建了一个支持 Delta 的实时数据源。我们可以选择添加额外的源,连接和转换数据,甚至使用 DeltaSink 将转换结果写回 Delta,或写入应用程序需要的其他地方。

接下来,我们将介绍如何使用 DeltaSink,并通过一个完整的端到端示例将各个部分连接起来。

DeltaSink API

DeltaSink API 提供了一个静态构建器,便于将数据写入 Delta Lake。与 DeltaSource API 一样,DeltaSink API 也提供了一个构建器类。构建器的创建过程如示例 4-4 所示。

示例 4-4:创建 DeltaSink 构建器

% Path deltaTable = new Path("s3://bucket/delta/table_name")
  Configuration hadoopConf = new Configuration()
  RowType rowType = …

  RowDataDeltaSinkBuilder sinkBuilder = DeltaSink.forRowData(
    sourceTable,
    hadoopConf,
    rowType);

到此为止,delta-flink 连接器的构建器模式应该已经很熟悉了。与创建此构建器的唯一不同之处在于需要添加 RowType 引用。

ROWTYPE

类似于 Spark 的 StructTypeRowType 存储了给定逻辑行内字段的逻辑类型信息。从更高的角度看,它可以被理解为简单的 DataFrame。它是一个抽象,使得处理动态数据变得更加简单。

实际上,如果我们能获取到 DataStream 中 DeltaSink 前的源或转换,我们可以通过一个简单的小技巧动态地提供 RowType。通过一些类型转换技巧,我们可以在 TypeInformation<T>RowData<T> 之间进行转换,如示例 4-5 所示。

示例 4-5:通过 TypeInformation 提取 RowType

% public RowType getRowType(TypeInformation<RowData> typeInfo) {
  InternalTypeInfo<RowData> sourceType = (InternalTypeInfo<RowData>) typeInfo;
  return (RowType) sourceType.toLogicalType();
}

getRowType 方法将提供的 typeInfo 对象转换为 InternalTypeInfo,并使用 toLogicalType 方法将其转换回 RowType

示例 4-6:从 DeltaSource 提取 RowType

% DeltaSource<RowData> source = …
                TypeInformation<RowData> typeInfo = source.getProducedType();
                RowType rowTypeForSink = getRowType(typeInfo);

如果我们有一个简单的流应用程序,我们可能已经顺利地运行了一段时间,而无需手动构建传统的 Java 对象(POJOs),或者与序列化和反序列化工具打交道;或者我们可能选择使用其他机制来创建数据对象,比如 Avro 或 Protocol Buffers。也可能我们从未需要处理传统数据库表以外的数据。不管是什么用例,处理列式数据意味着我们可以像 SQL 查询一样简单地读取我们需要的列。

考虑以下 SQL 语句:

% select name, age, country from users;

虽然我们可以使用 select * 读取表中的所有列,但更好的做法是只读取我们需要的列。这就是列式数据的优势。由于我们的数据应用程序很可能不需要所有的列,我们通过这种方式节省了计算周期和内存开销,同时在读取数据源时提供了一个干净的接口。

通过 Delta Lake 表动态读取和选择特定列的能力,意味着我们可以信任表的模式,这是普通数据湖中其他数据所无法提供的。虽然表模式可能随时间变化,但我们不需要为源表维护单独的 POJO。这看似不是什么大事,但移动部件越少,编写、发布和维护数据应用程序就越简单。我们只需要表达预期存在的列,这加速了我们创建灵活数据处理应用程序的能力,只要我们能信任从 Delta 表读取的数据具有向后兼容的模式演进。更多关于模式演进的内容,请参见第 5 章。

构建器选项

以下选项可以直接应用于构建器:

  • withPartitionColumns (string ...)
    此构建器选项接受一个字符串数组,表示列的子集。这些列必须在流中实际存在。
  • withMergeSchema (boolean)
    此构建器选项必须设置为 true,以启用自动模式演进。默认值为 false

精确一次写入保证

DeltaSink 并不会立即将数据写入 Delta 表。相反,行会追加到 flink.streaming.sink.filesystem.DeltaPendingFile 中,这与 Delta Lake 是不同的,因为这些文件提供了一种机制,将写入(增量)缓存在文件系统中,作为一系列累积的更改,直到可以一起提交。待处理文件会保持打开状态,直到满足检查点间隔(示例 4-7 展示了我们如何为 Flink 应用程序设置检查点间隔),待处理文件将被滚动,这是将缓冲的记录提交到 Delta 日志的时刻。我们通过启用检查点功能并提供间隔,来指定写入 Delta Lake 的频率。

示例 4-7:设置检查点间隔和模式

% StreamExecutionEnvironment
  .getExecutionEnvironment()
  .enableCheckpointing(2000, CheckpointingMode.EXACTLY_ONCE);

使用上述检查点配置,我们最多每 2 秒创建一个新的事务,此时 DeltaSink 将使用 Flink 应用程序的 appId 和与待处理文件关联的 checkpointId。这与使用 txnAppIdtxnVersion 实现幂等写入类似,未来可能会统一。

端到端示例

现在,我们将展示一个端到端的示例,使用 Flink DataStream API 从 Kafka 读取数据,并将其写入 Delta。应用程序的源代码和与 Docker 兼容的环境已提供在本书的 GitHub 仓库中的 /ch04/flink/ 路径下,包含初始化 ecomm.v1.clickstream Kafka 主题的步骤,写入(生成)记录供 Flink 应用程序消费,并最终将这些记录写入 Delta。运行应用程序后的结果可以在 图 4-1 中看到,该图展示了 Flink UI,并表示应用程序的最终状态。

image.png

让我们通过 示例 4-8 中的 KafkaSource 连接器和前面提到的 DeltaSink 来定义我们的 DataStream。

示例 4-8. 从 KafkaSource 到 DeltaSink 的 DataStream

public DataStreamSink<RowData> createDataStream(
    StreamExecutionEnvironment env) throws IOException {
    
    final KafkaSource<Ecommerce> source = this.getKafkaSource();
    final DeltaSink<RowData> sink =
       this.getDeltaSink(Ecommerce.ECOMMERCE_ROW_TYPE);

    final DataStreamSource<Ecommerce> stream = env
       .fromSource(source, WatermarkStrategy.noWatermarks(), "kafka-source");

    return stream
           .map((MapFunction<Ecommerce, RowData>) Ecommerce::convertToRowData)
           .setParallelism(1)
           .sinkTo(sink)
           .name("delta-sink")
           .setDescription("writes to Delta Lake")
           .setParallelism(1);
}

这个示例从 Kafka 中获取二进制数据,表示以 JSON 格式的电子商务交易。在后台,我们将 JSON 数据反序列化为电子商务行数据,然后将其从 JVM 对象转换为写入 Delta 表所需的内部 RowData 表示。接着,我们使用 DeltaSink 的实例将数据流写入 Delta Lake。

接下来,我们在 示例 4-9 中看到如何在添加了一些额外描述性元数据后调用 execute 来运行整个 DataStream。

示例 4-9. 运行端到端示例

public void run() throws Exception {
    StreamExecutionEnvironment env = this.getExecutionEnvironment();
    DataStreamSink<RowData> sink = createDataStream(env);
    sink
      .name("delta-sink")
      .setParallelism(NUM_SINKS)
      .setDescription("writes to Delta Lake");
        
    env.execute("kafka-to-delta-sink-job");
}

我们刚刚简单介绍了如何使用 Flink 连接器连接到 Delta Lake,现在是时候看看另一个连接器了。

注意: 要运行完整的端到端应用程序,只需按照本书 GitHub 仓库中 ch04/flink/README.md 文件中的逐步概述进行操作。

类似于我们使用 Flink 的端到端示例,接下来我们将探讨如何从 Kafka 中摄取相同的电子商务数据;不过,这次我们将使用基于 Rust 的 kafka-delta-ingest 库。

Kafka Delta Ingest

这个连接器的名字恰好概括了这个强大库的功能。它从 Kafka 主题读取一系列记录, optionally 转换每个记录(数据流)—例如,从原始字节到反序列化的 JSON 或 Avro 负载—然后将数据写入 Delta 表。幕后,通过最少的用户配置,帮助将连接器定制化以满足特定的用例。由于 kafka-delta-ingest 客户端的简易性,我们减少了数据工程生命周期中最关键阶段之一——通过 Delta Lake 将初始数据摄取到湖仓中的工作量。

Apache Kafka 简介

虽然 Kafka 自 2011 年以来就在开源社区中存在,但在深入了解摄取库之前,值得先提及一下它的基本知识。如果你已经熟悉 Kafka 的基本组件和架构,并且只想了解如何使连接器为你工作,可以跳过这一部分。

Kafka 是一个分布式事件存储和流处理框架,提供一个统一的、高吞吐量、低延迟的平台,用于处理实时数据流。

与以表为基础的架构不同,Kafka 架构建立在主题的概念上。与我们的 Delta 表类似,每个 Kafka 主题都有能力以无限制的方式进行扩展(代价是存储空间和集群利用率)。每个 Kafka 主题会在集群内的多个代理之间进行分区,且每个集群可以根据包含的主题需求进行扩展。

分布式架构的真正亮点在于,Kafka 通过简单配置实现高可用性和容错性,采用了所谓的同步副本(ISR)。每个副本都存储一个或多个分区的完整副本,如果某个代理失效(例如,它下线或因网络分区无法访问),Kafka 主题可以将另一个代理委托为集群中的主代理,并且新的代理可以接管以接收整个主题或部分分区的额外副本(ISR)。通过这种方式,我们可以保证通过特定主题流动的数据不会丢失,除非发生整个集群的严重故障。如果发生这种情况,我们只能希望已设置好好的灾难恢复(DR)计划来降低数据丢失的风险。

最后,有些不变的特性使得 Kafka 在处理时间序列数据时无可替代。每个 Kafka 主题可以保证在每个主题分区内的同步插入,而无需协调所有分区之间的插入顺序。这意味着当集群处于正常状态时,你可以信任事件的顺序,从而减少流处理的复杂性。虽然这可能不言而喻,但不需要进行昂贵的重新读取和排序,在处理时间序列数据时使我们在将 Kafka 源数据传输到 Delta 表时,能够保持分析的稳定性。现在,回到 kafka-delta-ingest 连接器。

Kafka Delta Ingest 连接器

Kafka Delta Ingest 连接器提供了一个守护进程,简化了将 Kafka 数据流式传输到 Delta Lake 表的常见步骤。入门可以通过四个简单步骤完成:

  1. 安装 Rust。
  2. 构建项目。
  3. 创建 Delta 表。
  4. 运行数据摄取流。

安装 Rust

可以通过 rustup 工具链来完成安装:

% curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

安装完 rustup 后,运行 rustup update 来确保我们使用的是最新稳定版的 Rust。

构建项目

此步骤确保我们能够访问源代码。

通过命令行使用 git,克隆连接器:

% git clone git@github.com:delta-io/kafka-delta-ingest.git \
  && cd kafka-delta-ingest

设置本地环境

在项目目录的根目录下,运行 Docker 设置工具:

% docker compose up setup

设置流程完成后,我们将获得 localstack(用于运行本地 Amazon Web Services [AWS] 实例)、Kafka(redpandas)、Confluent schema registry 以及 Azurite(用于本地 Azure 存储)。能够本地运行基于云的工作流大大减少了从应用设计阶段到生产阶段的痛苦。

构建连接器

Rust 使用 cargo 来管理依赖并构建项目。cargo 工具由 rustup 工具链为我们安装。在项目根目录下,执行以下命令:

% cargo build

此时,我们已经构建了连接器并安装了 Rust 依赖。接下来,我们可以选择运行示例,或者连接到我们自己的 Kafka 代理并开始工作。关于如何使用 kafka-delta-ingest 的最后一部分将涵盖如何运行端到端数据摄取流程。

运行数据摄取流

为了使数据摄取应用程序正常运行,我们需要满足两个条件——一个源 Kafka 主题和一个目标 Delta 表。生成 Delta 表时有一个注意事项,特别是如果你熟悉基于 Apache Spark 的 Delta 工作流:我们必须先创建目标 Delta 表,才能成功运行数据摄取流。

有一些变量可以修改 kafka-delta-ingest 应用程序。我们将从环境变量的基本介绍开始(见表 4-1),然后表 4-2 将提供一些在使用此连接器时可用的运行时变量(参数)。

表 4-1. 使用环境变量
环境变量描述默认值
KAFKA_BROKERSKafka 代理的字符串;可用于覆盖本地测试的代理位置,或用于应急和恢复应用程序localhost:9092
AWS_ENDPOINT_URL用于通过 LocalStack 运行本地测试none
AWS_ACCESS_KEY_ID用于提供应用程序身份test
AWS_SECRET_ACCESS_KEY用于验证应用程序身份test
AWS_DEFAULT_REGION对于运行 LocalStack 或引导不同的 S3 存储桶位置很有用none
表 4-2. 使用命令行参数
参数描述示例
allowed_latency用于指定在处理之前等待多长时间以填充缓冲区并等待新数据--allowed_latency 60
app_id用于通过 LocalStack 运行本地测试--app_id ingest-app
auto_offset_reset可以是 earliest 或 latest;此选项决定是从 Kafka 主题的尾部还是头部读取数据--auto_offset_reset earliest
checkpoints将记录每个处理过的摄取批次的 Kafka 元数据;这样可以轻松停止并重新启动应用程序,而不会丢失数据(除非 Kafka 删除了数据,主题的删除策略可以检查)--checkpoints
consumer_group_id为 Kafka 代理提供唯一的消费者名称;使用此组 ID,代理可以在多个消费者应用之间分配大主题的处理任务,而不会出现重复--consumer_group_id ecomm-ingest-app
max_messages_per_batch用此选项来限制每次应用程序循环处理的消息数量;这可以帮助防止应用程序因记录写入主题的量异常增加而内存不足--max_messages_per_batch 1600
min_bytes_per_file使用此选项可确保底层的 Delta 表不会产生过多的小文件--min_bytes_per_file 64000000
kafka用于将 Kafka 代理字符串传递给摄取应用程序--kafka 127.0.0.1:29092

现在,只需运行数据摄取应用程序。如果我们使用环境变量来运行应用程序,那么最简单的命令将提供 Kafka 主题和 Delta 表的位置。命令格式如下:

% cargo run ingest <topic> <delta_table_location>

接下来,我们将看到一个完整的示例:

% cargo run \
  ingest ecomm.v1.clickstream file:///dldg/ecomm-ingest/ \
  --allowed_latency 120 \
  --app_id clickstream_ecomm \
  --auto_offset_reset earliest \
  --checkpoints \
  --kafka 'localhost:9092' \
  --max_messages_per_batch 2000 \
  --transform 'date: substr(meta.producer.timestamp, `0`, `10`)' \
  --transform 'meta.kafka.offset: kafka.offset' \
  --transform 'meta.kafka.partition: kafka.partition' \
  --transform 'meta.kafka.topic: kafka.topic'

通过我们一起探索的简单步骤,现在我们可以轻松地从 Kafka 主题中摄取数据。通过确保消费我们数据的人能够以高可靠性的方式进行操作,我们为成功打下了基础。我们能够自动化的越多,发生人为错误并导致事件或令人头痛的数据丢失的机会就越小。

在下一节中,我们将探索 Trino。之前的两个示例与 Trino 生态系统兼容,它们减少了在写入可以通过传统 SQL 工具分析的稳固表之前摄取和转换数据的工作量。

Trino

Trino 是一个分布式 SQL 查询引擎,旨在无缝连接并与众多数据源进行交互。它提供了一个支持 Delta Lake 原生连接器的生态系统。

注意
Trino 是 Presto 项目的社区支持分支,最初在 Facebook 内部设计和开发。Trino 在 2020 年之前被称为 PrestoSQL,直到现在才更名为 Trino。

要了解更多关于 Trino 的信息,可以查阅《Trino: The Definitive Guide》(O'Reilly 出版)。

入门

要开始使用 Trino 和 Delta Lake,我们只需要一个版本为 373 以上的 Trino。本文写作时,Trino 的当前版本为 459。

连接器要求

虽然 Delta 连接器已原生包含在 Trino 发行版中,但为了确保顺畅的体验,仍有一些额外的事项需要考虑。

连接到 OSS 或 Databricks Delta Lake:
  • 由 Databricks Runtime 版本 7.3 LTS、9.1 LTS、10.4 LTS、11.3 LTS 或 >= 12.2 LTS 写入的 Delta 表。
  • 支持 AWS、HDFS、Azure 存储和 Google Cloud Storage (GCS) 的部署。
  • 协调器和工作节点需要能够访问 Delta Lake 存储。
  • 需要访问 Hive Metastore (HMS)。
  • 协调器和工作节点需要能够访问 HMS,默认端口为 9083,这是 HMS 使用的 Thrift 协议端口。
本地使用 Docker:
  • Trino 镜像
  • Hive Metastore (HMS) 服务(独立运行)
  • Postgres 或支持的关系型数据库管理系统(RDBMS) :用于存储 HMS 表属性、列、数据库及其他配置(可以指向如 RDS 等托管的 RDBMS)。
  • Amazon S3 或 MinIO:用于我们托管数据仓库的对象存储。

示例 4-10 展示了如何配置一个简单的 Trino 容器来进行本地测试。

示例 4-10. 基本 Trino Docker Compose
services:
  trinodb:
    image: trinodb/trino:426-arm64
    platform: linux/arm64
    hostname: trinodb
    container_name: trinodb
    volumes:
      - $PWD/etc/catalog/delta.properties:/etc/trino/catalog/delta.properties
      - $PWD/conf:/etc/hadoop/conf/
    ports:
      - target: 8080
        published: 9090
        protocol: tcp
        mode: host
    environment:
      - AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID
      - AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY
      - AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION:-us-west-1}
    networks:
      - dldg

接下来的示例假设我们有以下资源:

  • Amazon S3 或 MinIO:已配置存储桶,设置了用户及角色以允许读取、写入和删除权限。使用本地 MinIO 来模拟 S3 是一个简单的方法,无需前期成本。可以查看本书 GitHub 仓库中的 docker compose 示例,位于 ch04/trinodb/。
  • MySQL 或 PostgreSQL:可以本地运行,也可以设置在我们喜欢的云服务提供商上。例如,AWS RDS 是一个简单的起步方法。
  • Hive Metastore (HMS) 或 Amazon Glue 数据目录

接下来,我们将学习如何配置 Delta Lake 连接器,以便我们能够在 Trino 中创建 Delta 目录。如果你想了解更多关于使用 Hive Metastore (HMS) 的内容,包括如何配置 hive-site.xml、如何为 S3 包含所需的 JAR 文件以及如何运行 HMS,可以阅读《运行 Hive Metastore》部分。如果你不关心这些内容,可以跳过直接进入“配置并使用 Trino 连接器”。

运行 Hive Metastore

如果你已经设置了一个可靠的 Metastore 实例,可以修改连接属性以使用该实例。如果你希望有一个本地设置,则可以从创建 hive-site.xml 文件开始,该文件示例如 4-11 所示,并且需要连接到 MySQL 和 Amazon S3。

示例 4-11. Hive Metastore (HMS) 的 hive-site.xml 配置
<configuration>
  <property>
    <name>hive.metastore.version</name>
    <value>3.1.0</value>
  </property>
  <property>
    <name>javax.jdo.option.ConnectionURL</name>
    <value>jdbc:mysql://RDBMS_REMOTE_HOSTNAME:3306/metastore</value>
  </property>
  <property>
    <name>javax.jdo.option.ConnectionDriverName</name>
    <value>com.mysql.cj.jdbc.Driver</value>
  </property>
  <property>
    <name>javax.jdo.option.ConnectionUserName</name>
    <value>RDBMS_USERNAME</value>
  </property>
  <property>
    <name>javax.jdo.option.ConnectionPassword</name>
    <value>RDBMS_PASSWORD</value>
  </property>
  <property>
    <name>hive.metastore.warehouse.dir</name>
    <value>s3a://dldgv2/delta/</value>
  </property>
  <property>
    <name>fs.s3a.access.key</name>
    <value>S3_ACCESS_KEY</value>
  </property>
  <property>
    <name>fs.s3a.secret.key</name>
    <value>S3_SECRET_KEY</value>
  </property>
  <property>
    <name>fs.s3.path-style-access</name>
    <value>true</value>
  </property>
  <property>
    <name>fs.s3a.impl</name>
    <value>org.apache.hadoop.fs.s3a.S3AFileSystem</value>
  </property>
</configuration>

此配置提供了访问元数据数据库所需的基本设置,使用 JDBC 连接 URL、用户名和密码属性,以及使用 hive.metastore.warehouse.dir 和以 fs.s3a 为前缀的属性来访问数据仓库。

接下来,我们需要创建一个 Docker Compose 文件来运行 Metastore,这将在示例 4-12 中进行演示。

示例 4-12. Hive Metastore 的 Docker Compose 配置
version: "3.7"

services:
  metastore:
    image: apache/hive:3.1.3
    platform: linux/amd64
    hostname: metastore
    container_name: metastore
    volumes:
      - ${PWD}/jars/hadoop-aws-3.2.0.jar:/opt/hive/lib/
      - ${PWD}/jars/mysql-connector-java-8.0.23.jar:/opt/hive/lib/
      - ${PWD}/jars/aws-java-sdk-bundle-1.11.375.jar:/opt/hive/lib/
      - ${PWD}/conf:/opt/hive/conf
    environment:
      - SERVICE_NAME=metastore
      - DB_DRIVER=mysql
      - IS_RESUME="true"
    expose:
      - 9083
    ports:
      - target: 9083
        published: 9083
        protocol: tcp
        mode: host
    networks:
      - dldg

现在,Metastore 正在运行,我们可以开始了解如何利用 Trino 连接器来访问 Delta Lake。

配置和使用 Trino 连接器

Trino 使用名为 catalog 的配置文件。它们用于描述目录类型(如 delta_lake、hive 等),并允许我们调整特定目录的配置,以优化读写操作并管理其他连接器配置。Delta 连接器的最小配置要求是一个可以访问的 Hive Metastore 地址,例如 thrift:hostname:port(如果使用 HMS)。截至写作时,另一种受支持的目录是 AWS Glue。

在示例 4-13 中的代码配置了连接器,指向 Hive Metastore。

示例 4-13. Delta Lake 连接器属性

connector.name=delta_lake
hive.metastore=thrift
hive.metastore.uri=thrift://metastore:9083
delta.hive-catalog-name=metastore
delta.compression-codec=SNAPPY
delta.enable-non-concurrent-writes=true
delta.target-max-file-size=512MB
delta.unique-table-location=true
delta.vacuum.min-retention=7d

警告
如果存在多个写入者对表进行非原子性更改的可能性(通常发生在 Amazon S3 上),则必须将 delta.enable-non-concurrent-writes 属性设置为 true。设置为 true 可确保表的一致性。

示例 4-13 中的属性文件可以保存为 delta.properties。只要将文件复制到 Trino 目录(/etc/trino/catalog/),我们就能从底层的 hive.metastore.warehouse.dir 进行读写、删除等操作。

让我们看看有哪些操作是可能的。

使用 Show Catalogs

使用 show catalogs 是确认 Delta 连接器是否配置正确并作为资源显示的简单步骤:

trino> show catalogs;
Catalog
---------
delta
...
(6 rows)

只要看到 delta 出现在列表中,就可以继续创建模式。这确认了我们的目录已正确配置。

创建模式

模式的概念有些复杂。我们有表示表列的结构化数据的模式,也有表示传统数据库的模式。使用 create schema 可以在数据仓库中生成一个管理位置,作为访问和治理的边界,并将物理表数据分为 bronze、silver 和 golden 表。我们将在第 9 章学习更多关于勋章架构的内容,但现在让我们创建一个 bronze_schema 来存储一些原始表:

trino> create schema delta.bronze_schema;
CREATE SCHEMA

提示
如果我们看到异常,而不是返回 CREATE SCHEMA,那很可能是由于没有权限写入物理仓库。以下是此类异常的一个示例:

Query 20231001_182856_00004_zjwqg failed: Got exception: java.nio.file.AccessDeniedException s3a://com.newfront.dldgv2/delta/bronze_schema.db: getFileStatus on s3a://com.newfront.dldgv2/delta/bronze_schema.db: com.amazonaws.services.s3.model.AmazonS3Exception: Forbidden (Service: Amazon S3; Status Code: 403;

我们可以通过修改身份和访问管理(IAM)权限,或者确保使用正确的 IAM 角色来解决此问题。

Show Schemas

此命令允许我们查询目录以查看可用的模式:

trino> show schemas from delta;
Schema
--------------------
default
information_schema
bronze_schema
(3 rows)

如果我们想要的模式存在,那么就可以继续创建表了。

操作表

在 Trino 和 Delta 生态系统之间的表兼容性要求我们遵循一些准则。我们将首先讨论数据类型的互操作性,然后创建一个表,插入一些行,并查看 Delta 元数据,包括事务历史记录和启用更改数据捕获(CDF)的表的更改跟踪。最后,我们将讨论表的优化和清理操作。

数据类型

在使用 Trino 创建表时,特别是 Trino 和 Delta Lake 之间的类型映射差异,存在一些注意事项。表 4-3 显示的类型映射可以帮助我们确保使用合适的类型,并避免不兼容性问题,特别是在我们目标是实现互操作性时。

表 4-3. Delta 到 Trino 类型映射

Delta 数据类型Trino 数据类型
BOOLEANBOOLEAN
INTEGERINTEGER
BYTETINYINT
SHORTSMALLINT
LONGBIGINT
FLOATREAL
DOUBLEDOUBLE
DECIMAL(p,s)DECIMAL(p,s)
STRINGVARCHAR
BINARYVARBINARY
DATEDATE
TIMESTAMPNTZ (TIMESTAMP_NTZ)TIMESTAMP(6)
TIMESTAMPTIMESTAMP(3) WITH TIME ZONE
ARRAYARRAY
MAPMAP
STRUCT(...)ROW(...)

创建表选项

在 CREATE TABLE 操作中,我们可以使用 WITH 子句来应用支持的表选项(如表 4-4 所示)。这使得我们能够为表指定 Trino 否则无法理解的选项。在分区方面,Trino 不会自动发现分区,这可能会影响 SQL 查询的性能。

表 4-4. CREATE TABLE 选项

属性名描述默认值
location表的文件系统位置统一资源标识符(URI)。此选项已弃用。使用映射到 hive.metastore.warehouse.dir 或 Glue Catalog 等效项的管理表
partitioned_by按哪些列对表进行分区没有分区
checkpoint_intervalDelta Lake 提交更改的频率开源软件每 10 次,Databricks 每 100 次
change_data_feed_enabled启用表的更改数据捕获(CDC)/更改数据流(CDF)功能false
column_mapping_mode映射底层 Parquet 列的方式:选项(ID、name、none)none

创建表

我们可以使用长格式 <catalog>.<schema>.<table> 或者短格式 <table> 来创建表,前提是先调用 use delta.<schema>。示例 4-14 展示了使用短格式 CREATE 的例子。

示例 4-14. 使用 Trino 创建 Delta 表

trino> use delta.bronze_schema;
CREATE TABLE ecomm_v1_clickstream (
  event_date DATE,
  event_time VARCHAR(255),
  event_type VARCHAR(255),
  product_id INTEGER,
  category_id BIGINT,
  category_code VARCHAR(255),
  brand VARCHAR(255),
  price DECIMAL(5,2),
  user_id INTEGER,
  user_session VARCHAR(255)
)
WITH (
    partitioned_by = ARRAY['event_date'],
    checkpoint_interval = 30,
    change_data_feed_enabled = false,
    column_mapping_mode = 'name'
);

在示例 4-14 中使用的 DDL 语句生成了一个管理表,该表将在我们的数据仓库中进行日分区。表的结构表示了本章之前 "Apache Flink" 部分中的电子商务数据。

列出表

使用 show tables 可以查看 Delta 目录中给定模式下的表集合:

trino:bronze_schema> show tables;
Table
----------------------
ecomm_v1_clickstream
(1 row)

检查表

如果我们不是某个表的所有者,可以使用 describe 查看表的元数据:

trino> describe delta.bronze_schema."ecomm_v1_clickstream";
    Column     |     Type     | Extra | Comment
---------------+--------------+-------+---------
 event_date    | date         |       |
 event_time    | varchar      |       |
 event_type    | varchar      |       |
 product_id    | integer      |       |
 category_id   | bigint       |       |
 category_code | varchar      |       |
 brand         | varchar      |       |
 price         | decimal(5,2) |       |
 user_id       | integer      |       |
 user_session  | varchar      |       |
(10 rows)

使用 INSERT 插入行

可以通过命令行或 Trino 客户端直接插入行:

trino> INSERT INTO delta.bronze_schema."ecomm_v1_clickstream"
    VALUES
        (DATE '2023-10-01', '2023-10-01T19:10:05.704396Z', 'view', ...),
        (DATE('2023-10-01'), '2023-10-01T19:20:05.704396Z', 'view', ...);
INSERT: 2 rows

查询 Delta 表

使用 select 操作符可以查询 Delta 表:

trino> select event_date, product_id, brand, price 
  -> from delta.bronze_schema."ecomm_v1_clickstream";
 event_date | product_id |  brand  | price
------------+------------+---------+--------
 2023-10-01 |   44600062 | nars    |  35.79
 2023-10-01 |   54600062 | lancome | 122.79
(2 rows)

更新行

标准的 update 操作符是可用的:

trino> UPDATE delta.bronze_schema."ecomm_v1_clickstream"
    -> SET category_code = 'health.beauty.products'
    -> where category_id = 2103807459595387724;

使用选择创建表

我们可以使用另一个表创建一个新表,这被称为 CREATE TABLE AS。它允许我们通过引用另一个表来创建一个新的物理 Delta 表:

trino> CREATE TABLE delta.bronze_schema."ecomm_lite" 
    AS select event_date, product_id, brand, price 
    FROM delta.bronze_schema."ecomm_v1_clickstream";

表操作

对于优化性能以及清理物理文件系统中的Delta表,有许多表操作需要考虑。本章的第5章介绍了常见的维护和表工具函数,接下来的部分则讨论了Trino连接器中可用的函数。

清理(Vacuum)

清理操作将清除Delta表当前版本中不再需要的文件。第5章详细介绍了为什么需要清理,以及在支持表恢复和通过时间旅行回滚到先前版本时需要注意的注意事项。

在Trino中,Delta目录属性delta.vacuum.min-retention提供了一种保护机制,以防在调用清理时指定的天数或小时数过少:

trino> CALL delta.system.vacuum('bronze_schema', 'ecomm_v1_clickstream', '1d');
Retention specified (1.00d) is shorter than the minimum retention configured in the system (7.00d). Minimum retention can be changed with delta.vacuum.min-retention configuration property or delta.vacuum_min_retention session property

否则,清理操作将删除表中不再需要的物理文件。

表优化

根据我们对表的修改,在使用Trino时,创建的表部分可能会过小,造成太多的小文件。将这些小文件合并成较大的文件的一个简单方法是使用bin-packing optimize(在第5章和第10章的性能调优深度探讨中有介绍)。为了触发合并,我们可以使用ALTER TABLEEXECUTE一起调用:

trino> ALTER TABLE delta.bronze_schema."ecomm_v1_clickstream" EXECUTE optimize;

我们还可以提供更多的提示来改变优化操作的行为。以下命令会忽略大于10MB的文件:

trino> ALTER TABLE delta.bronze_schema."ecomm_v1_clickstream" 
    -> EXECUTE optimize(file_size_threshold => '10MB')

以下命令仅会尝试优化特定分区中的表文件(例如event_date = "2023-10-01"):

trino> ALTER TABLE delta.bronze_schema."ecomm_v1_clickstream" EXECUTE optimize
WHERE event_date = "2023-10-01"

元数据表

Trino连接器公开了每个Delta Lake表的几个元数据表,包含有关其内部结构的信息。我们可以查询这些表来了解更多关于表的信息,并检查更改和最近的历史记录。

表历史

每个事务都会记录在<table>$history元数据表中:

trino> describe delta.bronze_schema."ecomm_v1_clickstream$history";
        Column        |            Type             | Extra | Comment
----------------------+-----------------------------+-------+---------
 version              | bigint                      |       |
 timestamp            | timestamp(3) with time zone |       |
 user_id              | varchar                     |       |
 user_name            | varchar                     |       |
 operation            | varchar                     |       |
 operation_parameters | map(varchar, varchar)       |       |
 cluster_id           | varchar                     |       |
 read_version         | bigint                      |       |
 isolation_level      | varchar                     |       |
 is_blind_append      | boolean                     |       |

我们可以查询元数据表。以下是查询ecomm_v1_clickstream表的最后三个事务的例子:

trino> select version, timestamp, operation 
    -> from delta.bronze_schema."ecomm_v1_clickstream$history";
 version |          timestamp          |  operation
---------+-----------------------------+--------------
       0 | 2023-10-01 19:47:35.618 UTC | CREATE TABLE
       1 | 2023-10-01 19:48:41.212 UTC | WRITE
       2 | 2023-10-01 23:01:13.141 UTC | OPTIMIZE
(3 rows)

变更数据流(Change Data Feed, CDF)

Trino连接器提供了读取变更数据流(CDF)条目的功能,用于揭示Delta Lake表两个版本之间的行级更改。当change_data_feed_enabled表属性设置为true时,连接器会记录所有数据更改的变更事件:

trino> use delta.bronze_schema;
CREATE TABLE ecomm_v1_clickstream (
  ...
)
WITH (
    change_data_feed_enabled = true
);

现在,每一行的每一笔事务都会被记录(包括操作类型),使我们能够重建表的状态或查看在特定时间点之后对表的所有更改。

例如,如果我们想查看从表的版本0以来的所有更改,可以执行以下查询:

trino> select event_date, _change_type, _commit_version, _commit_timestamp 
from TABLE(
  delta.system.table_changes(
    schema_name => 'bronze_schema',
    table_name => 'ecomm_v1_clickstream',
    since_version => 0
  )
);

然后查看所做的更改。在此示例中,我们插入了两行数据:

 event_date | _change_type | _commit_version |      _commit_timestamp
------------+--------------+-----------------+-----------------------------
 2023-10-01 | insert       |               1 | 2023-10-01 19:48:41.212 UTC
 2023-10-01 | insert       |               1 | 2023-10-01 19:48:41.212 UTC
(2 rows)

查看表属性

查看与表相关联的表属性非常有用。我们可以使用元数据表<table>$properties来查看与Delta相关的TBLPROPERTIES:

trino> select * from  delta.bronze_schema."ecomm_v1_clickstream$properties";
               key               | value
---------------------------------+-------
 delta.enableChangeDataFeed      | true
 delta.columnMapping.maxColumnId | 10
 delta.columnMapping.mode        | name
 delta.checkpointInterval        | 30
 delta.minReaderVersion          | 2
 delta.minWriterVersion          | 5

修改表属性

如果我们想修改Delta表的底层表属性,需要使用Delta连接器的别名来支持表属性。例如,change_data_feed_enabled将映射到delta.enableChangeDataFeed属性:

trino> ALTER TABLE delta.bronze_schema."ecomm_v1_clickstream"
SET PROPERTIES "change_data_feed_enabled" = false;

删除表

使用DROP TABLE操作,可以永久删除不再需要的表:

trino> DROP TABLE delta.bronze_schema."ecomm_lite";

Trino连接器可以做更多操作,但超出了本书的范围。至此,我们将告别Trino,并结束本章的内容。

总结

在本章中,我们一起学习了如何轻松地将Delta表连接为Flink应用程序的源或目标。接着,我们学习了如何使用基于Rust的kafka-delta-ingest应用程序,简化数据摄取过程,这对大多数处理高吞吐量流数据的数据工程师来说是日常工作。通过减少仅仅读取数据流并将其写入Delta表所需的努力,我们大大减轻了认知负担。当我们开始以表格的形式思考所有数据——无论是有界数据还是无界数据——这一思维模型可以帮助我们解决即使是最复杂的数据密集型问题。最后,我们通过探索Delta的原生Trino连接器来结束本章内容。我们发现,简单的配置就能为分析和洞察打开大门,同时确保我们依然能够保持Delta表作为单一数据真相源的角色。