17611538698
webmaster@21cto.com

Uber 是如何构建实时基础设施每天处理 PB 级数据的

大数据 0 85 2024-04-22 06:23:07

图片

21CTO导读:本文向大家讲述Uber的实时数据基础设施,“揭密”世界最大的实时租车平台的内部流程。

背景

Uber是一家著名的科技与互联网公司。在 2010 年代初推出了一款可让司机和乘客轻松连接的应用程序,从而改变了出租车市场。

Uber 由 Garrett Camp 和 Travis Kalanick 于 2009 年创立。


二人在巴黎参加技术会议时提出来将开发一款拼车应用程序。由于对传统的出租车服务感到失望,他们希望引入一种更高效、更可靠的出行方式,利用技术将乘客与附近的司机联系起来,为乘客提供最大化的便利。

UberCab是 Uber 的第一个版本,于 2010 年在旧金山上线。该应用程序允许用户通过智能手机叫车并付款,票价根据距离与时间综合计算。 

与传统出租车不同的是,Uber司机并不是有执照的专业人士,而是拥有汽车的普通人,他们与Uber签约,并以兼职或全职工作的形式提供乘车服务。

到 2023 年,已经有 1.37 亿人每月使用一次 Uber 或 Uber Eats 优食。此外,到2023 年,Uber 司机完成了 94.4 亿次出行。为了支持平台业务,Uber 积极利用数据分析和机器学习模型进行运营。

从Uber 乘车的动态定价到UberEats Restaurant Manager 仪表盘,所有这些都必须利用实时数据进行高效操作。

在本篇文章里,让我们一起来看 Uber 如何管理并支持众多实时应用程序的幕后基础设施。

语境

图片

Uber 基础设施的数据流架构


Uber 的业务本质上是高度实时的。

数据是从许多来源持续收集的:司机、乘客、餐馆、食客以及商家后端服务。Uber 处理这些数据,以提取有价值的信息,以便为许多实际用例(例如客户激励、欺诈检测和机器学习模型预测)做出实时决策。

实时数据处理在 Uber 的业务中发挥着相当重要的作用。该平台依靠着开源解决方案和内部改进来构建实时基础设施。

较高层面来看,Uber 的实时数据处理包括以下三个主要领域:

  • 消息平台:允许生产者和订阅者之间进行通信。

  • 流处理:允许将处理逻辑应用于消息流。

  • 在线分析处理 (OLAP):可以接近实时的对所有数据进行分析查询。


每个领域都面临三个基本的扩展挑战:

  • 扩展数据:传入的实时数据总量呈指数级增长。此外,Uber的基础设施分布在多个地理区域,如果想实现高可用性,这意味着系统必须能够大量增加的数据处理,同时保持数据新鲜度、减少端到端延迟和可用性SLA。

  • 扩展用例:随着 Uber 业务的增长,随着组织不同部门之间不同需求的出现,新的用例不断出现。

  • 用户类型扩展:与实时数据系统交互的不同用户具有不同的技术&技能水平,从没有工程背景的业务用户到需要开发复杂的实时数据管道的高级用户。


对基础设施的要求


Uber 的实时基础设施需要以下几点:


  • 一致性:关键应用程序需要所有区域的数据一致性。

  • 可用性:基础设施必须具有 99.99% 的高可用性保证。

  • 新鲜度:大多数用例需要二级新鲜度。这确保了响应特定事件(例如安全事件)的能力。

  • 延迟:某些用例需要对原始数据执行查询,并要求 p99 查询延迟低于 1 秒。

  • 可扩展性:系统可以随着数据量的不断增长而扩展。

  • 成本:Uber需要较低的数据处理和服务成本来确保高运营效率。

  • 灵活性:Uber 必须提供一个编程和声明式接口来表达计算逻辑,以服务不同的用户类别。


构建模块


在本节中,我们将了解 Uber 基础设施的主要逻辑构建块:

图片



  • 存储:该层为其他层提供对象或 blob 存储,并保证写后读一致性。它用于长期存储,应针对高写入速率进行优化。Uber 使用该层将数据回填或引导到流或 OLAP 数据表中。

  • Stream:它充当发布/订阅接口,应该针对读取和写入的低延迟进行优化。它需要对数据进行分区并保证至少一次语义计算。

  • 计算:该层提供流和存储层上的计算。还要求源和接收器之间至少有一种语义。

  • OLAP:该层针对来自流或存储的数据,提供有限的 SQL 功能,还要对其进行优化以服务分析查询。在从不同来源获取数据时,它至少需要一次语义计算。某些用例要求根据主键仅摄取一次数据。

  • SQL是 计算层和 OLAP 层之上的查询层。它将 SQL 语句编译为计算函数,可以应用于流或存储。与OLAP层配合使用,会增强OLAP层的SQL限制能力。

  • API:高层应用程序访问流或计算功能的编程方式。

  • 元数据:管理所有层的各种元数据的简单接口。该层需要元数据版本控制和跨版本的向后兼容性。

以下部分将为大家介绍 Uber 针对相应构建模块采用的开源系统。

Apache Kafka

流式存储

图片


Apache Kafka是业界广泛采用的流行开源事件流系统。

它最初由 LinkedIn 开发,随后于 2011 年初开源。除了性能较好之外,采用 Kafka 的其它几个因素还包括简单性、生态系统成熟度和成熟的开源用户社区。

在 Uber,他们拥有全球最大的 Apache Kafka 部署之一:每天有数万亿条消息和 PB 级数据。Uber 的 Kafka 支持许多工作流程:从乘客和司机应用程序传播事件数据、启用流分析平台或将数据库更改日志发送给下游订阅者。由于Uber独特的规模特征,他们对Kafka进行了以下增强功能的定制:

集群联邦

逻辑集群

图片


Uber 开发了一个联合 Kafka 集群设置,可以向生产者和消费者隐藏集群详细信息:

  • 他们向用户公开“逻辑 Kafka 集群”。用户并不需要知道主题位于哪个集群。

  • 专用服务器集中集群和主题的所有元数据,以将客户端的请求路由到物理集群。

  • 此外,集群联合有助于提高可扩展性。当集群充分被利用时,Kafka服务可以通过添加更多集群来水平扩展。新主题在新集群上无缝创建。

  • 集群联合还简化了主题管理。由于应用程序和客户端较多,在 Kafka 集群之间迁移实时主题需要大量工作。在大多数情况下,该过程需要手动配置以将流量路由到新集群,这会导致消费者重新启动。集群联合有助于将流量重定向到另一个物理集群,而无需重新启动应用程序。


死信队列

失败的消息队列

图片


在某些情况下,下游系统无法处理消息(例如,消息损坏)。最初,有两种选择来处理这种情况:

  • 卡夫卡丢弃这些消息。

  • 系统无限期地重试,这会阻止后续消息的处理。


然而,Uber 有很多场景既不需要数据丢失,也不需要阻塞处理。

为了解决此类用例,Uber在 Kafka 之上构建了死信队列 (Dead Letter Queue)策略:如果消费者在重试后无法处理消息,它将将该消息发布到 DLQ。这样,未处理的消息将被单独处理,不会影响其他消息。

消费者代理

中间层

图片


Uber 已有数以万计的运行 Kafka 的应用程序,调试它们和升级客户端库都很困难。用户还在组织内部使用多种编程语言与 Kafka 交互,这使得提供多语言支持变得非常具有挑战性。

Uber 建立了消费者代理层来应对挑战;代理从 Kafka 读取消息并将它们路由到 gRPC 服务端点。它处理消费者库的复杂性,应用程序只需要采用瘦 gRPC 客户端。当下游服务无法接收或处理某些消息时,代理可以重试路由,并在多次重试失败后将其发送到DLQ。

代理还将 Kafka 中的传递机制从消息轮询更改为基于推送的消息调度。这提高了消费吞吐量并允许更多并发应用程序处理机会。

跨集群复制

集群之间高效的主题复制

由于业务规模较大,Uber在不同的数据中心分别部署和应用多个Kafka集群。此种部署,Uber 需要 Kafka 的跨集群数据复制,原因有两个:

  • 用户需要对各种用例的数据进行全局视图。例如,他们必须整合和分析来自所有数据中心的数据以计算行程指标。

  • Uber 复制了 Kafka 部署,以在发生故障时实现冗余。


Uber 构建并开源了一个名为uReplicator的可靠解决方案,用于 Kafka 复制目的。复制器具有重新平衡算法,可在重新平衡期间将受影响的主题分区的数量保持在尽可能低的水平。

此外,在流量突发的情况下,它可以在运行时将负载重新分配给备用工作人员。

我对 uReplicator 的高级架构进行了一些研究,以下是我的发现:

图片


  • Uber 使用Apache Helix进行 uReplicator 集群管理。

  • Helix控制器负责将主题分区分配给worker、处理主题/分区的添加/删除、检测节点故障以及重新分配那些特定的主题分区。

  • 收到主题/分区复制请求后,Helix 控制器将主题/分区与活动工作线程之间的映射更新到Zookeeper服务,该服务的作用类似于中央状态管理服务。

  • 当映射发生变化时,worker 中的 Helix 代理将收到通知。

  • DynamicKafkaConsumer实例,工作人员将在其中执行复制的任务。


Uber 还开发并开源了另一项名为Chaperone的服务,以确保跨集群复制不会丢失数据。它收集关键统计数据,例如来自每个复制阶段的唯一消息的数量。然后,Chaperone 会比较统计数据,并在出现不匹配时生成警报。

Apache Flink

流处理

图片


Uber 使用Apache Flink构建流处理平台,处理来自 Kafka 的所有实时数据。Flink 提供了一个高吞吐量、低延迟的分布式流处理框架。Uber 采用 Apache Flink 的原因如下:


  • 其稳健性通过本机状态管理和用于故障恢复的检查点功能支持许多工作负载。

  • 它易于扩展并且可以有效地处理背压。

  • Flink 拥有庞大且活跃的开源社区。


Uber 对 Apache Flink 做出了以下贡献和改进:

Flink SQL

使用 SQL 构建流分析应用程序。

图片


Uber 在 Flink 之上贡献了一个称为 Flink SQL 的层。

它可以将Apache Calcite SQL 输入转换为 Flink Jobs。处理器将查询编译成分布式 Flink 应用程序并管理其整个生命周期,使用户能够专注于流程逻辑。在后台,系统将 SQL 输入转换为逻辑计划,然后通过优化器形成物理计划。最后,使用Flink API将计划转换为 Flink Jobs

然而,向用户隐藏复杂性会增加基础设施团队管理生产作业的运营开销。Uber 需要应对这些挑战:

  • 资源估计和自动扩展:Uber 使用分析来查找常见工作类型和资源需求之间的相关性。他们还持续监控工作负载,以实现更好的集群利用率并按需执行自动扩展。

  • 作业监控和自动故障恢复:由于用户不知道幕后发生了什么,因此平台必须自动处理 Flink 作业故障。Uber 为此构建了一个基于规则的引擎。该组件比较作业的指标,然后采取相应的操作,例如重新启动作业。

注意:Flink SQL 是一个具有无限输入和输出的流处理引擎。它的语义不同于批处理 SQL 系统,例如 Presto,稍后将讨论。

统一的部署、管理、运营架构

图片


Uber 的 Flink 统一平台形成了分层架构,可以实现更好的可扩展性和可伸缩性。

  • 平台层组织业务逻辑以及与其他平台的集成,例如机器学习或工作流管理。该层将业务逻辑转换为标准的 Flink 作业定义,并将其传递到下一层。

  • 作业管理层处理 Flink 作业的生命周期:验证、部署、监控和故障恢复。它存储作业信息:状态检查点和元数据。该层还充当代理,根据作业信息将作业路由到物理集群。该层还有一个共享组件,可以持续监控作业的运行状况并自动恢复失败的作业。它公开了一组平台层的 API 抽象。

  • 底层由计算集群和存储后端组成。它提供了物理资源的抽象,无论它们是本地基础设施还是云基础设施。例如,存储后端可以使用HDFS、Amazon S3或Google Cloud Storage (GCS)作为 Flink 作业的检查点。


由于这些改进,Flink 已成为 Uber 的中央处理平台,负责数千个Jobs。现在,让我们继续讨论 OLAP 构建块的下一个开源系统:Apache Pinot。

Apache Pinot

OLAP系统

图片


Apache Pinot是一个开源分布式 OLAP 系统,用于执行低延迟分析查询。


它是在“工程人员确定没有现成的解决方案可以满足该社交网站的要求之后”,由 LinkedIn 上创建的。 Pinot 具有lambda 架构,可呈现在线(实时)和离线(历史)数据之间的统一视图。

自 Uber 推出 Pinot 以来的两年内,其数据足迹已从几 GB 增长到数百 TB 数据。随着时间的推移,查询工作负载从几百QPS(每秒查询数)增加到数万QPS。


Pinot 支持多种索引技术来回答低延迟 OLAP 查询,例如倒排索引、范围索引或星形树索引。Pinot 采用分散-聚集-合并方法以分布式方式查询大型表。它按时间边界划分数据并将其分组为段,而查询计划并行执行它们。

以下是 Uber 决定使用 Pinot 作为其 OLAP 解决方案的原因:

  • 2018 年,可用的选项是Elasticsearch和Apache Druid,但他们的后续评估表明 Pinot 具有更小的内存和磁盘占用空间,并且支持显着降低的查询延迟 SLA。

  • 对于ElasticSearch:将相同数量的数据摄取到 Elasticsearch 和 Pinot 中;Elasitcsearch 的内存使用率比 Pinot 高 4 倍,磁盘使用率高 8 倍。此外,Elasticsearch 的查询延迟高出 2 到 4 倍,通过过滤器、聚合和分组/排序查询的组合进行基准测试。

  • 对于Apache Druid:Pinot 在架构上与 Apache Druid 类似,但合并了优化的数据结构,例如位压缩前向索引,以减少数据占用。它还使用专门的索引来加快查询执行速度,例如星形树索引、排序索引和范围索引,这可能会导致查询延迟出现数量级的差异。


在 Uber,用户利用 Pinot 进行许多实时分析用例。此类用例的主要要求是数据新鲜度和查询延迟。

开源工程师们为 Apache Pinot 贡献了以下功能来满足 Uber 的独特需求:

更新插入


upsert 操作结合了插入和更新操作。


它允许用户更新现有记录并在数据库中不存在该记录时插入新记录。更新插入是许多用例中的常见要求,例如更正乘车费用或更新送货状态。

图片


更新插入操作的主要挑战是找到所需记录的位置。为了克服这个问题,Uber 使用主键将输入流拆分为多个分区,并将每个分区分发到一个节点进行处理。


这意味着同一个节点将处理具有相同键的所有记录。Uber 还开发了一种路由策略,将同一分区的各段上的子查询路由到同一节点。

完整的 SQL 支持


Pinot 最初缺乏重要的 SQL 功能,例如子查询和联接。Uber 将 Pinot 与 Presto 集成,以在 Pinot 之上启用标准 PrestoSQL 查询。


与数据生态系统的其他部分集成


Uber投入了大量精力将 Pinot 与数据生态系统的其他部分集成,以确保良好的用户体验。

例如,Pinot 与 Uber 的模式服务集成,从输入的 Kafka 主题推断模式并估计数据的基数。Pinot 还与 Flink SQL 集成作为数据接收器,以便客户可以构建 SQL 转换查询并将输出消息推送到 Pinot。

分布式文件系统

档案商店

图片


Uber 使用HDFS来存储长期数据。来自 Kafka 的大多数Avro 格式数据都作为原始日志存储在 HDFS 中。紧凑过程将日志合并为 Parquet 格式,然后可通过Hive、Presto或Spark等处理引擎使用。该数据集充当所有分析目的的事实来源。Uber 还使用此存储在 Kafka 和 Pinot 中进行数据回填。

此外,其他平台也将 HDFS 用于其特定目的。例如:


  • Apache Flink 使用 HDFS 作为作业检查点。

  • Apache Pinot 使用 HDFS 进行长期段归档。


非常快

交互式查询层

图片


Uber 采用 Presto 作为其交互式查询引擎解决方案。

Presto 是 Facebook 开发的开源分布式查询引擎。它旨在通过采用大规模并行处理(MPP)引擎并在内存中执行所有计算来对大规模数据集进行快速分析查询,从而避免将中间结果写入磁盘。

Presto 提供具有高性能 I/O 接口的连接器 API,允许连接到多个数据源:Hadoop 数据仓库、RDBMS 或 NoSQL 系统。Uber 为 Presto 构建了 Pinot 连接器,以满足实时探索需求。这样,用户就可以在 Apache Pinot 之上执行标准PrestoSQL 。

Pinot 连接器需要决定物理计划的哪些部分可以下推到 Pinot 层。

由于 API 的限制,该连接器的第一个版本仅包含谓词下推。Uber 改进了 Presto 的查询规划器并扩展了 Connector API,以将尽可能多的运算符推送到 Pinot 层。这有助于降低查询延迟并利用 Pinot 的索引。

在了解 Uber 如何使用开源系统构建实时基础设施之后,我们将讨论 Uber 生产中的一些用例以及他们如何使用这些系统来实现其目标。

分析应用:峰时定价


峰时定价用例是 Uber 的动态定价机制,可平衡可用司机的供应与乘车需求。用例的总体设计:


  • 流数据是从 Kafka 摄取的。

  • 该管道在 Flink 中运行复杂的基于机器学习的算法,并将结果存储在键值存储中以供快速查找。

  • 激增定价应用程序优先考虑数据新鲜度和可用性,而不是数据一致性,以满足延迟 SLA 要求,因为迟到的消息不会参与计算。

  • 这种权衡导致 Kafka 集群的配置追求更高的吞吐量,但不追求无损保证。


仪表盘:UberEats 餐厅管理


Uber Eats 优食餐厅管理仪表盘允许餐厅老板运行切片查询,以查看 Uber Eats 优食订单的数据,例如客户满意度、受欢迎的菜单项和服务质量分析。


用例的总体设计:

  • 该用例需要新鲜数据和低查询延迟,但不需要太多灵活性,因为查询的模式是固定的。

  • Uber 使用带有启动树索引的 Pinot 来减少服务时间。

  • 他们利用 Flink 执行过滤、聚合和汇总等任务,以帮助 Pinot 减少处理时间。

  • Uber 还观察了转换时间 (Flink) 和查询时间 (Pinot) 之间的权衡。转换过程会产生优化的指数(以 Pinot 为单位)并减少服务数据。反过来,它降低了服务层的查询灵活性,因为系统已经将数据变成了“固定形状”。


机器学习:实时预测监控


机器学习在 Uber 中发挥着至关重要的作用,为了确保模式的质量,监控模型预测输出的准确性至关重要。


用例的总体设计:

  • 由于数据量大且基数高,该解决方案需要可扩展性:数千个已部署的模型,每个模型都有数百个功能。

  • 它利用了 Flink 的水平可扩展性。Uber 部署了一个大型流作业来聚合指标并检测预测异常。

  • Flink 作业将预聚合创建为 Pinot 表,以提高查询性能。


特别探索:UberEats 运营自动化


UberEats 团队需要对来自快递员、餐馆和食客的实时数据执行临时分析查询。


这些运营见解将用于基于规则的自动化框架。该框架特别有助于运营团队在 COVID-19 期间按照法规和安全规则运营业务。


用例的总体设计:

  • 底层系统必须高度可靠和可扩展,因为这个决策过程对业务至关重要。

  • 用户在 Pinot 管理的实时数据之上使用 Presto 来检索相关指标,然后将其输入到自动化框架中。

  • 该框架使用 Pinot 来汇总过去几分钟内给定位置所需的统计数据,然后相应地为快递员和餐馆生成警报和通知。

  • Pinot、Presto 和 Flink 随着数据的增长而快速扩展,并在高峰时段可靠地运行。

在文章结束之前,我将在下面的章节中介绍 Uber 的全主动策略、如何管理数据回填以及 Uber 的经验教训。

全面主动战略

本节将展示 Uber 如何提供业务弹性和连续性。

Uber 依靠多区域策略,确保服务在地理上分布的数据中心的备份下运行,这样,如果一个区域的一项服务不可用,它仍然可以在其他区域启动并运行。

这种方法的基础是多区域 Kafka 设置,可提供数据冗余和流量连续性。

图片


以下是动态定价应用程序的主动-主动设置示例:

  • 所有行程事件都会发送到 Kafka 区域集群,然后路由到聚合集群以实现全局视图。

  • Flink 作业将计算每个区域中不同区域的定价。

  • 每个区域都有一个更新服务 实例,并且全活动协调服务将其中一个标记为主要服务。

  • 来自主要区域的更新服务将定价结果存储在主动/主动数据库中以便快速查找。

  • 当主区域发生故障时,双活服务会分配另一个区域作为主区域,并将计算故障转移到另一个区域。

  • Flink作业的计算状态太大,无法在Region之间同步复制,因此必须独立计算。

  • → 这种方法是计算密集型的,因为 Uber 需要管理每个区域的冗余管道。


数据回填


Uber 需要回到过去并重新处理数据流,原因如下:

  • 新的数据管道通常需要针对现有数据进行测试。

  • 机器学习模型必须使用几个月的数据进行训练。

  • 流处理管道中的更改或错误需要重新处理旧数据。


Uber 使用 Flink 构建了流处理回填的解决方案,该解决方案有两种操作模式:

  • 基于SQL:该模式允许用户在实时(Kafka)和离线数据集(Hive)上执行相同的SQL查询。

  • 基于API:Kappa+架构允许直接在批量数据上重用流处理逻辑。


Uber 的经验教训


开源采用


Uber 在开源组件上构建了大部分实时分析堆栈。依靠这些组件为 Uber 奠定了坚实的基础。尽管如此,这还是遇到了一些挑战:

  • 根据他们的经验,大多数开源技术都是为了特定目的而构建的。

  • Uber 必须做大量工作才能使开源解决方案适用于广泛的用例和编程语言。


系统快速开发和演进


对于像 Uber 这样的大公司来说,架构演变中经常会看到多种驱动因素,例如新的业务需求或行业趋势。


结果,Uber 认识到了实现快速软件开发的重要性,以便每个系统都能快速发展:


  • 接口标准化对于干净的服务边界至关重要。Uber 利用Monorepo来管理单个代码存储库中的所有项目。

  • Uber 一直偏爱瘦客户端,以减少客户端升级的频率。在引入Kafka瘦客户端之前,升级一个Kafka客户端需要几个月的时间。

  • 他们采用语言整合策略来减少与系统通信的方式数量。Uber 仅支持Java和Golang编程语言,以及PrestoSQL声明性 SQL 语言。

  • 平台团队将所有基础设施组件与 Uber 专有的 CI/CD 框架集成,以在临时环境中持续测试和部署开源软件更新或功能开发。这也最大限度地减少了生产环境中的问题和错误。


易于操作和监控


  • 运营:Uber 投资了声明式框架来管理系统部署。用户定义集群启动/关闭、资源重新分配或流量重新平衡等操作的高级意图后,框架将处理这些指令,而无需工程师干预。

  • 监控:Uber 使用 Kafka、Flink 或 Pinot 为每个特定用例构建了实时自动化仪表板和警报。


易于用户入门和调试


Uber在以下几个方面做出了努力来解决用户规模挑战:


  • 数据发现:Uber 的集中式元数据存储库充当 Kafka、Pinot 和 Hive 等跨系统模式的真实来源,使用户可以非常方便地搜索所需的数据集。该系统还记录跨这些组件的数据流的数据沿袭。

  • 数据审计:对应用程序的事件进行端到端审计。Kafka 客户端将附加元数据归因于各个事件,例如唯一标识符、应用程序时间戳、服务名称和层。该系统使用这些元数据来跟踪数据生态系统每个阶段的数据丢失和重复,帮助用户有效地检测数据问题。

  • 无缝上线:系统自动为生产环境中部署的相应服务提供应用程序日志的Kafka主题。用户还可以使用拖放式 UI 创建 Flink 和 Pinot 管道,这隐藏了基础设施配置的复杂性。


其他


Uber 的论文包含了有关实时基础设施、系统设计以及公司如何改进和调整 Kafka、Pinot 或 Presto 等开源解决方案以满足其独特的扩展需求的宝贵经验。


我计划将我的写作主题扩展到系统设计和数据架构等其他领域,特别是大型科技公司如何管理和开发他们的大数据技术堆栈,所以请继续关注我未来的写作;)


现在是时候说再见了,下周见~


参考资料:

  • [1] Yupeng Fu 和 Chinmay Soman,Uber 的实时数据基础设施(2021 年)。

  • [2] Mansoor Iqbal,优步收入与使用统计(2024 年)。

  • [3] Arpit Bhayani,了解读写的一致性及其重要性。

  • [4] Alex Xu,最多一次,至少一次,恰好一次(2022)。

  • [5] Hongliang Xu,uReplicator:Uber Engineering 的可扩展与鲁棒性 Kafka Replicator (2018)。

  • [6] CelerData,计算架构优缺点 — 分散/聚集、MapReduce 和 MPP(大规模并行处理) (2023)

  • [7] Aditi Prakash,揭秘谓词下推:优化数据库查询指南(2023)。


作者:校长

评论